2 * info backend for Halibut
4 * Possible future work:
6 * - configurable indentation, bullets, emphasis, quotes etc?
8 * - configurable choice of how to allocate node names?
9 * + possibly a template-like approach, choosing node names to
10 * be the full section title or perhaps the internal keyword?
11 * + neither of those seems quite right. Perhaps instead a
12 * Windows Help-like mechanism, where a magic config
13 * directive allows user choice of name for every node.
14 * + Only trouble with that is, now what happens to the section
15 * numbers? Do they become completely vestigial and just sit
16 * in the title text of each node? Or do we keep them in the
17 * menus somehow? I think people might occasionally want to
18 * go to a section by number, if only because all the _other_
19 * formats of the same document will reference the numbers
20 * all the time. So our menu lines could look like one of
22 * * Nodename: Section 1.2. Title of section.
23 * * Section 1.2: Nodename. Title of section.
25 * - might be helpful to diagnose duplicate node names!
45 #define EMPTY_INFO_DATA { { 0, 0, NULL }, 0, CHARSET_INIT_STATE, FALSE }
46 static const info_data empty_info_data
= EMPTY_INFO_DATA
;
48 typedef struct node_tag node
;
51 node
*up
, *prev
, *next
, *lastchild
;
52 int pos
, started_menu
, filenum
;
64 static int info_rdadd(info_data
*, wchar_t);
65 static int info_rdadds(info_data
*, wchar_t const *);
66 static int info_rdaddc(info_data
*, char);
67 static int info_rdaddsc(info_data
*, char const *);
69 static void info_heading(info_data
*, word
*, word
*, int);
70 static void info_rule(info_data
*, int, int);
71 static void info_para(info_data
*, word
*, wchar_t *, word
*, keywordlist
*,
73 static void info_codepara(info_data
*, word
*, int, int);
74 static void info_versionid(info_data
*, word
*);
75 static void info_menu_item(info_data
*, node
*, paragraph
*);
76 static word
*info_transform_wordlist(word
*, keywordlist
*);
77 static int info_check_index(word
*, node
*, indexdata
*);
79 static int info_rdaddwc(info_data
*, word
*, word
*, int);
81 static node
*info_node_new(char *name
, int charset
);
82 static char *info_node_name(paragraph
*p
, int charset
);
84 static infoconfig
info_configure(paragraph
*source
) {
90 ret
.filename
= dupstr("output.info");
91 ret
.maxfilesize
= 64 << 10;
92 ret
.charset
= CS_ASCII
;
94 for (; source
; source
= source
->next
) {
95 if (source
->type
== para_Config
) {
96 if (!ustricmp(source
->keyword
, L
"info-filename")) {
98 ret
.filename
= dupstr(adv(source
->origkeyword
));
99 } else if (!ustricmp(source
->keyword
, L
"info-charset")) {
100 char *csname
= utoa_dup(uadv(source
->keyword
), CS_ASCII
);
101 ret
.charset
= charset_from_localenc(csname
);
103 } else if (!ustricmp(source
->keyword
, L
"info-max-file-size")) {
104 ret
.maxfilesize
= utoi(uadv(source
->keyword
));
112 paragraph
*info_config_filename(char *filename
)
114 return cmdline_cfg_simple("info-filename", filename
, NULL
);
117 void info_backend(paragraph
*sourceform
, keywordlist
*keywords
,
118 indexdata
*idx
, void *unused
) {
121 word
*prefix
, *body
, *wp
;
123 wchar_t *prefixextra
;
124 int nesting
, nestindent
;
125 int indentb
, indenta
;
128 info_data intro_text
= EMPTY_INFO_DATA
;
129 node
*topnode
, *currnode
;
134 * FIXME: possibly configurability?
136 int width
= 70, listindentbefore
= 1, listindentafter
= 3;
137 int indent_code
= 2, index_width
= 40;
141 conf
= info_configure(sourceform
);
144 * Go through and create a node for each section.
146 topnode
= info_node_new("Top", conf
.charset
);
148 for (p
= sourceform
; p
; p
= p
->next
) switch (p
->type
) {
154 case para_UnnumberedChapter
:
158 node
*newnode
, *upnode
;
161 nodename
= info_node_name(p
, conf
.charset
);
162 newnode
= info_node_new(nodename
, conf
.charset
);
165 p
->private_data
= newnode
;
168 upnode
= (node
*)p
->parent
->private_data
;
172 newnode
->up
= upnode
;
174 currnode
->next
= newnode
;
175 newnode
->prev
= currnode
;
177 currnode
->listnext
= newnode
;
184 * Set up the display form of each index entry.
190 for (i
= 0; (entry
= index234(idx
->entries
, i
)) != NULL
; i
++) {
191 info_idx
*ii
= mknew(info_idx
);
192 info_data id
= EMPTY_INFO_DATA
;
194 id
.charset
= conf
.charset
;
196 ii
->nnodes
= ii
->nodesize
= 0;
199 ii
->length
= info_rdaddwc(&id
, entry
->text
, NULL
, FALSE
);
201 ii
->text
= id
.output
.text
;
203 entry
->backend_data
= ii
;
208 * An Info file begins with a piece of introductory text which
209 * is apparently never shown anywhere. This seems to me to be a
210 * good place to put the copyright notice and the version IDs.
211 * Also, Info directory entries are expected to go here.
213 intro_text
.charset
= conf
.charset
;
215 info_rdaddsc(&intro_text
,
216 "This Info file generated by Halibut, ");
217 info_rdaddsc(&intro_text
, version
);
218 info_rdaddsc(&intro_text
, "\n\n");
220 for (p
= sourceform
; p
; p
= p
->next
)
221 if (p
->type
== para_Config
&&
222 !ustricmp(p
->keyword
, L
"info-dir-entry")) {
223 wchar_t *section
, *shortname
, *longname
, *kw
;
226 section
= uadv(p
->keyword
);
227 shortname
= *section ?
uadv(section
) : NULL
;
228 longname
= *shortname ?
uadv(shortname
) : NULL
;
229 kw
= *longname ?
uadv(longname
) : NULL
;
232 error(err_infodirentry
, &p
->fpos
);
236 info_rdaddsc(&intro_text
, "INFO-DIR-SECTION ");
237 info_rdadds(&intro_text
, section
);
238 info_rdaddsc(&intro_text
, "\nSTART-INFO-DIR-ENTRY\n* ");
239 info_rdadds(&intro_text
, shortname
);
240 info_rdaddsc(&intro_text
, ": (");
241 s
= dupstr(conf
.filename
);
242 if (strlen(s
) > 5 && !strcmp(s
+strlen(s
)-5, ".info"))
243 s
[strlen(s
)-5] = '\0';
244 info_rdaddsc(&intro_text
, s
);
246 info_rdaddsc(&intro_text
, ")");
248 keyword
*kwl
= kw_lookup(keywords
, kw
);
249 if (kwl
&& kwl
->para
->private_data
) {
250 node
*n
= (node
*)kwl
->para
->private_data
;
251 info_rdaddsc(&intro_text
, n
->name
);
254 info_rdaddsc(&intro_text
, ". ");
255 info_rdadds(&intro_text
, longname
);
256 info_rdaddsc(&intro_text
, "\nEND-INFO-DIR-ENTRY\n\n");
259 for (p
= sourceform
; p
; p
= p
->next
)
260 if (p
->type
== para_Copyright
)
261 info_para(&intro_text
, NULL
, NULL
, p
->words
, keywords
,
264 for (p
= sourceform
; p
; p
= p
->next
)
265 if (p
->type
== para_VersionID
)
266 info_versionid(&intro_text
, p
->words
);
268 if (intro_text
.output
.text
[intro_text
.output
.pos
-1] != '\n')
269 info_rdaddc(&intro_text
, '\n');
272 for (p
= sourceform
; p
; p
= p
->next
)
273 if (p
->type
== para_Title
)
274 info_heading(&topnode
->text
, NULL
, p
->words
, width
);
276 nestindent
= listindentbefore
+ listindentafter
;
281 /* Do the main document */
282 for (p
= sourceform
; p
; p
= p
->next
) switch (p
->type
) {
289 assert(nesting
>= 0);
293 nesting
+= nestindent
;
296 nesting
-= nestindent
;
297 assert(nesting
>= 0);
301 * Things we ignore because we've already processed them or
302 * aren't going to touch them in this pass.
306 case para_Biblio
: /* only touch BiblioCited */
317 case para_UnnumberedChapter
:
320 currnode
= p
->private_data
;
322 assert(currnode
->up
);
324 if (!currnode
->up
->started_menu
) {
325 info_rdaddsc(&currnode
->up
->text
, "* Menu:\n\n");
326 currnode
->up
->started_menu
= TRUE
;
328 info_menu_item(&currnode
->up
->text
, currnode
, p
);
330 has_index
|= info_check_index(p
->words
, currnode
, idx
);
331 info_heading(&currnode
->text
, p
->kwtext
, p
->words
, width
);
336 info_rule(&currnode
->text
, nesting
, width
- nesting
);
341 case para_DescribedThing
:
342 case para_Description
:
343 case para_BiblioCited
:
345 case para_NumberedList
:
346 has_index
|= info_check_index(p
->words
, currnode
, idx
);
347 if (p
->type
== para_Bullet
) {
350 bullet
.type
= word_Normal
;
351 bullet
.text
= L
"-"; /* FIXME: configurability */
354 indentb
= listindentbefore
;
355 indenta
= listindentafter
;
356 } else if (p
->type
== para_NumberedList
) {
358 prefixextra
= L
"."; /* FIXME: configurability */
359 indentb
= listindentbefore
;
360 indenta
= listindentafter
;
361 } else if (p
->type
== para_Description
) {
364 indentb
= listindentbefore
;
365 indenta
= listindentafter
;
369 indentb
= indenta
= 0;
371 if (p
->type
== para_BiblioCited
) {
372 body
= dup_word_list(p
->kwtext
);
373 for (wp
= body
; wp
->next
; wp
= wp
->next
);
374 wp
->next
= &spaceword
;
375 spaceword
.next
= p
->words
;
376 spaceword
.alt
= NULL
;
377 spaceword
.type
= word_WhiteSpace
;
378 spaceword
.text
= NULL
;
383 info_para(&currnode
->text
, prefix
, prefixextra
, body
, keywords
,
384 nesting
+ indentb
, indenta
,
385 width
- nesting
- indentb
- indenta
);
388 free_word_list(body
);
393 info_codepara(&currnode
->text
, p
->words
,
394 nesting
+ indent_code
,
395 width
- nesting
- 2 * indent_code
);
400 * Create an index node if required.
407 newnode
= info_node_new("Index", conf
.charset
);
408 newnode
->up
= topnode
;
410 currnode
->next
= newnode
;
411 newnode
->prev
= currnode
;
412 currnode
->listnext
= newnode
;
414 info_rdaddsc(&newnode
->text
, "Index\n-----\n\n");
416 info_menu_item(&topnode
->text
, newnode
, NULL
);
418 for (i
= 0; (entry
= index234(idx
->entries
, i
)) != NULL
; i
++) {
419 info_idx
*ii
= (info_idx
*)entry
->backend_data
;
421 for (j
= 0; j
< ii
->nnodes
; j
++) {
423 * When we have multiple references for a single
424 * index term, we only display the actual term on
425 * the first line, to make it clear that the terms
426 * really are the same.
429 info_rdaddsc(&newnode
->text
, ii
->text
);
430 for (k
= (j ?
0 : ii
->length
); k
< index_width
; k
++)
431 info_rdaddc(&newnode
->text
, ' ');
432 info_rdaddsc(&newnode
->text
, " *Note ");
433 info_rdaddsc(&newnode
->text
, ii
->nodes
[j
]->name
);
434 info_rdaddsc(&newnode
->text
, "::\n");
440 * Finalise the text of each node, by adding the ^_ delimiter
441 * and the node line at the top.
443 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
) {
444 char *origtext
= currnode
->text
.output
.text
;
445 currnode
->text
= empty_info_data
;
446 currnode
->text
.charset
= conf
.charset
;
447 info_rdaddsc(&currnode
->text
, "\037\nFile: ");
448 info_rdaddsc(&currnode
->text
, conf
.filename
);
449 info_rdaddsc(&currnode
->text
, ", Node: ");
450 info_rdaddsc(&currnode
->text
, currnode
->name
);
451 if (currnode
->prev
) {
452 info_rdaddsc(&currnode
->text
, ", Prev: ");
453 info_rdaddsc(&currnode
->text
, currnode
->prev
->name
);
455 info_rdaddsc(&currnode
->text
, ", Up: ");
456 info_rdaddsc(&currnode
->text
, (currnode
->up ?
457 currnode
->up
->name
: "(dir)"));
458 if (currnode
->next
) {
459 info_rdaddsc(&currnode
->text
, ", Next: ");
460 info_rdaddsc(&currnode
->text
, currnode
->next
->name
);
462 info_rdaddsc(&currnode
->text
, "\n\n");
463 info_rdaddsc(&currnode
->text
, origtext
);
465 * Just make _absolutely_ sure we end with a newline.
467 if (currnode
->text
.output
.text
[currnode
->text
.output
.pos
-1] != '\n')
468 info_rdaddc(&currnode
->text
, '\n');
474 * Compute the offsets for the tag table.
476 filepos
= intro_text
.output
.pos
;
477 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
) {
478 currnode
->pos
= filepos
;
479 filepos
+= currnode
->text
.output
.pos
;
483 * Split into sub-files.
485 if (conf
.maxfilesize
> 0) {
486 int currfilesize
= intro_text
.output
.pos
, currfilenum
= 1;
487 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
) {
488 if (currfilesize
> intro_text
.output
.pos
&&
489 currfilesize
+ currnode
->text
.output
.pos
> conf
.maxfilesize
) {
491 currfilesize
= intro_text
.output
.pos
;
493 currnode
->filenum
= currfilenum
;
494 currfilesize
+= currnode
->text
.output
.pos
;
499 * Write the primary output file.
501 fp
= fopen(conf
.filename
, "w");
503 error(err_cantopenw
, conf
.filename
);
506 fputs(intro_text
.output
.text
, fp
);
507 if (conf
.maxfilesize
== 0) {
508 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
)
509 fputs(currnode
->text
.output
.text
, fp
);
512 fprintf(fp
, "\037\nIndirect:\n");
513 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
)
514 if (filenum
!= currnode
->filenum
) {
515 filenum
= currnode
->filenum
;
516 fprintf(fp
, "%s-%d: %d\n", conf
.filename
, filenum
,
520 fprintf(fp
, "\037\nTag Table:\n");
521 if (conf
.maxfilesize
> 0)
522 fprintf(fp
, "(Indirect)\n");
523 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
)
524 fprintf(fp
, "Node: %s\177%d\n", currnode
->name
, currnode
->pos
);
525 fprintf(fp
, "\037\nEnd Tag Table\n");
529 * Write the subfiles.
531 if (conf
.maxfilesize
> 0) {
535 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
) {
536 if (filenum
!= currnode
->filenum
) {
539 filenum
= currnode
->filenum
;
543 fname
= mknewa(char, strlen(conf
.filename
) + 40);
544 sprintf(fname
, "%s-%d", conf
.filename
, filenum
);
545 fp
= fopen(fname
, "w");
547 error(err_cantopenw
, fname
);
551 fputs(intro_text
.output
.text
, fp
);
553 fputs(currnode
->text
.output
.text
, fp
);
561 static int info_check_index(word
*w
, node
*n
, indexdata
*idx
)
565 for (; w
; w
= w
->next
) {
566 if (w
->type
== word_IndexRef
) {
570 tag
= index_findtag(idx
, w
->text
);
574 for (i
= 0; i
< tag
->nrefs
; i
++) {
575 indexentry
*entry
= tag
->refs
[i
];
576 info_idx
*ii
= (info_idx
*)entry
->backend_data
;
578 if (ii
->nnodes
> 0 && ii
->nodes
[ii
->nnodes
-1] == n
) {
580 * If the same index term is indexed twice
581 * within the same section, we only want to
582 * mention it once in the index. So do nothing
588 if (ii
->nnodes
>= ii
->nodesize
) {
590 ii
->nodes
= resize(ii
->nodes
, ii
->nodesize
);
593 ii
->nodes
[ii
->nnodes
++] = n
;
603 static word
*info_transform_wordlist(word
*words
, keywordlist
*keywords
)
605 word
*ret
= dup_word_list(words
);
609 for (w
= ret
; w
; w
= w
->next
) {
610 w
->private_data
= NULL
;
611 if (w
->type
== word_UpperXref
|| w
->type
== word_LowerXref
) {
612 kwl
= kw_lookup(keywords
, w
->text
);
614 if (kwl
->para
->type
== para_NumberedList
||
615 kwl
->para
->type
== para_BiblioCited
) {
617 * In Info, we do nothing special for xrefs to
618 * numbered list items or bibliography entries.
623 * An xref to a different section has its text
624 * completely replaced.
630 if (w2
->type
== word_XrefEnd
) {
640 * Now w is the UpperXref / LowerXref we
641 * started with, and w4 is the next word after
642 * the corresponding XrefEnd (if any). The
643 * simplest thing is just to stick a pointer to
644 * the target node structure in the private
645 * data field of the xref word, and let
646 * info_rdaddwc and friends read the node name
650 w
->private_data
= kwl
->para
->private_data
;
651 assert(w
->private_data
);
660 static int info_rdaddwc(info_data
*id
, word
*words
, word
*end
, int xrefs
) {
663 for (; words
&& words
!= end
; words
= words
->next
) switch (words
->type
) {
674 case word_WhiteSpace
:
677 case word_WkCodeSpace
:
681 case word_WkCodeQuote
:
682 assert(words
->type
!= word_CodeQuote
&&
683 words
->type
!= word_WkCodeQuote
);
684 if (towordstyle(words
->type
) == word_Emph
&&
685 (attraux(words
->aux
) == attr_First
||
686 attraux(words
->aux
) == attr_Only
))
687 ret
+= info_rdadd(id
, L
'_'); /* FIXME: configurability */
688 else if (towordstyle(words
->type
) == word_Code
&&
689 (attraux(words
->aux
) == attr_First
||
690 attraux(words
->aux
) == attr_Only
))
691 ret
+= info_rdadd(id
, L
'`'); /* FIXME: configurability */
692 if (removeattr(words
->type
) == word_Normal
) {
693 if (cvt_ok(id
->charset
, words
->text
) || !words
->alt
)
694 ret
+= info_rdadds(id
, words
->text
);
696 ret
+= info_rdaddwc(id
, words
->alt
, NULL
, FALSE
);
697 } else if (removeattr(words
->type
) == word_WhiteSpace
) {
698 ret
+= info_rdadd(id
, L
' ');
699 } else if (removeattr(words
->type
) == word_Quote
) {
700 ret
+= info_rdadd(id
, quoteaux(words
->aux
) == quote_Open ? L
'`' : L
'\'');
701 /* FIXME: configurability */
703 if (towordstyle(words
->type
) == word_Emph
&&
704 (attraux(words
->aux
) == attr_Last
||
705 attraux(words
->aux
) == attr_Only
))
706 ret
+= info_rdadd(id
, L
'_'); /* FIXME: configurability */
707 else if (towordstyle(words
->type
) == word_Code
&&
708 (attraux(words
->aux
) == attr_Last
||
709 attraux(words
->aux
) == attr_Only
))
710 ret
+= info_rdadd(id
, L
'\''); /* FIXME: configurability */
715 if (xrefs
&& words
->private_data
) {
717 * This bit is structural and so must be done in char
718 * rather than wchar_t.
720 ret
+= info_rdaddsc(id
, "*Note ");
721 ret
+= info_rdaddsc(id
, ((node
*)words
->private_data
)->name
);
722 ret
+= info_rdaddsc(id
, "::");
730 static int info_width_internal(word
*words
, int xrefs
, int charset
);
732 static int info_width_internal_list(word
*words
, int xrefs
, int charset
) {
735 w
+= info_width_internal(words
, xrefs
, charset
);
741 static int info_width_internal(word
*words
, int xrefs
, int charset
) {
742 switch (words
->type
) {
753 return (((words
->type
== word_Emph
||
754 words
->type
== word_Code
)
755 ?
(attraux(words
->aux
) == attr_Only ?
2 :
756 attraux(words
->aux
) == attr_Always ?
0 : 1)
758 (cvt_ok(charset
, words
->text
) || !words
->alt ?
759 ustrwid(words
->text
, charset
) :
760 info_width_internal_list(words
->alt
, xrefs
, charset
)));
762 case word_WhiteSpace
:
765 case word_WkCodeSpace
:
769 case word_WkCodeQuote
:
770 assert(words
->type
!= word_CodeQuote
&&
771 words
->type
!= word_WkCodeQuote
);
772 return (((towordstyle(words
->type
) == word_Emph
||
773 towordstyle(words
->type
) == word_Code
)
774 ?
(attraux(words
->aux
) == attr_Only ?
2 :
775 attraux(words
->aux
) == attr_Always ?
0 : 1)
780 if (xrefs
&& words
->private_data
) {
781 /* "*Note " plus "::" comes to 8 characters */
782 return 8 + strwid(((node
*)words
->private_data
)->name
, charset
);
786 return 0; /* should never happen */
789 static int info_width_noxrefs(void *ctx
, word
*words
)
791 return info_width_internal(words
, FALSE
, *(int *)ctx
);
793 static int info_width_xrefs(void *ctx
, word
*words
)
795 return info_width_internal(words
, TRUE
, *(int *)ctx
);
798 static void info_heading(info_data
*text
, word
*tprefix
,
799 word
*words
, int width
) {
801 int firstlinewidth
, wrapwidth
;
802 wrappedline
*wrapping
, *p
;
806 length
+= info_rdaddwc(text
, tprefix
, NULL
, FALSE
);
807 length
+= info_rdadds(text
, L
": ");/* FIXME: configurability */
811 firstlinewidth
= width
- length
;
813 wrapping
= wrap_para(words
, firstlinewidth
, wrapwidth
,
814 info_width_noxrefs
, &text
->charset
, 0);
815 for (p
= wrapping
; p
; p
= p
->next
) {
816 length
+= info_rdaddwc(text
, p
->begin
, p
->end
, FALSE
);
817 info_rdadd(text
, L
'\n');
819 info_rdadd(text
, L
'-'); /* FIXME: configurability */
820 info_rdadd(text
, L
'\n');
824 info_rdadd(text
, L
'\n');
827 static void info_rule(info_data
*text
, int indent
, int width
) {
828 while (indent
--) info_rdadd(text
, L
' ');
829 while (width
--) info_rdadd(text
, L
'-');
830 info_rdadd(text
, L
'\n');
831 info_rdadd(text
, L
'\n');
834 static void info_para(info_data
*text
, word
*prefix
, wchar_t *prefixextra
,
835 word
*input
, keywordlist
*keywords
,
836 int indent
, int extraindent
, int width
) {
837 wrappedline
*wrapping
, *p
;
841 int firstlinewidth
= width
;
843 words
= info_transform_wordlist(input
, keywords
);
846 for (i
= 0; i
< indent
; i
++)
847 info_rdadd(text
, L
' ');
848 e
= info_rdaddwc(text
, prefix
, NULL
, FALSE
);
850 e
+= info_rdadds(text
, prefixextra
);
851 /* If the prefix is too long, shorten the first line to fit. */
854 firstlinewidth
+= e
; /* this decreases it, since e < 0 */
855 if (firstlinewidth
< 0) {
856 e
= indent
+ extraindent
;
857 firstlinewidth
= width
;
858 info_rdadd(text
, L
'\n');
863 e
= indent
+ extraindent
;
865 wrapping
= wrap_para(words
, firstlinewidth
, width
, info_width_xrefs
,
867 for (p
= wrapping
; p
; p
= p
->next
) {
868 for (i
= 0; i
< e
; i
++)
869 info_rdadd(text
, L
' ');
870 info_rdaddwc(text
, p
->begin
, p
->end
, TRUE
);
871 info_rdadd(text
, L
'\n');
872 e
= indent
+ extraindent
;
875 info_rdadd(text
, L
'\n');
877 free_word_list(words
);
880 static void info_codepara(info_data
*text
, word
*words
,
881 int indent
, int width
) {
884 for (; words
; words
= words
->next
) if (words
->type
== word_WeakCode
) {
885 for (i
= 0; i
< indent
; i
++)
886 info_rdadd(text
, L
' ');
887 if (info_rdadds(text
, words
->text
) > width
) {
890 info_rdadd(text
, L
'\n');
893 info_rdadd(text
, L
'\n');
896 static void info_versionid(info_data
*text
, word
*words
) {
897 info_rdadd(text
, L
'['); /* FIXME: configurability */
898 info_rdaddwc(text
, words
, NULL
, FALSE
);
899 info_rdadds(text
, L
"]\n");
902 static node
*info_node_new(char *name
, int charset
)
907 n
->text
= empty_info_data
;
908 n
->text
.charset
= charset
;
909 n
->up
= n
->next
= n
->prev
= n
->lastchild
= n
->listnext
= NULL
;
910 n
->name
= dupstr(name
);
911 n
->started_menu
= FALSE
;
916 static char *info_node_name(paragraph
*par
, int charset
)
918 info_data id
= EMPTY_INFO_DATA
;
921 id
.charset
= charset
;
922 info_rdaddwc(&id
, par
->kwtext ? par
->kwtext
: par
->words
, NULL
, FALSE
);
923 info_rdaddsc(&id
, NULL
);
926 * We cannot have commas or colons in a node name. Remove any
927 * that we find, with a warning.
929 p
= q
= id
.output
.text
;
931 if (*p
== ':' || *p
== ',') {
932 error(err_infonodechar
, &par
->fpos
, *p
);
940 return id
.output
.text
;
943 static void info_menu_item(info_data
*text
, node
*n
, paragraph
*p
)
946 * FIXME: Depending on how we're doing node names in this info
947 * file, we might want to do
949 * * Node name:: Chapter title
953 * * Chapter number: Node name.
955 * This function mostly works in char rather than wchar_t,
956 * because a menu item is a structural component.
958 info_rdaddsc(text
, "* ");
959 info_rdaddsc(text
, n
->name
);
960 info_rdaddsc(text
, "::");
962 info_rdaddc(text
, ' ');
963 info_rdaddwc(text
, p
->words
, NULL
, FALSE
);
965 info_rdaddc(text
, '\n');
969 * These functions implement my wrapper on the rdadd* calls which
970 * allows me to switch arbitrarily between literal octet-string
971 * text and charset-translated Unicode. (Because no matter what
972 * character set I write the actual text in, I expect info readers
973 * to treat node names and file names literally and to expect
974 * keywords like `*Note' in their canonical form, so I have to take
975 * steps to ensure that those structural elements of the file
976 * aren't messed with.)
978 static int info_rdadds(info_data
*d
, wchar_t const *wcs
)
981 d
->state
= charset_init_state
;
989 width
= ustrwid(wcs
, d
->charset
);
995 ret
= charset_from_unicode(&wcs
, &len
, buf
, lenof(buf
),
996 d
->charset
, &d
->state
, NULL
);
998 assert(len
< prevlen
);
1002 rdaddsc(&d
->output
, buf
);
1011 static int info_rdaddsc(info_data
*d
, char const *cs
)
1017 ret
= charset_from_unicode(NULL
, 0, buf
, lenof(buf
),
1018 d
->charset
, &d
->state
, NULL
);
1021 rdaddsc(&d
->output
, buf
);
1028 rdaddsc(&d
->output
, cs
);
1034 static int info_rdadd(info_data
*d
, wchar_t wc
)
1039 return info_rdadds(d
, wcs
);
1042 static int info_rdaddc(info_data
*d
, char c
)
1047 return info_rdaddsc(d
, cs
);