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!
38 typedef struct node_tag node
;
41 node
*up
, *prev
, *next
, *lastchild
;
42 int pos
, started_menu
, filenum
;
53 static int info_convert(wchar_t *, char **);
55 static void info_heading(rdstringc
*, word
*, word
*, int);
56 static void info_rule(rdstringc
*, int, int);
57 static void info_para(rdstringc
*, word
*, char *, word
*, keywordlist
*,
59 static void info_codepara(rdstringc
*, word
*, int, int);
60 static void info_versionid(rdstringc
*, word
*);
61 static void info_menu_item(rdstringc
*, node
*, paragraph
*);
62 static word
*info_transform_wordlist(word
*, keywordlist
*);
63 static int info_check_index(word
*, node
*, indexdata
*);
65 static void info_rdaddwc(rdstringc
*, word
*, word
*, int);
67 static node
*info_node_new(char *name
);
68 static char *info_node_name(paragraph
*p
);
70 static infoconfig
info_configure(paragraph
*source
) {
76 ret
.filename
= dupstr("output.info");
77 ret
.maxfilesize
= 64 << 10;
79 for (; source
; source
= source
->next
) {
80 if (source
->type
== para_Config
) {
81 if (!ustricmp(source
->keyword
, L
"info-filename")) {
83 ret
.filename
= dupstr(adv(source
->origkeyword
));
84 } else if (!ustricmp(source
->keyword
, L
"info-max-file-size")) {
85 ret
.maxfilesize
= utoi(uadv(source
->keyword
));
93 paragraph
*info_config_filename(char *filename
)
95 return cmdline_cfg_simple("info-filename", filename
, NULL
);
98 void info_backend(paragraph
*sourceform
, keywordlist
*keywords
,
99 indexdata
*idx
, void *unused
) {
102 word
*prefix
, *body
, *wp
;
105 int nesting
, nestindent
;
106 int indentb
, indenta
;
109 rdstringc intro_text
= { 0, 0, NULL
};
110 node
*topnode
, *currnode
;
117 int width
= 70, listindentbefore
= 1, listindentafter
= 3;
118 int indent_code
= 2, index_width
= 40;
122 conf
= info_configure(sourceform
);
125 * Go through and create a node for each section.
127 topnode
= info_node_new("Top");
129 for (p
= sourceform
; p
; p
= p
->next
) switch (p
->type
) {
135 case para_UnnumberedChapter
:
139 node
*newnode
, *upnode
;
142 nodename
= info_node_name(p
);
143 newnode
= info_node_new(nodename
);
146 p
->private_data
= newnode
;
149 upnode
= (node
*)p
->parent
->private_data
;
153 newnode
->up
= upnode
;
155 currnode
->next
= newnode
;
156 newnode
->prev
= currnode
;
158 currnode
->listnext
= newnode
;
165 * Set up the display form of each index entry.
171 for (i
= 0; (entry
= index234(idx
->entries
, i
)) != NULL
; i
++) {
172 info_idx
*ii
= mknew(info_idx
);
173 rdstringc rs
= { 0, 0, NULL
};
175 ii
->nnodes
= ii
->nodesize
= 0;
178 info_rdaddwc(&rs
, entry
->text
, NULL
, FALSE
);
182 entry
->backend_data
= ii
;
187 * An Info file begins with a piece of introductory text which
188 * is apparently never shown anywhere. This seems to me to be a
189 * good place to put the copyright notice and the version IDs.
190 * Also, Info directory entries are expected to go here.
194 "This Info file generated by Halibut, ");
195 rdaddsc(&intro_text
, version
);
196 rdaddsc(&intro_text
, "\n\n");
198 for (p
= sourceform
; p
; p
= p
->next
)
199 if (p
->type
== para_Config
&&
200 !ustricmp(p
->keyword
, L
"info-dir-entry")) {
201 wchar_t *section
, *shortname
, *longname
, *kw
;
204 section
= uadv(p
->keyword
);
205 shortname
= *section ?
uadv(section
) : NULL
;
206 longname
= *shortname ?
uadv(shortname
) : NULL
;
207 kw
= *longname ?
uadv(longname
) : NULL
;
210 error(err_infodirentry
, &p
->fpos
);
214 rdaddsc(&intro_text
, "INFO-DIR-SECTION ");
215 s
= utoa_dup(section
, CS_FIXME
);
216 rdaddsc(&intro_text
, s
);
218 rdaddsc(&intro_text
, "\nSTART-INFO-DIR-ENTRY\n* ");
219 s
= utoa_dup(shortname
, CS_FIXME
);
220 rdaddsc(&intro_text
, s
);
222 rdaddsc(&intro_text
, ": (");
223 s
= dupstr(conf
.filename
);
224 if (strlen(s
) > 5 && !strcmp(s
+strlen(s
)-5, ".info"))
225 s
[strlen(s
)-5] = '\0';
226 rdaddsc(&intro_text
, s
);
228 rdaddsc(&intro_text
, ")");
230 keyword
*kwl
= kw_lookup(keywords
, kw
);
231 if (kwl
&& kwl
->para
->private_data
) {
232 node
*n
= (node
*)kwl
->para
->private_data
;
233 rdaddsc(&intro_text
, n
->name
);
236 rdaddsc(&intro_text
, ". ");
237 s
= utoa_dup(longname
, CS_FIXME
);
238 rdaddsc(&intro_text
, s
);
240 rdaddsc(&intro_text
, "\nEND-INFO-DIR-ENTRY\n\n");
243 for (p
= sourceform
; p
; p
= p
->next
)
244 if (p
->type
== para_Copyright
)
245 info_para(&intro_text
, NULL
, NULL
, p
->words
, keywords
,
248 for (p
= sourceform
; p
; p
= p
->next
)
249 if (p
->type
== para_VersionID
)
250 info_versionid(&intro_text
, p
->words
);
252 if (intro_text
.text
[intro_text
.pos
-1] != '\n')
253 rdaddc(&intro_text
, '\n');
256 for (p
= sourceform
; p
; p
= p
->next
)
257 if (p
->type
== para_Title
)
258 info_heading(&topnode
->text
, NULL
, p
->words
, width
);
260 nestindent
= listindentbefore
+ listindentafter
;
265 /* Do the main document */
266 for (p
= sourceform
; p
; p
= p
->next
) switch (p
->type
) {
273 assert(nesting
>= 0);
277 nesting
+= nestindent
;
280 nesting
-= nestindent
;
281 assert(nesting
>= 0);
285 * Things we ignore because we've already processed them or
286 * aren't going to touch them in this pass.
290 case para_Biblio
: /* only touch BiblioCited */
301 case para_UnnumberedChapter
:
304 currnode
= p
->private_data
;
306 assert(currnode
->up
);
308 if (!currnode
->up
->started_menu
) {
309 rdaddsc(&currnode
->up
->text
, "* Menu:\n\n");
310 currnode
->up
->started_menu
= TRUE
;
312 info_menu_item(&currnode
->up
->text
, currnode
, p
);
314 has_index
|= info_check_index(p
->words
, currnode
, idx
);
315 info_heading(&currnode
->text
, p
->kwtext
, p
->words
, width
);
320 info_rule(&currnode
->text
, nesting
, width
- nesting
);
325 case para_DescribedThing
:
326 case para_Description
:
327 case para_BiblioCited
:
329 case para_NumberedList
:
330 has_index
|= info_check_index(p
->words
, currnode
, idx
);
331 if (p
->type
== para_Bullet
) {
334 bullet
.type
= word_Normal
;
335 bullet
.text
= L
"-"; /* FIXME: configurability */
338 indentb
= listindentbefore
;
339 indenta
= listindentafter
;
340 } else if (p
->type
== para_NumberedList
) {
342 prefixextra
= "."; /* FIXME: configurability */
343 indentb
= listindentbefore
;
344 indenta
= listindentafter
;
345 } else if (p
->type
== para_Description
) {
348 indentb
= listindentbefore
;
349 indenta
= listindentafter
;
353 indentb
= indenta
= 0;
355 if (p
->type
== para_BiblioCited
) {
356 body
= dup_word_list(p
->kwtext
);
357 for (wp
= body
; wp
->next
; wp
= wp
->next
);
358 wp
->next
= &spaceword
;
359 spaceword
.next
= p
->words
;
360 spaceword
.alt
= NULL
;
361 spaceword
.type
= word_WhiteSpace
;
362 spaceword
.text
= NULL
;
367 info_para(&currnode
->text
, prefix
, prefixextra
, body
, keywords
,
368 nesting
+ indentb
, indenta
,
369 width
- nesting
- indentb
- indenta
);
372 free_word_list(body
);
377 info_codepara(&currnode
->text
, p
->words
,
378 nesting
+ indent_code
,
379 width
- nesting
- 2 * indent_code
);
384 * Create an index node if required.
391 newnode
= info_node_new("Index");
392 newnode
->up
= topnode
;
394 currnode
->next
= newnode
;
395 newnode
->prev
= currnode
;
396 currnode
->listnext
= newnode
;
398 rdaddsc(&newnode
->text
, "Index\n-----\n\n");
400 info_menu_item(&topnode
->text
, newnode
, NULL
);
402 for (i
= 0; (entry
= index234(idx
->entries
, i
)) != NULL
; i
++) {
403 info_idx
*ii
= (info_idx
*)entry
->backend_data
;
405 for (j
= 0; j
< ii
->nnodes
; j
++) {
406 int pos0
= newnode
->text
.pos
;
408 * When we have multiple references for a single
409 * index term, we only display the actual term on
410 * the first line, to make it clear that the terms
411 * really are the same.
414 rdaddsc(&newnode
->text
, ii
->text
);
415 for (k
= newnode
->text
.pos
- pos0
; k
< index_width
; k
++)
416 rdaddc(&newnode
->text
, ' ');
417 rdaddsc(&newnode
->text
, " *Note ");
418 rdaddsc(&newnode
->text
, ii
->nodes
[j
]->name
);
419 rdaddsc(&newnode
->text
, "::\n");
425 * Finalise the text of each node, by adding the ^_ delimiter
426 * and the node line at the top.
428 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
) {
429 char *origtext
= currnode
->text
.text
;
430 currnode
->text
.text
= NULL
;
431 currnode
->text
.pos
= currnode
->text
.size
= 0;
432 rdaddsc(&currnode
->text
, "\037\nFile: ");
433 rdaddsc(&currnode
->text
, conf
.filename
);
434 rdaddsc(&currnode
->text
, ", Node: ");
435 rdaddsc(&currnode
->text
, currnode
->name
);
436 if (currnode
->prev
) {
437 rdaddsc(&currnode
->text
, ", Prev: ");
438 rdaddsc(&currnode
->text
, currnode
->prev
->name
);
440 rdaddsc(&currnode
->text
, ", Up: ");
441 rdaddsc(&currnode
->text
, (currnode
->up ?
442 currnode
->up
->name
: "(dir)"));
443 if (currnode
->next
) {
444 rdaddsc(&currnode
->text
, ", Next: ");
445 rdaddsc(&currnode
->text
, currnode
->next
->name
);
447 rdaddsc(&currnode
->text
, "\n\n");
448 rdaddsc(&currnode
->text
, origtext
);
450 * Just make _absolutely_ sure we end with a newline.
452 if (currnode
->text
.text
[currnode
->text
.pos
-1] != '\n')
453 rdaddc(&currnode
->text
, '\n');
459 * Compute the offsets for the tag table.
461 filepos
= intro_text
.pos
;
462 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
) {
463 currnode
->pos
= filepos
;
464 filepos
+= currnode
->text
.pos
;
468 * Split into sub-files.
470 if (conf
.maxfilesize
> 0) {
471 int currfilesize
= intro_text
.pos
, currfilenum
= 1;
472 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
) {
473 if (currfilesize
> intro_text
.pos
&&
474 currfilesize
+ currnode
->text
.pos
> conf
.maxfilesize
) {
476 currfilesize
= intro_text
.pos
;
478 currnode
->filenum
= currfilenum
;
479 currfilesize
+= currnode
->text
.pos
;
484 * Write the primary output file.
486 fp
= fopen(conf
.filename
, "w");
488 error(err_cantopenw
, conf
.filename
);
491 fputs(intro_text
.text
, fp
);
492 if (conf
.maxfilesize
== 0) {
493 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
)
494 fputs(currnode
->text
.text
, fp
);
497 fprintf(fp
, "\037\nIndirect:\n");
498 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
)
499 if (filenum
!= currnode
->filenum
) {
500 filenum
= currnode
->filenum
;
501 fprintf(fp
, "%s-%d: %d\n", conf
.filename
, filenum
,
505 fprintf(fp
, "\037\nTag Table:\n");
506 if (conf
.maxfilesize
> 0)
507 fprintf(fp
, "(Indirect)\n");
508 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
)
509 fprintf(fp
, "Node: %s\177%d\n", currnode
->name
, currnode
->pos
);
510 fprintf(fp
, "\037\nEnd Tag Table\n");
514 * Write the subfiles.
516 if (conf
.maxfilesize
> 0) {
520 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
) {
521 if (filenum
!= currnode
->filenum
) {
524 filenum
= currnode
->filenum
;
528 fname
= mknewa(char, strlen(conf
.filename
) + 40);
529 sprintf(fname
, "%s-%d", conf
.filename
, filenum
);
530 fp
= fopen(fname
, "w");
532 error(err_cantopenw
, fname
);
536 fputs(intro_text
.text
, fp
);
538 fputs(currnode
->text
.text
, fp
);
546 static int info_check_index(word
*w
, node
*n
, indexdata
*idx
)
550 for (; w
; w
= w
->next
) {
551 if (w
->type
== word_IndexRef
) {
555 tag
= index_findtag(idx
, w
->text
);
559 for (i
= 0; i
< tag
->nrefs
; i
++) {
560 indexentry
*entry
= tag
->refs
[i
];
561 info_idx
*ii
= (info_idx
*)entry
->backend_data
;
563 if (ii
->nnodes
> 0 && ii
->nodes
[ii
->nnodes
-1] == n
) {
565 * If the same index term is indexed twice
566 * within the same section, we only want to
567 * mention it once in the index. So do nothing
573 if (ii
->nnodes
>= ii
->nodesize
) {
575 ii
->nodes
= resize(ii
->nodes
, ii
->nodesize
);
578 ii
->nodes
[ii
->nnodes
++] = n
;
589 * Convert a wide string into a string of chars. If `result' is
590 * non-NULL, mallocs the resulting string and stores a pointer to
591 * it in `*result'. If `result' is NULL, merely checks whether all
592 * characters in the string are feasible for the output character
595 * Return is nonzero if all characters are OK. If not all
596 * characters are OK but `result' is non-NULL, a result _will_
597 * still be generated!
599 static int info_convert(wchar_t *s
, char **result
) {
601 * FIXME. Currently this is ISO8859-1 only.
603 int doing
= (result
!= 0);
606 int plen
= 0, psize
= 0;
612 if ((c
>= 32 && c
<= 126) ||
613 (c
>= 160 && c
<= 255)) {
617 /* Char is not OK. */
619 outc
= 0xBF; /* approximate the good old DEC `uh?' */
624 p
= resize(p
, psize
);
630 p
= resize(p
, plen
+1);
637 static word
*info_transform_wordlist(word
*words
, keywordlist
*keywords
)
639 word
*ret
= dup_word_list(words
);
643 for (w
= ret
; w
; w
= w
->next
) {
644 w
->private_data
= NULL
;
645 if (w
->type
== word_UpperXref
|| w
->type
== word_LowerXref
) {
646 kwl
= kw_lookup(keywords
, w
->text
);
648 if (kwl
->para
->type
== para_NumberedList
||
649 kwl
->para
->type
== para_BiblioCited
) {
651 * In Info, we do nothing special for xrefs to
652 * numbered list items or bibliography entries.
657 * An xref to a different section has its text
658 * completely replaced.
664 if (w2
->type
== word_XrefEnd
) {
674 * Now w is the UpperXref / LowerXref we
675 * started with, and w4 is the next word after
676 * the corresponding XrefEnd (if any). The
677 * simplest thing is just to stick a pointer to
678 * the target node structure in the private
679 * data field of the xref word, and let
680 * info_rdaddwc and friends read the node name
684 w
->private_data
= kwl
->para
->private_data
;
685 assert(w
->private_data
);
694 static void info_rdaddwc(rdstringc
*rs
, word
*words
, word
*end
, int xrefs
) {
697 for (; words
&& words
!= end
; words
= words
->next
) switch (words
->type
) {
708 case word_WhiteSpace
:
711 case word_WkCodeSpace
:
715 case word_WkCodeQuote
:
716 assert(words
->type
!= word_CodeQuote
&&
717 words
->type
!= word_WkCodeQuote
);
718 if (towordstyle(words
->type
) == word_Emph
&&
719 (attraux(words
->aux
) == attr_First
||
720 attraux(words
->aux
) == attr_Only
))
721 rdaddc(rs
, '_'); /* FIXME: configurability */
722 else if (towordstyle(words
->type
) == word_Code
&&
723 (attraux(words
->aux
) == attr_First
||
724 attraux(words
->aux
) == attr_Only
))
725 rdaddc(rs
, '`'); /* FIXME: configurability */
726 if (removeattr(words
->type
) == word_Normal
) {
727 if (info_convert(words
->text
, &c
) || !words
->alt
)
730 info_rdaddwc(rs
, words
->alt
, NULL
, FALSE
);
732 } else if (removeattr(words
->type
) == word_WhiteSpace
) {
734 } else if (removeattr(words
->type
) == word_Quote
) {
735 rdaddc(rs
, quoteaux(words
->aux
) == quote_Open ?
'`' : '\'');
736 /* FIXME: configurability */
738 if (towordstyle(words
->type
) == word_Emph
&&
739 (attraux(words
->aux
) == attr_Last
||
740 attraux(words
->aux
) == attr_Only
))
741 rdaddc(rs
, '_'); /* FIXME: configurability */
742 else if (towordstyle(words
->type
) == word_Code
&&
743 (attraux(words
->aux
) == attr_Last
||
744 attraux(words
->aux
) == attr_Only
))
745 rdaddc(rs
, '\''); /* FIXME: configurability */
750 if (xrefs
&& words
->private_data
) {
751 rdaddsc(rs
, "*Note ");
752 rdaddsc(rs
, ((node
*)words
->private_data
)->name
);
759 static int info_width_internal(word
*words
, int xrefs
);
761 static int info_width_internal_list(word
*words
, int xrefs
) {
764 w
+= info_width_internal(words
, xrefs
);
770 static int info_width_internal(word
*words
, int xrefs
) {
771 switch (words
->type
) {
782 return (((words
->type
== word_Emph
||
783 words
->type
== word_Code
)
784 ?
(attraux(words
->aux
) == attr_Only ?
2 :
785 attraux(words
->aux
) == attr_Always ?
0 : 1)
787 (info_convert(words
->text
, NULL
) || !words
->alt ?
788 ustrlen(words
->text
) :
789 info_width_internal_list(words
->alt
, xrefs
)));
791 case word_WhiteSpace
:
794 case word_WkCodeSpace
:
798 case word_WkCodeQuote
:
799 assert(words
->type
!= word_CodeQuote
&&
800 words
->type
!= word_WkCodeQuote
);
801 return (((towordstyle(words
->type
) == word_Emph
||
802 towordstyle(words
->type
) == word_Code
)
803 ?
(attraux(words
->aux
) == attr_Only ?
2 :
804 attraux(words
->aux
) == attr_Always ?
0 : 1)
809 if (xrefs
&& words
->private_data
) {
810 /* "*Note " plus "::" comes to 8 characters */
811 return 8 + strlen(((node
*)words
->private_data
)->name
);
815 return 0; /* should never happen */
818 static int info_width_noxrefs(void *ctx
, word
*words
)
821 return info_width_internal(words
, FALSE
);
823 static int info_width_xrefs(void *ctx
, word
*words
)
826 return info_width_internal(words
, TRUE
);
829 static void info_heading(rdstringc
*text
, word
*tprefix
,
830 word
*words
, int width
) {
831 rdstringc t
= { 0, 0, NULL
};
833 int firstlinewidth
, wrapwidth
;
835 wrappedline
*wrapping
, *p
;
838 info_rdaddwc(&t
, tprefix
, NULL
, FALSE
);
839 rdaddsc(&t
, ": "); /* FIXME: configurability */
841 margin
= length
= (t
.text ?
strlen(t
.text
) : 0);
844 firstlinewidth
= width
- length
;
847 wrapping
= wrap_para(words
, firstlinewidth
, wrapwidth
,
848 info_width_noxrefs
, NULL
, 0);
849 for (p
= wrapping
; p
; p
= p
->next
) {
850 info_rdaddwc(&t
, p
->begin
, p
->end
, FALSE
);
851 length
= (t
.text ?
strlen(t
.text
) : 0);
852 for (i
= 0; i
< margin
; i
++)
854 rdaddsc(text
, t
.text
);
856 for (i
= 0; i
< margin
; i
++)
871 static void info_rule(rdstringc
*text
, int indent
, int width
) {
872 while (indent
--) rdaddc(text
, ' ');
873 while (width
--) rdaddc(text
, '-');
878 static void info_para(rdstringc
*text
, word
*prefix
, char *prefixextra
,
879 word
*input
, keywordlist
*keywords
,
880 int indent
, int extraindent
, int width
) {
881 wrappedline
*wrapping
, *p
;
883 rdstringc pfx
= { 0, 0, NULL
};
886 int firstlinewidth
= width
;
888 words
= info_transform_wordlist(input
, keywords
);
891 info_rdaddwc(&pfx
, prefix
, NULL
, FALSE
);
893 rdaddsc(&pfx
, prefixextra
);
894 for (i
= 0; i
< indent
; i
++)
896 rdaddsc(text
, pfx
.text
);
897 /* If the prefix is too long, shorten the first line to fit. */
898 e
= extraindent
- strlen(pfx
.text
);
900 firstlinewidth
+= e
; /* this decreases it, since e < 0 */
901 if (firstlinewidth
< 0) {
902 e
= indent
+ extraindent
;
903 firstlinewidth
= width
;
910 e
= indent
+ extraindent
;
912 wrapping
= wrap_para(words
, firstlinewidth
, width
, info_width_xrefs
,
914 for (p
= wrapping
; p
; p
= p
->next
) {
915 for (i
= 0; i
< e
; i
++)
917 info_rdaddwc(text
, p
->begin
, p
->end
, TRUE
);
919 e
= indent
+ extraindent
;
924 free_word_list(words
);
927 static void info_codepara(rdstringc
*text
, word
*words
,
928 int indent
, int width
) {
931 for (; words
; words
= words
->next
) if (words
->type
== word_WeakCode
) {
933 info_convert(words
->text
, &c
);
934 if (strlen(c
) > (size_t)width
) {
937 for (i
= 0; i
< indent
; i
++)
947 static void info_versionid(rdstringc
*text
, word
*words
) {
948 rdaddc(text
, '['); /* FIXME: configurability */
949 info_rdaddwc(text
, words
, NULL
, FALSE
);
950 rdaddsc(text
, "]\n");
953 static node
*info_node_new(char *name
)
959 n
->text
.pos
= n
->text
.size
= 0;
960 n
->up
= n
->next
= n
->prev
= n
->lastchild
= n
->listnext
= NULL
;
961 n
->name
= dupstr(name
);
962 n
->started_menu
= FALSE
;
967 static char *info_node_name(paragraph
*par
)
969 rdstringc rsc
= { 0, 0, NULL
};
971 info_rdaddwc(&rsc
, par
->kwtext ? par
->kwtext
: par
->words
, NULL
, FALSE
);
974 * We cannot have commas or colons in a node name. Remove any
975 * that we find, with a warning.
979 if (*p
== ':' || *p
== ',') {
980 error(err_infonodechar
, &par
->fpos
, *p
);
991 static void info_menu_item(rdstringc
*text
, node
*n
, paragraph
*p
)
994 * FIXME: Depending on how we're doing node names in this info
995 * file, we might want to do
997 * * Node name:: Chapter title
1001 * * Chapter number: Node name.
1005 rdaddsc(text
, "* ");
1006 rdaddsc(text
, n
->name
);
1007 rdaddsc(text
, "::");
1010 info_rdaddwc(text
, p
->words
, NULL
, FALSE
);