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!
31 * - alignment in the index is broken when a non-representable
32 * character appears with no alternative. More generally, I
33 * fear, this is the fault of the info_rdadd* functions failing
34 * to return correct width figures in this circumstance (so it
35 * will affect list paragraph prefixes and paragraph wrapping as
56 #define EMPTY_INFO_DATA { { 0, 0, NULL }, 0, CHARSET_INIT_STATE, FALSE }
57 static const info_data empty_info_data
= EMPTY_INFO_DATA
;
59 typedef struct node_tag node
;
62 node
*up
, *prev
, *next
, *lastchild
;
63 int pos
, started_menu
, filenum
;
75 static int info_rdadd(info_data
*, wchar_t);
76 static int info_rdadds(info_data
*, wchar_t const *);
77 static int info_rdaddc(info_data
*, char);
78 static int info_rdaddsc(info_data
*, char const *);
80 static void info_heading(info_data
*, word
*, word
*, int);
81 static void info_rule(info_data
*, int, int);
82 static void info_para(info_data
*, word
*, wchar_t *, word
*, keywordlist
*,
84 static void info_codepara(info_data
*, word
*, int, int);
85 static void info_versionid(info_data
*, word
*);
86 static void info_menu_item(info_data
*, node
*, paragraph
*);
87 static word
*info_transform_wordlist(word
*, keywordlist
*);
88 static int info_check_index(word
*, node
*, indexdata
*);
90 static int info_rdaddwc(info_data
*, word
*, word
*, int);
92 static node
*info_node_new(char *name
, int charset
);
93 static char *info_node_name(paragraph
*p
, int charset
);
95 static infoconfig
info_configure(paragraph
*source
) {
101 ret
.filename
= dupstr("output.info");
102 ret
.maxfilesize
= 64 << 10;
103 ret
.charset
= CS_ASCII
;
105 for (; source
; source
= source
->next
) {
106 if (source
->type
== para_Config
) {
107 if (!ustricmp(source
->keyword
, L
"info-filename")) {
109 ret
.filename
= dupstr(adv(source
->origkeyword
));
110 } else if (!ustricmp(source
->keyword
, L
"info-charset")) {
111 char *csname
= utoa_dup(uadv(source
->keyword
), CS_ASCII
);
112 ret
.charset
= charset_from_localenc(csname
);
114 } else if (!ustricmp(source
->keyword
, L
"info-max-file-size")) {
115 ret
.maxfilesize
= utoi(uadv(source
->keyword
));
123 paragraph
*info_config_filename(char *filename
)
125 return cmdline_cfg_simple("info-filename", filename
, NULL
);
128 void info_backend(paragraph
*sourceform
, keywordlist
*keywords
,
129 indexdata
*idx
, void *unused
) {
132 word
*prefix
, *body
, *wp
;
134 wchar_t *prefixextra
;
135 int nesting
, nestindent
;
136 int indentb
, indenta
;
139 info_data intro_text
= EMPTY_INFO_DATA
;
140 node
*topnode
, *currnode
;
145 * FIXME: possibly configurability?
147 int width
= 70, listindentbefore
= 1, listindentafter
= 3;
148 int indent_code
= 2, index_width
= 40;
152 conf
= info_configure(sourceform
);
155 * Go through and create a node for each section.
157 topnode
= info_node_new("Top", conf
.charset
);
159 for (p
= sourceform
; p
; p
= p
->next
) switch (p
->type
) {
165 case para_UnnumberedChapter
:
169 node
*newnode
, *upnode
;
172 nodename
= info_node_name(p
, conf
.charset
);
173 newnode
= info_node_new(nodename
, conf
.charset
);
176 p
->private_data
= newnode
;
179 upnode
= (node
*)p
->parent
->private_data
;
183 newnode
->up
= upnode
;
185 currnode
->next
= newnode
;
186 newnode
->prev
= currnode
;
188 currnode
->listnext
= newnode
;
195 * Set up the display form of each index entry.
201 for (i
= 0; (entry
= index234(idx
->entries
, i
)) != NULL
; i
++) {
202 info_idx
*ii
= mknew(info_idx
);
203 info_data id
= EMPTY_INFO_DATA
;
205 id
.charset
= conf
.charset
;
207 ii
->nnodes
= ii
->nodesize
= 0;
210 ii
->length
= info_rdaddwc(&id
, entry
->text
, NULL
, FALSE
);
212 ii
->text
= id
.output
.text
;
214 entry
->backend_data
= ii
;
219 * An Info file begins with a piece of introductory text which
220 * is apparently never shown anywhere. This seems to me to be a
221 * good place to put the copyright notice and the version IDs.
222 * Also, Info directory entries are expected to go here.
224 intro_text
.charset
= conf
.charset
;
226 info_rdaddsc(&intro_text
,
227 "This Info file generated by Halibut, ");
228 info_rdaddsc(&intro_text
, version
);
229 info_rdaddsc(&intro_text
, "\n\n");
231 for (p
= sourceform
; p
; p
= p
->next
)
232 if (p
->type
== para_Config
&&
233 !ustricmp(p
->keyword
, L
"info-dir-entry")) {
234 wchar_t *section
, *shortname
, *longname
, *kw
;
237 section
= uadv(p
->keyword
);
238 shortname
= *section ?
uadv(section
) : NULL
;
239 longname
= *shortname ?
uadv(shortname
) : NULL
;
240 kw
= *longname ?
uadv(longname
) : NULL
;
243 error(err_infodirentry
, &p
->fpos
);
247 info_rdaddsc(&intro_text
, "INFO-DIR-SECTION ");
248 info_rdadds(&intro_text
, section
);
249 info_rdaddsc(&intro_text
, "\nSTART-INFO-DIR-ENTRY\n* ");
250 info_rdadds(&intro_text
, shortname
);
251 info_rdaddsc(&intro_text
, ": (");
252 s
= dupstr(conf
.filename
);
253 if (strlen(s
) > 5 && !strcmp(s
+strlen(s
)-5, ".info"))
254 s
[strlen(s
)-5] = '\0';
255 info_rdaddsc(&intro_text
, s
);
257 info_rdaddsc(&intro_text
, ")");
259 keyword
*kwl
= kw_lookup(keywords
, kw
);
260 if (kwl
&& kwl
->para
->private_data
) {
261 node
*n
= (node
*)kwl
->para
->private_data
;
262 info_rdaddsc(&intro_text
, n
->name
);
265 info_rdaddsc(&intro_text
, ". ");
266 info_rdadds(&intro_text
, longname
);
267 info_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
.output
.text
[intro_text
.output
.pos
-1] != '\n')
280 info_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 info_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
= L
"."; /* 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", conf
.charset
);
419 newnode
->up
= topnode
;
421 currnode
->next
= newnode
;
422 newnode
->prev
= currnode
;
423 currnode
->listnext
= newnode
;
425 info_rdaddsc(&newnode
->text
, "Index\n-----\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
++) {
434 * When we have multiple references for a single
435 * index term, we only display the actual term on
436 * the first line, to make it clear that the terms
437 * really are the same.
440 info_rdaddsc(&newnode
->text
, ii
->text
);
441 for (k
= (j ?
0 : ii
->length
); k
< index_width
; k
++)
442 info_rdaddc(&newnode
->text
, ' ');
443 info_rdaddsc(&newnode
->text
, " *Note ");
444 info_rdaddsc(&newnode
->text
, ii
->nodes
[j
]->name
);
445 info_rdaddsc(&newnode
->text
, "::\n");
451 * Finalise the text of each node, by adding the ^_ delimiter
452 * and the node line at the top.
454 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
) {
455 char *origtext
= currnode
->text
.output
.text
;
456 currnode
->text
= empty_info_data
;
457 currnode
->text
.charset
= conf
.charset
;
458 info_rdaddsc(&currnode
->text
, "\037\nFile: ");
459 info_rdaddsc(&currnode
->text
, conf
.filename
);
460 info_rdaddsc(&currnode
->text
, ", Node: ");
461 info_rdaddsc(&currnode
->text
, currnode
->name
);
462 if (currnode
->prev
) {
463 info_rdaddsc(&currnode
->text
, ", Prev: ");
464 info_rdaddsc(&currnode
->text
, currnode
->prev
->name
);
466 info_rdaddsc(&currnode
->text
, ", Up: ");
467 info_rdaddsc(&currnode
->text
, (currnode
->up ?
468 currnode
->up
->name
: "(dir)"));
469 if (currnode
->next
) {
470 info_rdaddsc(&currnode
->text
, ", Next: ");
471 info_rdaddsc(&currnode
->text
, currnode
->next
->name
);
473 info_rdaddsc(&currnode
->text
, "\n\n");
474 info_rdaddsc(&currnode
->text
, origtext
);
476 * Just make _absolutely_ sure we end with a newline.
478 if (currnode
->text
.output
.text
[currnode
->text
.output
.pos
-1] != '\n')
479 info_rdaddc(&currnode
->text
, '\n');
485 * Compute the offsets for the tag table.
487 filepos
= intro_text
.output
.pos
;
488 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
) {
489 currnode
->pos
= filepos
;
490 filepos
+= currnode
->text
.output
.pos
;
494 * Split into sub-files.
496 if (conf
.maxfilesize
> 0) {
497 int currfilesize
= intro_text
.output
.pos
, currfilenum
= 1;
498 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
) {
499 if (currfilesize
> intro_text
.output
.pos
&&
500 currfilesize
+ currnode
->text
.output
.pos
> conf
.maxfilesize
) {
502 currfilesize
= intro_text
.output
.pos
;
504 currnode
->filenum
= currfilenum
;
505 currfilesize
+= currnode
->text
.output
.pos
;
510 * Write the primary output file.
512 fp
= fopen(conf
.filename
, "w");
514 error(err_cantopenw
, conf
.filename
);
517 fputs(intro_text
.output
.text
, fp
);
518 if (conf
.maxfilesize
== 0) {
519 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
)
520 fputs(currnode
->text
.output
.text
, fp
);
523 fprintf(fp
, "\037\nIndirect:\n");
524 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
)
525 if (filenum
!= currnode
->filenum
) {
526 filenum
= currnode
->filenum
;
527 fprintf(fp
, "%s-%d: %d\n", conf
.filename
, filenum
,
531 fprintf(fp
, "\037\nTag Table:\n");
532 if (conf
.maxfilesize
> 0)
533 fprintf(fp
, "(Indirect)\n");
534 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
)
535 fprintf(fp
, "Node: %s\177%d\n", currnode
->name
, currnode
->pos
);
536 fprintf(fp
, "\037\nEnd Tag Table\n");
540 * Write the subfiles.
542 if (conf
.maxfilesize
> 0) {
546 for (currnode
= topnode
; currnode
; currnode
= currnode
->listnext
) {
547 if (filenum
!= currnode
->filenum
) {
550 filenum
= currnode
->filenum
;
554 fname
= mknewa(char, strlen(conf
.filename
) + 40);
555 sprintf(fname
, "%s-%d", conf
.filename
, filenum
);
556 fp
= fopen(fname
, "w");
558 error(err_cantopenw
, fname
);
562 fputs(intro_text
.output
.text
, fp
);
564 fputs(currnode
->text
.output
.text
, fp
);
572 static int info_check_index(word
*w
, node
*n
, indexdata
*idx
)
576 for (; w
; w
= w
->next
) {
577 if (w
->type
== word_IndexRef
) {
581 tag
= index_findtag(idx
, w
->text
);
585 for (i
= 0; i
< tag
->nrefs
; i
++) {
586 indexentry
*entry
= tag
->refs
[i
];
587 info_idx
*ii
= (info_idx
*)entry
->backend_data
;
589 if (ii
->nnodes
> 0 && ii
->nodes
[ii
->nnodes
-1] == n
) {
591 * If the same index term is indexed twice
592 * within the same section, we only want to
593 * mention it once in the index. So do nothing
599 if (ii
->nnodes
>= ii
->nodesize
) {
601 ii
->nodes
= resize(ii
->nodes
, ii
->nodesize
);
604 ii
->nodes
[ii
->nnodes
++] = n
;
614 static word
*info_transform_wordlist(word
*words
, keywordlist
*keywords
)
616 word
*ret
= dup_word_list(words
);
620 for (w
= ret
; w
; w
= w
->next
) {
621 w
->private_data
= NULL
;
622 if (w
->type
== word_UpperXref
|| w
->type
== word_LowerXref
) {
623 kwl
= kw_lookup(keywords
, w
->text
);
625 if (kwl
->para
->type
== para_NumberedList
||
626 kwl
->para
->type
== para_BiblioCited
) {
628 * In Info, we do nothing special for xrefs to
629 * numbered list items or bibliography entries.
634 * An xref to a different section has its text
635 * completely replaced.
641 if (w2
->type
== word_XrefEnd
) {
651 * Now w is the UpperXref / LowerXref we
652 * started with, and w4 is the next word after
653 * the corresponding XrefEnd (if any). The
654 * simplest thing is just to stick a pointer to
655 * the target node structure in the private
656 * data field of the xref word, and let
657 * info_rdaddwc and friends read the node name
661 w
->private_data
= kwl
->para
->private_data
;
662 assert(w
->private_data
);
671 static int info_rdaddwc(info_data
*id
, word
*words
, word
*end
, int xrefs
) {
674 for (; words
&& words
!= end
; words
= words
->next
) switch (words
->type
) {
685 case word_WhiteSpace
:
688 case word_WkCodeSpace
:
692 case word_WkCodeQuote
:
693 assert(words
->type
!= word_CodeQuote
&&
694 words
->type
!= word_WkCodeQuote
);
695 if (towordstyle(words
->type
) == word_Emph
&&
696 (attraux(words
->aux
) == attr_First
||
697 attraux(words
->aux
) == attr_Only
))
698 ret
+= info_rdadd(id
, L
'_'); /* FIXME: configurability */
699 else if (towordstyle(words
->type
) == word_Code
&&
700 (attraux(words
->aux
) == attr_First
||
701 attraux(words
->aux
) == attr_Only
))
702 ret
+= info_rdadd(id
, L
'`'); /* FIXME: configurability */
703 if (removeattr(words
->type
) == word_Normal
) {
704 if (cvt_ok(id
->charset
, words
->text
) || !words
->alt
)
705 ret
+= info_rdadds(id
, words
->text
);
707 ret
+= info_rdaddwc(id
, words
->alt
, NULL
, FALSE
);
708 } else if (removeattr(words
->type
) == word_WhiteSpace
) {
709 ret
+= info_rdadd(id
, L
' ');
710 } else if (removeattr(words
->type
) == word_Quote
) {
711 ret
+= info_rdadd(id
, quoteaux(words
->aux
) == quote_Open ? L
'`' : L
'\'');
712 /* FIXME: configurability */
714 if (towordstyle(words
->type
) == word_Emph
&&
715 (attraux(words
->aux
) == attr_Last
||
716 attraux(words
->aux
) == attr_Only
))
717 ret
+= info_rdadd(id
, L
'_'); /* FIXME: configurability */
718 else if (towordstyle(words
->type
) == word_Code
&&
719 (attraux(words
->aux
) == attr_Last
||
720 attraux(words
->aux
) == attr_Only
))
721 ret
+= info_rdadd(id
, L
'\''); /* FIXME: configurability */
726 if (xrefs
&& words
->private_data
) {
728 * This bit is structural and so must be done in char
729 * rather than wchar_t.
731 ret
+= info_rdaddsc(id
, "*Note ");
732 ret
+= info_rdaddsc(id
, ((node
*)words
->private_data
)->name
);
733 ret
+= info_rdaddsc(id
, "::");
741 static int info_width_internal(word
*words
, int xrefs
, int charset
);
743 static int info_width_internal_list(word
*words
, int xrefs
, int charset
) {
746 w
+= info_width_internal(words
, xrefs
, charset
);
752 static int info_width_internal(word
*words
, int xrefs
, int charset
) {
753 switch (words
->type
) {
764 return (((words
->type
== word_Emph
||
765 words
->type
== word_Code
)
766 ?
(attraux(words
->aux
) == attr_Only ?
2 :
767 attraux(words
->aux
) == attr_Always ?
0 : 1)
769 (cvt_ok(charset
, words
->text
) || !words
->alt ?
770 ustrlen(words
->text
) :
771 info_width_internal_list(words
->alt
, xrefs
, charset
)));
773 case word_WhiteSpace
:
776 case word_WkCodeSpace
:
780 case word_WkCodeQuote
:
781 assert(words
->type
!= word_CodeQuote
&&
782 words
->type
!= word_WkCodeQuote
);
783 return (((towordstyle(words
->type
) == word_Emph
||
784 towordstyle(words
->type
) == word_Code
)
785 ?
(attraux(words
->aux
) == attr_Only ?
2 :
786 attraux(words
->aux
) == attr_Always ?
0 : 1)
791 if (xrefs
&& words
->private_data
) {
792 /* "*Note " plus "::" comes to 8 characters */
793 return 8 + strlen(((node
*)words
->private_data
)->name
);
797 return 0; /* should never happen */
800 static int info_width_noxrefs(void *ctx
, word
*words
)
802 return info_width_internal(words
, FALSE
, *(int *)ctx
);
804 static int info_width_xrefs(void *ctx
, word
*words
)
806 return info_width_internal(words
, TRUE
, *(int *)ctx
);
809 static void info_heading(info_data
*text
, word
*tprefix
,
810 word
*words
, int width
) {
812 int firstlinewidth
, wrapwidth
;
813 wrappedline
*wrapping
, *p
;
817 length
+= info_rdaddwc(text
, tprefix
, NULL
, FALSE
);
818 length
+= info_rdadds(text
, L
": ");/* FIXME: configurability */
822 firstlinewidth
= width
- length
;
824 wrapping
= wrap_para(words
, firstlinewidth
, wrapwidth
,
825 info_width_noxrefs
, &text
->charset
, 0);
826 for (p
= wrapping
; p
; p
= p
->next
) {
827 length
+= info_rdaddwc(text
, p
->begin
, p
->end
, FALSE
);
828 info_rdadd(text
, L
'\n');
830 info_rdadd(text
, L
'-'); /* FIXME: configurability */
831 info_rdadd(text
, L
'\n');
835 info_rdadd(text
, L
'\n');
838 static void info_rule(info_data
*text
, int indent
, int width
) {
839 while (indent
--) info_rdadd(text
, L
' ');
840 while (width
--) info_rdadd(text
, L
'-');
841 info_rdadd(text
, L
'\n');
842 info_rdadd(text
, L
'\n');
845 static void info_para(info_data
*text
, word
*prefix
, wchar_t *prefixextra
,
846 word
*input
, keywordlist
*keywords
,
847 int indent
, int extraindent
, int width
) {
848 wrappedline
*wrapping
, *p
;
852 int firstlinewidth
= width
;
854 words
= info_transform_wordlist(input
, keywords
);
857 for (i
= 0; i
< indent
; i
++)
858 info_rdadd(text
, L
' ');
859 e
= info_rdaddwc(text
, prefix
, NULL
, FALSE
);
861 e
+= info_rdadds(text
, prefixextra
);
862 /* If the prefix is too long, shorten the first line to fit. */
865 firstlinewidth
+= e
; /* this decreases it, since e < 0 */
866 if (firstlinewidth
< 0) {
867 e
= indent
+ extraindent
;
868 firstlinewidth
= width
;
869 info_rdadd(text
, L
'\n');
874 e
= indent
+ extraindent
;
876 wrapping
= wrap_para(words
, firstlinewidth
, width
, info_width_xrefs
,
878 for (p
= wrapping
; p
; p
= p
->next
) {
879 for (i
= 0; i
< e
; i
++)
880 info_rdadd(text
, L
' ');
881 info_rdaddwc(text
, p
->begin
, p
->end
, TRUE
);
882 info_rdadd(text
, L
'\n');
883 e
= indent
+ extraindent
;
886 info_rdadd(text
, L
'\n');
888 free_word_list(words
);
891 static void info_codepara(info_data
*text
, word
*words
,
892 int indent
, int width
) {
895 for (; words
; words
= words
->next
) if (words
->type
== word_WeakCode
) {
896 for (i
= 0; i
< indent
; i
++)
897 info_rdadd(text
, L
' ');
898 if (info_rdadds(text
, words
->text
) > width
) {
901 info_rdadd(text
, L
'\n');
904 info_rdadd(text
, L
'\n');
907 static void info_versionid(info_data
*text
, word
*words
) {
908 info_rdadd(text
, L
'['); /* FIXME: configurability */
909 info_rdaddwc(text
, words
, NULL
, FALSE
);
910 info_rdadds(text
, L
"]\n");
913 static node
*info_node_new(char *name
, int charset
)
918 n
->text
= empty_info_data
;
919 n
->text
.charset
= charset
;
920 n
->up
= n
->next
= n
->prev
= n
->lastchild
= n
->listnext
= NULL
;
921 n
->name
= dupstr(name
);
922 n
->started_menu
= FALSE
;
927 static char *info_node_name(paragraph
*par
, int charset
)
929 info_data id
= EMPTY_INFO_DATA
;
932 id
.charset
= charset
;
933 info_rdaddwc(&id
, par
->kwtext ? par
->kwtext
: par
->words
, NULL
, FALSE
);
934 info_rdaddsc(&id
, NULL
);
937 * We cannot have commas or colons in a node name. Remove any
938 * that we find, with a warning.
940 p
= q
= id
.output
.text
;
942 if (*p
== ':' || *p
== ',') {
943 error(err_infonodechar
, &par
->fpos
, *p
);
951 return id
.output
.text
;
954 static void info_menu_item(info_data
*text
, node
*n
, paragraph
*p
)
957 * FIXME: Depending on how we're doing node names in this info
958 * file, we might want to do
960 * * Node name:: Chapter title
964 * * Chapter number: Node name.
966 * This function mostly works in char rather than wchar_t,
967 * because a menu item is a structural component.
969 info_rdaddsc(text
, "* ");
970 info_rdaddsc(text
, n
->name
);
971 info_rdaddsc(text
, "::");
973 info_rdaddc(text
, ' ');
974 info_rdaddwc(text
, p
->words
, NULL
, FALSE
);
976 info_rdaddc(text
, '\n');
980 * These functions implement my wrapper on the rdadd* calls which
981 * allows me to switch arbitrarily between literal octet-string
982 * text and charset-translated Unicode. (Because no matter what
983 * character set I write the actual text in, I expect info readers
984 * to treat node names and file names literally and to expect
985 * keywords like `*Note' in their canonical form, so I have to take
986 * steps to ensure that those structural elements of the file
987 * aren't messed with.)
989 static int info_rdadds(info_data
*d
, wchar_t const *wcs
)
992 d
->state
= charset_init_state
;
998 int len
, origlen
, ret
;
1000 origlen
= len
= ustrlen(wcs
);
1004 ret
= charset_from_unicode(&wcs
, &len
, buf
, lenof(buf
),
1005 d
->charset
, &d
->state
, NULL
);
1007 assert(len
< prevlen
);
1011 rdaddsc(&d
->output
, buf
);
1020 static int info_rdaddsc(info_data
*d
, char const *cs
)
1026 ret
= charset_from_unicode(NULL
, 0, buf
, lenof(buf
),
1027 d
->charset
, &d
->state
, NULL
);
1030 rdaddsc(&d
->output
, buf
);
1037 rdaddsc(&d
->output
, cs
);
1043 static int info_rdadd(info_data
*d
, wchar_t wc
)
1048 return info_rdadds(d
, wcs
);
1051 static int info_rdaddc(info_data
*d
, char c
)
1056 return info_rdaddsc(d
, cs
);