2 * info backend for Halibut
6 * - configurable choice of how to allocate node names
7 * - might be helpful to diagnose duplicate node names!
8 * - test everything in info(1), and probably jed too
12 * - configurable indentation, bullets, emphasis, quotes etc?
25 typedef struct node_tag node
;
28 node
*up
, *prev
, *next
, *lastchild
;
29 int pos
, started_menu
, filenum
;
40 static int info_convert(wchar_t *, char **);
42 static void info_heading(rdstringc
*, word
*, word
*, int);
43 static void info_rule(rdstringc
*, int, int);
44 static void info_para(rdstringc
*, word
*, char *, word
*, keywordlist
*,
46 static void info_codepara(rdstringc
*, word
*, int, int);
47 static void info_versionid(rdstringc
*, word
*);
48 static void info_menu_item(rdstringc
*, node
*, paragraph
*);
49 static word
*info_transform_wordlist(word
*, keywordlist
*);
50 static int info_check_index(word
*, node
*, indexdata
*);
52 static void info_rdaddwc(rdstringc
*, word
*, word
*, int);
54 static node
*info_node_new(char *name
);
55 static char *info_node_name(paragraph
*p
);
57 static infoconfig
info_configure(paragraph
*source
) {
63 ret
.filename
= dupstr("output.info");
64 ret
.maxfilesize
= 64 << 10;
66 for (; source
; source
= source
->next
) {
67 if (source
->type
== para_Config
) {
68 if (!ustricmp(source
->keyword
, L
"info-filename")) {
70 ret
.filename
= utoa_dup(uadv(source
->keyword
));
71 } else if (!ustricmp(source
->keyword
, L
"info-max-file-size")) {
72 ret
.maxfilesize
= utoi(uadv(source
->keyword
));
80 paragraph
*info_config_filename(char *filename
)
83 wchar_t *ufilename
, *up
;
87 memset(p
, 0, sizeof(*p
));
88 p
->type
= para_Config
;
90 p
->fpos
.filename
= "<command line>";
91 p
->fpos
.line
= p
->fpos
.col
= -1;
93 ufilename
= ufroma_dup(filename
);
94 len
= ustrlen(ufilename
) + 2 + lenof(L
"info-filename");
95 p
->keyword
= mknewa(wchar_t, len
);
97 ustrcpy(up
, L
"info-filename");
99 ustrcpy(up
, ufilename
);
102 assert(up
- p
->keyword
< len
);
108 void info_backend(paragraph
*sourceform
, keywordlist
*keywords
,
112 word
*prefix
, *body
, *wp
;
115 int nesting
, nestindent
;
116 int indentb
, indenta
;
119 rdstringc intro_text
= { 0, 0, NULL
};
120 node
*topnode
, *currnode
;
127 int width
= 70, listindentbefore
= 1, listindentafter
= 3;
128 int indent_code
= 2, index_width
= 40;
130 IGNORE(keywords
); /* we don't happen to need this */
131 IGNORE(idx
); /* or this */
133 conf
= info_configure(sourceform
);
136 * Go through and create a node for each section.
138 topnode
= info_node_new("Top");
140 for (p
= sourceform
; p
; p
= p
->next
) switch (p
->type
) {
146 case para_UnnumberedChapter
:
150 node
*newnode
, *upnode
;
153 nodename
= info_node_name(p
);
154 newnode
= info_node_new(nodename
);
157 p
->private_data
= newnode
;
160 upnode
= (node
*)p
->parent
->private_data
;
164 newnode
->up
= upnode
;
166 currnode
->next
= newnode
;
167 newnode
->prev
= currnode
;
169 currnode
->listnext
= newnode
;
176 * Set up the display form of each index entry.
182 for (i
= 0; (entry
= index234(idx
->entries
, i
)) != NULL
; i
++) {
183 info_idx
*ii
= mknew(info_idx
);
184 rdstringc rs
= { 0, 0, NULL
};
187 ii
->nnodes
= ii
->nodesize
= 0;
190 info_rdaddwc(&rs
, entry
->text
, NULL
, FALSE
);
193 * We cannot have colons in index terms, since they
194 * disrupt the structure of the index menu. Remove any
195 * that we find, with a warning.
200 error(err_infoindexcolon
, &entry
->fpos
);
209 entry
->backend_data
= ii
;
214 * An Info file begins with a piece of introductory text which
215 * is apparently never shown anywhere. This seems to me to be a
216 * good place to put the copyright notice and the version IDs.
217 * Also, Info directory entries are expected to go here.
221 "This Info file generated by Halibut, ");
222 rdaddsc(&intro_text
, version
);
223 rdaddsc(&intro_text
, "\n\n");
225 for (p
= sourceform
; p
; p
= p
->next
)
226 if (p
->type
== para_Config
&&
227 !ustricmp(p
->keyword
, L
"info-dir-entry")) {
228 wchar_t *section
, *shortname
, *longname
, *kw
;
231 section
= uadv(p
->keyword
);
232 shortname
= *section ?
uadv(section
) : NULL
;
233 longname
= *shortname ?
uadv(shortname
) : NULL
;
234 kw
= *longname ?
uadv(longname
) : NULL
;
237 error(err_infodirentry
, &p
->fpos
);
241 rdaddsc(&intro_text
, "INFO-DIR-SECTION ");
242 s
= utoa_dup(section
);
243 rdaddsc(&intro_text
, s
);
245 rdaddsc(&intro_text
, "\nSTART-INFO-DIR-ENTRY\n* ");
246 s
= utoa_dup(shortname
);
247 rdaddsc(&intro_text
, s
);
249 rdaddsc(&intro_text
, ": (");
250 s
= dupstr(conf
.filename
);
251 if (strlen(s
) > 5 && !strcmp(s
+strlen(s
)-5, ".info"))
252 s
[strlen(s
)-5] = '\0';
253 rdaddsc(&intro_text
, s
);
255 rdaddsc(&intro_text
, ")");
257 keyword
*kwl
= kw_lookup(keywords
, kw
);
258 if (kwl
&& kwl
->para
->private_data
) {
259 node
*n
= (node
*)kwl
->para
->private_data
;
260 rdaddsc(&intro_text
, n
->name
);
263 rdaddsc(&intro_text
, ". ");
264 s
= utoa_dup(longname
);
265 rdaddsc(&intro_text
, s
);
267 rdaddsc(&intro_text
, "\nEND-INFO-DIR-ENTRY\n\n");
270 for (p
= sourceform
; p
; p
= p
->next
)
271 if (p
->type
== para_Copyright
)
272 info_para(&intro_text
, NULL
, NULL
, p
->words
, keywords
,
275 for (p
= sourceform
; p
; p
= p
->next
)
276 if (p
->type
== para_VersionID
)
277 info_versionid(&intro_text
, p
->words
);
279 if (intro_text
.text
[intro_text
.pos
-1] != '\n')
280 rdaddc(&intro_text
, '\n');
283 for (p
= sourceform
; p
; p
= p
->next
)
284 if (p
->type
== para_Title
)
285 info_heading(&topnode
->text
, NULL
, p
->words
, width
);
287 nestindent
= listindentbefore
+ listindentafter
;
292 /* Do the main document */
293 for (p
= sourceform
; p
; p
= p
->next
) switch (p
->type
) {
300 assert(nesting
>= 0);
304 nesting
+= nestindent
;
307 nesting
-= nestindent
;
308 assert(nesting
>= 0);
312 * Things we ignore because we've already processed them or
313 * aren't going to touch them in this pass.
317 case para_Biblio
: /* only touch BiblioCited */
328 case para_UnnumberedChapter
:
331 currnode
= p
->private_data
;
333 assert(currnode
->up
);
335 if (!currnode
->up
->started_menu
) {
336 rdaddsc(&currnode
->up
->text
, "* Menu:\n\n");
337 currnode
->up
->started_menu
= TRUE
;
339 info_menu_item(&currnode
->up
->text
, currnode
, p
);
341 has_index
|= info_check_index(p
->words
, currnode
, idx
);
342 info_heading(&currnode
->text
, p
->kwtext
, p
->words
, width
);
347 info_rule(&currnode
->text
, nesting
, width
- nesting
);
352 case para_DescribedThing
:
353 case para_Description
:
354 case para_BiblioCited
:
356 case para_NumberedList
:
357 has_index
|= info_check_index(p
->words
, currnode
, idx
);
358 if (p
->type
== para_Bullet
) {
361 bullet
.type
= word_Normal
;
362 bullet
.text
= L
"-"; /* FIXME: configurability */
365 indentb
= listindentbefore
;
366 indenta
= listindentafter
;
367 } else if (p
->type
== para_NumberedList
) {
369 prefixextra
= "."; /* FIXME: configurability */
370 indentb
= listindentbefore
;
371 indenta
= listindentafter
;
372 } else if (p
->type
== para_Description
) {
375 indentb
= listindentbefore
;
376 indenta
= listindentafter
;
380 indentb
= indenta
= 0;
382 if (p
->type
== para_BiblioCited
) {
383 body
= dup_word_list(p
->kwtext
);
384 for (wp
= body
; wp
->next
; wp
= wp
->next
);
385 wp
->next
= &spaceword
;
386 spaceword
.next
= p
->words
;
387 spaceword
.alt
= NULL
;
388 spaceword
.type
= word_WhiteSpace
;
389 spaceword
.text
= NULL
;
394 info_para(&currnode
->text
, prefix
, prefixextra
, body
, keywords
,
395 nesting
+ indentb
, indenta
,
396 width
- nesting
- indentb
- indenta
);
399 free_word_list(body
);
404 info_codepara(&currnode
->text
, p
->words
,
405 nesting
+ indent_code
,
406 width
- nesting
- 2 * indent_code
);
411 * Create an index node if required.
418 newnode
= info_node_new("Index");
419 newnode
->up
= topnode
;
421 currnode
->next
= newnode
;
422 newnode
->prev
= currnode
;
423 currnode
->listnext
= newnode
;
425 rdaddsc(&newnode
->text
, "Index\n-----\n\n* Menu:\n\n");
427 info_menu_item(&topnode
->text
, newnode
, NULL
);
429 for (i
= 0; (entry
= index234(idx
->entries
, i
)) != NULL
; i
++) {
430 info_idx
*ii
= (info_idx
*)entry
->backend_data
;
432 for (j
= 0; j
< ii
->nnodes
; j
++) {
433 int pos0
= newnode
->text
.pos
;
434 rdaddsc(&newnode
->text
, "* ");
436 * When we have multiple references for a single
437 * index term, we only display the actual term on
438 * the first line, to make it clear that the terms
439 * really are the same.
442 rdaddsc(&newnode
->text
, ii
->text
);
443 for (k
= newnode
->text
.pos
- pos0
; k
< index_width
; k
++)
444 rdaddc(&newnode
->text
, ' ');
445 rdaddsc(&newnode
->text
, ": ");
446 rdaddsc(&newnode
->text
, ii
->nodes
[j
]->name
);
447 rdaddsc(&newnode
->text
, ".\n");
453 * Finalise the text of each node, by adding the ^_ delimiter
454 * and the node line at the top.
456 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
) {
457 char *origtext
= currnode
->text
.text
;
458 currnode
->text
.text
= NULL
;
459 currnode
->text
.pos
= currnode
->text
.size
= 0;
460 rdaddsc(&currnode
->text
, "\037\nFile: ");
461 rdaddsc(&currnode
->text
, conf
.filename
);
462 rdaddsc(&currnode
->text
, ", Node: ");
463 rdaddsc(&currnode
->text
, currnode
->name
);
464 if (currnode
->prev
) {
465 rdaddsc(&currnode
->text
, ", Prev: ");
466 rdaddsc(&currnode
->text
, currnode
->prev
->name
);
468 rdaddsc(&currnode
->text
, ", Up: ");
469 rdaddsc(&currnode
->text
, (currnode
->up ?
470 currnode
->up
->name
: "(dir)"));
471 if (currnode
->next
) {
472 rdaddsc(&currnode
->text
, ", Next: ");
473 rdaddsc(&currnode
->text
, currnode
->next
->name
);
475 rdaddsc(&currnode
->text
, "\n\n");
476 rdaddsc(&currnode
->text
, origtext
);
478 * Just make _absolutely_ sure we end with a newline.
480 if (currnode
->text
.text
[currnode
->text
.pos
-1] != '\n')
481 rdaddc(&currnode
->text
, '\n');
487 * Compute the offsets for the tag table.
489 filepos
= intro_text
.pos
;
490 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
) {
491 currnode
->pos
= filepos
;
492 filepos
+= currnode
->text
.pos
;
496 * Split into sub-files.
498 if (conf
.maxfilesize
> 0) {
499 int currfilesize
= intro_text
.pos
, currfilenum
= 1;
500 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
) {
501 if (currfilesize
> intro_text
.pos
&&
502 currfilesize
+ currnode
->text
.pos
> conf
.maxfilesize
) {
504 currfilesize
= intro_text
.pos
;
506 currnode
->filenum
= currfilenum
;
507 currfilesize
+= currnode
->text
.pos
;
512 * Write the primary output file.
514 fp
= fopen(conf
.filename
, "w");
516 error(err_cantopenw
, conf
.filename
);
519 fputs(intro_text
.text
, fp
);
520 if (conf
.maxfilesize
== 0) {
521 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
)
522 fputs(currnode
->text
.text
, fp
);
525 fprintf(fp
, "\037\nIndirect:\n");
526 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
)
527 if (filenum
!= currnode
->filenum
) {
528 filenum
= currnode
->filenum
;
529 fprintf(fp
, "%s-%d: %d\n", conf
.filename
, filenum
,
533 fprintf(fp
, "\037\nTag Table:\n");
534 if (conf
.maxfilesize
> 0)
535 fprintf(fp
, "(Indirect)\n");
536 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
)
537 fprintf(fp
, "Node: %s\177%d\n", currnode
->name
, currnode
->pos
);
538 fprintf(fp
, "\037\nEnd Tag Table\n");
542 * Write the subfiles.
544 if (conf
.maxfilesize
> 0) {
548 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
) {
549 if (filenum
!= currnode
->filenum
) {
552 filenum
= currnode
->filenum
;
556 fname
= mknewa(char, strlen(conf
.filename
) + 40);
557 sprintf(fname
, "%s-%d", conf
.filename
, filenum
);
558 fp
= fopen(fname
, "w");
560 error(err_cantopenw
, fname
);
564 fputs(intro_text
.text
, fp
);
566 fputs(currnode
->text
.text
, fp
);
574 static int info_check_index(word
*w
, node
*n
, indexdata
*idx
)
578 for (; w
; w
= w
->next
) {
579 if (w
->type
== word_IndexRef
) {
583 tag
= index_findtag(idx
, w
->text
);
587 for (i
= 0; i
< tag
->nrefs
; i
++) {
588 indexentry
*entry
= tag
->refs
[i
];
589 info_idx
*ii
= (info_idx
*)entry
->backend_data
;
591 if (ii
->nnodes
> 0 && ii
->nodes
[ii
->nnodes
-1] == n
) {
593 * If the same index term is indexed twice
594 * within the same section, we only want to
595 * mention it once in the index. So do nothing
601 if (ii
->nnodes
>= ii
->nodesize
) {
603 ii
->nodes
= resize(ii
->nodes
, ii
->nodesize
);
606 ii
->nodes
[ii
->nnodes
++] = n
;
617 * Convert a wide string into a string of chars. If `result' is
618 * non-NULL, mallocs the resulting string and stores a pointer to
619 * it in `*result'. If `result' is NULL, merely checks whether all
620 * characters in the string are feasible for the output character
623 * Return is nonzero if all characters are OK. If not all
624 * characters are OK but `result' is non-NULL, a result _will_
625 * still be generated!
627 static int info_convert(wchar_t *s
, char **result
) {
629 * FIXME. Currently this is ISO8859-1 only.
631 int doing
= (result
!= 0);
634 int plen
= 0, psize
= 0;
640 if ((c
>= 32 && c
<= 126) ||
641 (c
>= 160 && c
<= 255)) {
645 /* Char is not OK. */
647 outc
= 0xBF; /* approximate the good old DEC `uh?' */
652 p
= resize(p
, psize
);
658 p
= resize(p
, plen
+1);
665 static word
*info_transform_wordlist(word
*words
, keywordlist
*keywords
)
667 word
*ret
= dup_word_list(words
);
671 for (w
= ret
; w
; w
= w
->next
) {
672 w
->private_data
= NULL
;
673 if (w
->type
== word_UpperXref
|| w
->type
== word_LowerXref
) {
674 kwl
= kw_lookup(keywords
, w
->text
);
676 if (kwl
->para
->type
== para_NumberedList
||
677 kwl
->para
->type
== para_BiblioCited
) {
679 * In Info, we do nothing special for xrefs to
680 * numbered list items or bibliography entries.
685 * An xref to a different section has its text
686 * completely replaced.
692 if (w2
->type
== word_XrefEnd
) {
702 * Now w is the UpperXref / LowerXref we
703 * started with, and w4 is the next word after
704 * the corresponding XrefEnd (if any). The
705 * simplest thing is just to stick a pointer to
706 * the target node structure in the private
707 * data field of the xref word, and let
708 * info_rdaddwc and friends read the node name
712 w
->private_data
= kwl
->para
->private_data
;
713 assert(w
->private_data
);
722 static void info_rdaddwc(rdstringc
*rs
, word
*words
, word
*end
, int xrefs
) {
725 for (; words
&& words
!= end
; words
= words
->next
) switch (words
->type
) {
736 case word_WhiteSpace
:
739 case word_WkCodeSpace
:
743 case word_WkCodeQuote
:
744 assert(words
->type
!= word_CodeQuote
&&
745 words
->type
!= word_WkCodeQuote
);
746 if (towordstyle(words
->type
) == word_Emph
&&
747 (attraux(words
->aux
) == attr_First
||
748 attraux(words
->aux
) == attr_Only
))
749 rdaddc(rs
, '_'); /* FIXME: configurability */
750 else if (towordstyle(words
->type
) == word_Code
&&
751 (attraux(words
->aux
) == attr_First
||
752 attraux(words
->aux
) == attr_Only
))
753 rdaddc(rs
, '`'); /* FIXME: configurability */
754 if (removeattr(words
->type
) == word_Normal
) {
755 if (info_convert(words
->text
, &c
))
758 info_rdaddwc(rs
, words
->alt
, NULL
, FALSE
);
760 } else if (removeattr(words
->type
) == word_WhiteSpace
) {
762 } else if (removeattr(words
->type
) == word_Quote
) {
763 rdaddc(rs
, quoteaux(words
->aux
) == quote_Open ?
'`' : '\'');
764 /* FIXME: configurability */
766 if (towordstyle(words
->type
) == word_Emph
&&
767 (attraux(words
->aux
) == attr_Last
||
768 attraux(words
->aux
) == attr_Only
))
769 rdaddc(rs
, '_'); /* FIXME: configurability */
770 else if (towordstyle(words
->type
) == word_Code
&&
771 (attraux(words
->aux
) == attr_Last
||
772 attraux(words
->aux
) == attr_Only
))
773 rdaddc(rs
, '\''); /* FIXME: configurability */
778 if (xrefs
&& words
->private_data
) {
779 rdaddsc(rs
, "*Note ");
780 rdaddsc(rs
, ((node
*)words
->private_data
)->name
);
787 static int info_width_internal(word
*words
, int xrefs
);
789 static int info_width_internal_list(word
*words
, int xrefs
) {
792 w
+= info_width_internal(words
, xrefs
);
798 static int info_width_internal(word
*words
, int xrefs
) {
799 switch (words
->type
) {
810 return (((words
->type
== word_Emph
||
811 words
->type
== word_Code
)
812 ?
(attraux(words
->aux
) == attr_Only ?
2 :
813 attraux(words
->aux
) == attr_Always ?
0 : 1)
815 (info_convert(words
->text
, NULL
) ?
816 ustrlen(words
->text
) :
817 info_width_internal_list(words
->alt
, xrefs
)));
819 case word_WhiteSpace
:
822 case word_WkCodeSpace
:
826 case word_WkCodeQuote
:
827 assert(words
->type
!= word_CodeQuote
&&
828 words
->type
!= word_WkCodeQuote
);
829 return (((towordstyle(words
->type
) == word_Emph
||
830 towordstyle(words
->type
) == word_Code
)
831 ?
(attraux(words
->aux
) == attr_Only ?
2 :
832 attraux(words
->aux
) == attr_Always ?
0 : 1)
837 if (xrefs
&& words
->private_data
) {
838 /* "*Note " plus "::" comes to 8 characters */
839 return 8 + strlen(((node
*)words
->private_data
)->name
);
843 return 0; /* should never happen */
846 static int info_width_noxrefs(word
*words
)
848 return info_width_internal(words
, FALSE
);
850 static int info_width_xrefs(word
*words
)
852 return info_width_internal(words
, TRUE
);
855 static void info_heading(rdstringc
*text
, word
*tprefix
,
856 word
*words
, int width
) {
857 rdstringc t
= { 0, 0, NULL
};
859 int firstlinewidth
, wrapwidth
;
861 wrappedline
*wrapping
, *p
;
864 info_rdaddwc(&t
, tprefix
, NULL
, FALSE
);
865 rdaddsc(&t
, ": "); /* FIXME: configurability */
867 margin
= length
= (t
.text ?
strlen(t
.text
) : 0);
870 firstlinewidth
= width
- length
;
873 wrapping
= wrap_para(words
, firstlinewidth
, wrapwidth
, info_width_noxrefs
);
874 for (p
= wrapping
; p
; p
= p
->next
) {
875 info_rdaddwc(&t
, p
->begin
, p
->end
, FALSE
);
876 length
= (t
.text ?
strlen(t
.text
) : 0);
877 for (i
= 0; i
< margin
; i
++)
879 rdaddsc(text
, t
.text
);
881 for (i
= 0; i
< margin
; i
++)
896 static void info_rule(rdstringc
*text
, int indent
, int width
) {
897 while (indent
--) rdaddc(text
, ' ');
898 while (width
--) rdaddc(text
, '-');
903 static void info_para(rdstringc
*text
, word
*prefix
, char *prefixextra
,
904 word
*input
, keywordlist
*keywords
,
905 int indent
, int extraindent
, int width
) {
906 wrappedline
*wrapping
, *p
;
908 rdstringc pfx
= { 0, 0, NULL
};
911 int firstlinewidth
= width
;
913 words
= info_transform_wordlist(input
, keywords
);
916 info_rdaddwc(&pfx
, prefix
, NULL
, FALSE
);
918 rdaddsc(&pfx
, prefixextra
);
919 for (i
= 0; i
< indent
; i
++)
921 rdaddsc(text
, pfx
.text
);
922 /* If the prefix is too long, shorten the first line to fit. */
923 e
= extraindent
- strlen(pfx
.text
);
925 firstlinewidth
+= e
; /* this decreases it, since e < 0 */
926 if (firstlinewidth
< 0) {
927 e
= indent
+ extraindent
;
928 firstlinewidth
= width
;
935 e
= indent
+ extraindent
;
937 wrapping
= wrap_para(words
, firstlinewidth
, width
, info_width_xrefs
);
938 for (p
= wrapping
; p
; p
= p
->next
) {
939 for (i
= 0; i
< e
; i
++)
941 info_rdaddwc(text
, p
->begin
, p
->end
, TRUE
);
943 e
= indent
+ extraindent
;
948 free_word_list(words
);
951 static void info_codepara(rdstringc
*text
, word
*words
,
952 int indent
, int width
) {
955 for (; words
; words
= words
->next
) if (words
->type
== word_WeakCode
) {
957 info_convert(words
->text
, &c
);
958 if (strlen(c
) > (size_t)width
) {
961 for (i
= 0; i
< indent
; i
++)
971 static void info_versionid(rdstringc
*text
, word
*words
) {
972 rdaddc(text
, '['); /* FIXME: configurability */
973 info_rdaddwc(text
, words
, NULL
, FALSE
);
974 rdaddsc(text
, "]\n");
977 static node
*info_node_new(char *name
)
983 n
->text
.pos
= n
->text
.size
= 0;
984 n
->up
= n
->next
= n
->prev
= n
->lastchild
= n
->listnext
= NULL
;
985 n
->name
= dupstr(name
);
986 n
->started_menu
= FALSE
;
991 static char *info_node_name(paragraph
*par
)
993 rdstringc rsc
= { 0, 0, NULL
};
995 info_rdaddwc(&rsc
, par
->kwtext ? par
->kwtext
: par
->words
, NULL
, FALSE
);
998 * We cannot have commas or colons in a node name. Remove any
999 * that we find, with a warning.
1003 if (*p
== ':' || *p
== ',') {
1004 error(err_infonodechar
, &par
->fpos
, *p
);
1015 static void info_menu_item(rdstringc
*text
, node
*n
, paragraph
*p
)
1018 * FIXME: Depending on how we're doing node names in this info
1019 * file, we might want to do
1021 * * Node name:: Chapter title
1025 * * Chapter number: Node name.
1029 rdaddsc(text
, "* ");
1030 rdaddsc(text
, n
->name
);
1031 rdaddsc(text
, "::");
1034 info_rdaddwc(text
, p
->words
, NULL
, FALSE
);