Annotation of docbook2mdoc/docbook2mdoc.c, Revision 1.60
1.60 ! schwarze 1: /* $Id: docbook2mdoc.c,v 1.59 2019/03/22 16:55:44 schwarze Exp $ */
1.1 kristaps 2: /*
3: * Copyright (c) 2014 Kristaps Dzonsons <kristaps@bsd.lv>
1.50 schwarze 4: * Copyright (c) 2019 Ingo Schwarze <schwarze@openbsd.org>
1.1 kristaps 5: *
6: * Permission to use, copy, modify, and distribute this software for any
7: * purpose with or without fee is hereby granted, provided that the above
8: * copyright notice and this permission notice appear in all copies.
9: *
10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17: */
18: #include <sys/queue.h>
19:
20: #include <assert.h>
21: #include <ctype.h>
22: #include <expat.h>
23: #include <fcntl.h>
24: #include <getopt.h>
25: #include <stdio.h>
26: #include <stdlib.h>
27: #include <string.h>
1.7 kristaps 28: #include <unistd.h>
1.1 kristaps 29:
1.13 kristaps 30: #include "extern.h"
1.12 kristaps 31:
32: /*
1.1 kristaps 33: * Global parse state.
34: * Keep this as simple and small as possible.
35: */
36: struct parse {
1.12 kristaps 37: XML_Parser xml;
1.1 kristaps 38: enum nodeid node; /* current (NODE_ROOT if pre-tree) */
1.12 kristaps 39: const char *fname; /* filename */
1.1 kristaps 40: int stop; /* should we stop now? */
1.43 kristaps 41: #define PARSE_EQN 1
42: unsigned int flags; /* document-wide flags */
1.1 kristaps 43: struct pnode *root; /* root of parse tree */
44: struct pnode *cur; /* current node in tree */
1.47 schwarze 45: char *b; /* NUL-terminated buffer for pre-print */
1.8 kristaps 46: size_t bsz; /* current length of b */
47: size_t mbsz; /* max bsz allocation */
1.52 schwarze 48: int level; /* header level, starting at 1 */
1.10 kristaps 49: int newln; /* output: are we on a fresh line */
1.1 kristaps 50: };
51:
52: struct node {
1.8 kristaps 53: const char *name; /* docbook element name */
1.56 schwarze 54: enum nodeid node; /* docbook element to generate */
1.1 kristaps 55: };
56:
57: TAILQ_HEAD(pnodeq, pnode);
1.12 kristaps 58: TAILQ_HEAD(pattrq, pattr);
59:
60: struct pattr {
61: enum attrkey key;
62: enum attrval val;
63: char *rawval;
64: TAILQ_ENTRY(pattr) child;
65: };
1.1 kristaps 66:
67: struct pnode {
68: enum nodeid node; /* node type */
69: char *b; /* binary data buffer */
1.37 kristaps 70: char *real; /* store for "b" */
1.1 kristaps 71: size_t bsz; /* data buffer size */
72: struct pnode *parent; /* parent (or NULL if top) */
73: struct pnodeq childq; /* queue of children */
1.12 kristaps 74: struct pattrq attrq; /* attributes of node */
1.1 kristaps 75: TAILQ_ENTRY(pnode) child;
76: };
77:
1.12 kristaps 78: static const char *attrkeys[ATTRKEY__MAX] = {
79: "choice",
1.60 ! schwarze 80: "class",
1.41 kristaps 81: "close",
1.12 kristaps 82: "id",
1.58 schwarze 83: "linkend",
1.41 kristaps 84: "open",
1.12 kristaps 85: "rep"
86: };
87:
88: static const char *attrvals[ATTRVAL__MAX] = {
1.60 ! schwarze 89: "monospaced",
1.12 kristaps 90: "norepeat",
91: "opt",
92: "plain",
93: "repeat",
94: "req"
95: };
96:
1.56 schwarze 97: static const struct node nodes[] = {
98: { "acronym", NODE_ACRONYM },
99: { "affiliation", NODE_AFFILIATION },
100: { "anchor", NODE_ANCHOR },
101: { "application", NODE_APPLICATION },
102: { "arg", NODE_ARG },
103: { "author", NODE_AUTHOR },
104: { "authorgroup", NODE_AUTHORGROUP },
105: { "blockquote", NODE_BLOCKQUOTE },
106: { "book", NODE_BOOK },
107: { "bookinfo", NODE_BOOKINFO },
108: { "caution", NODE_CAUTION },
109: { "chapter", NODE_SECTION },
110: { "citerefentry", NODE_CITEREFENTRY },
111: { "citetitle", NODE_CITETITLE },
112: { "cmdsynopsis", NODE_CMDSYNOPSIS },
113: { "code", NODE_CODE },
114: { "colspec", NODE_COLSPEC },
115: { "command", NODE_COMMAND },
116: { "constant", NODE_CONSTANT },
117: { "copyright", NODE_COPYRIGHT },
118: { "date", NODE_DATE },
119: { "editor", NODE_EDITOR },
120: { "emphasis", NODE_EMPHASIS },
121: { "entry", NODE_ENTRY },
122: { "envar", NODE_ENVAR },
123: { "fieldsynopsis", NODE_FIELDSYNOPSIS },
124: { "filename", NODE_FILENAME },
125: { "firstname", NODE_FIRSTNAME },
126: { "firstterm", NODE_FIRSTTERM },
127: { "footnote", NODE_FOOTNOTE },
128: { "funcdef", NODE_FUNCDEF },
129: { "funcprototype", NODE_FUNCPROTOTYPE },
130: { "funcsynopsis", NODE_FUNCSYNOPSIS },
131: { "funcsynopsisinfo", NODE_FUNCSYNOPSISINFO },
132: { "function", NODE_FUNCTION },
133: { "glossterm", NODE_GLOSSTERM },
134: { "group", NODE_GROUP },
135: { "holder", NODE_HOLDER },
136: { "index", NODE_INDEX },
137: { "indexterm", NODE_INDEXTERM },
138: { "info", NODE_INFO },
139: { "informalequation", NODE_INFORMALEQUATION },
140: { "informaltable", NODE_INFORMALTABLE },
141: { "inlineequation", NODE_INLINEEQUATION },
142: { "itemizedlist", NODE_ITEMIZEDLIST },
143: { "keysym", NODE_KEYSYM },
144: { "legalnotice", NODE_LEGALNOTICE },
145: { "link", NODE_LINK },
146: { "listitem", NODE_LISTITEM },
147: { "literal", NODE_LITERAL },
148: { "literallayout", NODE_LITERALLAYOUT },
149: { "manvolnum", NODE_MANVOLNUM },
150: { "member", NODE_MEMBER },
151: { "mml:math", NODE_MML_MATH },
152: { "mml:mfenced", NODE_MML_MFENCED },
153: { "mml:mfrac", NODE_MML_MFRAC },
154: { "mml:mi", NODE_MML_MI },
155: { "mml:mn", NODE_MML_MN },
156: { "mml:mo", NODE_MML_MO },
157: { "mml:mrow", NODE_MML_MROW },
158: { "mml:msub", NODE_MML_MSUB },
159: { "mml:msup", NODE_MML_MSUP },
160: { "modifier", NODE_MODIFIER },
161: { "note", NODE_NOTE },
162: { "option", NODE_OPTION },
163: { "orderedlist", NODE_ORDEREDLIST },
164: { "orgname", NODE_ORGNAME },
165: { "othername", NODE_OTHERNAME },
166: { "para", NODE_PARA },
167: { "paramdef", NODE_PARAMDEF },
168: { "parameter", NODE_PARAMETER },
169: { "part", NODE_SECTION },
170: { "phrase", NODE_PHRASE },
171: { "preface", NODE_PREFACE },
172: { "primary", NODE_PRIMARY },
173: { "programlisting", NODE_PROGRAMLISTING },
174: { "prompt", NODE_PROMPT },
175: { "quote", NODE_QUOTE },
176: { "refclass", NODE_REFCLASS },
177: { "refdescriptor", NODE_REFDESCRIPTOR },
178: { "refentry", NODE_REFENTRY },
179: { "refentryinfo", NODE_REFENTRYINFO },
180: { "refentrytitle", NODE_REFENTRYTITLE },
181: { "refmeta", NODE_REFMETA },
182: { "refmetainfo", NODE_REFMETAINFO },
183: { "refmiscinfo", NODE_REFMISCINFO },
184: { "refname", NODE_REFNAME },
185: { "refnamediv", NODE_REFNAMEDIV },
186: { "refpurpose", NODE_REFPURPOSE },
187: { "refsect1", NODE_SECTION },
188: { "refsect2", NODE_SECTION },
189: { "refsect3", NODE_SECTION },
190: { "refsection", NODE_SECTION },
191: { "refsynopsisdiv", NODE_REFSYNOPSISDIV },
192: { "releaseinfo", NODE_RELEASEINFO },
193: { "replaceable", NODE_REPLACEABLE },
194: { "row", NODE_ROW },
195: { "sbr", NODE_SBR },
196: { "screen", NODE_SCREEN },
197: { "secondary", NODE_SECONDARY },
198: { "sect1", NODE_SECTION },
199: { "sect2", NODE_SECTION },
200: { "section", NODE_SECTION },
201: { "sgmltag", NODE_SGMLTAG },
202: { "simplelist", NODE_SIMPLELIST },
203: { "spanspec", NODE_SPANSPEC },
204: { "structname", NODE_STRUCTNAME },
205: { "subtitle", NODE_SUBTITLE },
206: { "surname", NODE_SURNAME },
207: { "synopsis", NODE_SYNOPSIS },
208: { "table", NODE_TABLE },
209: { "tbody", NODE_TBODY },
210: { "term", NODE_TERM },
211: { "tfoot", NODE_TFOOT },
212: { "tgroup", NODE_TGROUP },
213: { "thead", NODE_THEAD },
214: { "tip", NODE_TIP },
215: { "title", NODE_TITLE },
216: { "trademark", NODE_TRADEMARK },
217: { "type", NODE_TYPE },
218: { "ulink", NODE_ULINK },
219: { "userinput", NODE_USERINPUT },
220: { "variablelist", NODE_VARIABLELIST },
221: { "varlistentry", NODE_VARLISTENTRY },
222: { "varname", NODE_VARNAME },
223: { "warning", NODE_WARNING },
224: { "wordasword", NODE_WORDASWORD },
225: { "year", NODE_YEAR },
226: { NULL, NODE__MAX }
1.1 kristaps 227: };
228:
1.38 kristaps 229: static int warn = 0;
230:
1.10 kristaps 231: static void
232: pnode_print(struct parse *p, struct pnode *pn);
233:
1.8 kristaps 234: /*
235: * Process a stream of characters.
236: * We store text as nodes in and of themselves.
237: * If a text node is already open, append to it.
238: * If it's not open, open one under the current context.
239: */
1.1 kristaps 240: static void
241: xml_char(void *arg, const XML_Char *p, int sz)
242: {
243: struct parse *ps = arg;
244: struct pnode *dat;
1.4 kristaps 245: int i;
1.1 kristaps 246:
247: /* Stopped or no tree yet. */
248: if (ps->stop || NODE_ROOT == ps->node)
249: return;
250:
251: assert(NULL != ps->cur);
252:
253: /*
254: * Are we in the midst of processing text?
255: * If we're not processing text right now, then create a text
256: * node for doing so.
1.4 kristaps 257: * However, don't do so unless we have some non-whitespace to
1.10 kristaps 258: * process: strip out all leading whitespace to be sure.
1.1 kristaps 259: */
260: if (NODE_TEXT != ps->node) {
1.4 kristaps 261: for (i = 0; i < sz; i++)
1.46 schwarze 262: if ( ! isspace((unsigned char)p[i]))
1.4 kristaps 263: break;
264: if (i == sz)
265: return;
1.10 kristaps 266: p += i;
267: sz -= i;
1.1 kristaps 268: dat = calloc(1, sizeof(struct pnode));
269: if (NULL == dat) {
270: perror(NULL);
271: exit(EXIT_FAILURE);
272: }
273:
274: dat->node = ps->node = NODE_TEXT;
275: dat->parent = ps->cur;
276: TAILQ_INIT(&dat->childq);
1.12 kristaps 277: TAILQ_INIT(&dat->attrq);
1.1 kristaps 278: TAILQ_INSERT_TAIL(&ps->cur->childq, dat, child);
279: ps->cur = dat;
280: assert(NULL != ps->root);
281: }
282:
283: /* Append to current buffer. */
284: assert(sz >= 0);
1.44 schwarze 285: ps->cur->b = realloc(ps->cur->b,
1.1 kristaps 286: ps->cur->bsz + (size_t)sz);
287: if (NULL == ps->cur->b) {
288: perror(NULL);
289: exit(EXIT_FAILURE);
290: }
291: memcpy(ps->cur->b + ps->cur->bsz, p, sz);
292: ps->cur->bsz += (size_t)sz;
1.37 kristaps 293: ps->cur->real = ps->cur->b;
1.1 kristaps 294: }
295:
1.10 kristaps 296: static void
297: pnode_trim(struct pnode *pn)
298: {
299:
300: assert(NODE_TEXT == pn->node);
301: for ( ; pn->bsz > 0; pn->bsz--)
1.46 schwarze 302: if ( ! isspace((unsigned char)pn->b[pn->bsz - 1]))
1.10 kristaps 303: break;
304: }
305:
1.1 kristaps 306: /*
307: * Begin an element.
308: * First, look for the element.
309: * If we don't find it and we're not parsing, keep going.
1.8 kristaps 310: * If we don't find it and we're parsing, puke and exit.
1.1 kristaps 311: * If we find it but we're not parsing yet (i.e., it's not a refentry
312: * and thus out of context), keep going.
1.8 kristaps 313: * If we find it and we're at the root and already have a tree, puke and
314: * exit (FIXME: I don't think this is right?).
315: * If we find it but we're parsing a text node, close out the text node,
316: * return to its parent, and keep going.
1.1 kristaps 317: * Make sure that the element is in the right context.
318: * Lastly, put the node onto our parse tree and continue.
319: */
320: static void
321: xml_elem_start(void *arg, const XML_Char *name, const XML_Char **atts)
322: {
1.12 kristaps 323: struct parse *ps = arg;
1.56 schwarze 324: const struct node *node;
1.12 kristaps 325: enum attrkey key;
326: enum attrval val;
327: struct pnode *dat;
328: struct pattr *pattr;
329: const XML_Char **att;
1.1 kristaps 330:
1.36 kristaps 331: /* FIXME: find a better way to ditch other namespaces. */
332: if (ps->stop || 0 == strcmp(name, "xi:include"))
1.1 kristaps 333: return;
334:
335: /* Close out text node, if applicable... */
336: if (NODE_TEXT == ps->node) {
337: assert(NULL != ps->cur);
1.10 kristaps 338: pnode_trim(ps->cur);
1.1 kristaps 339: ps->cur = ps->cur->parent;
340: assert(NULL != ps->cur);
341: ps->node = ps->cur->node;
342: }
343:
1.56 schwarze 344: for (node = nodes; NULL != node->name; node++)
345: if (0 == strcmp(node->name, name))
1.1 kristaps 346: break;
347:
1.56 schwarze 348: if (NULL == node->name) {
349: if (NODE_ROOT == ps->node)
350: return;
1.44 schwarze 351: fprintf(stderr, "%s:%zu:%zu: unknown node \"%s\"\n",
1.12 kristaps 352: ps->fname, XML_GetCurrentLineNumber(ps->xml),
353: XML_GetCurrentColumnNumber(ps->xml), name);
1.1 kristaps 354: ps->stop = 1;
355: return;
356: } else if (NODE_ROOT == ps->node && NULL != ps->root) {
1.12 kristaps 357: fprintf(stderr, "%s:%zu:%zu: multiple refentries\n",
358: ps->fname, XML_GetCurrentLineNumber(ps->xml),
359: XML_GetCurrentColumnNumber(ps->xml));
1.1 kristaps 360: ps->stop = 1;
361: return;
1.50 schwarze 362: }
1.1 kristaps 363:
1.56 schwarze 364: if (NODE_INLINEEQUATION == node->node)
1.43 kristaps 365: ps->flags |= PARSE_EQN;
366:
1.1 kristaps 367: if (NULL == (dat = calloc(1, sizeof(struct pnode)))) {
368: perror(NULL);
369: exit(EXIT_FAILURE);
370: }
371:
1.56 schwarze 372: dat->node = ps->node = node->node;
1.1 kristaps 373: dat->parent = ps->cur;
374: TAILQ_INIT(&dat->childq);
1.12 kristaps 375: TAILQ_INIT(&dat->attrq);
1.1 kristaps 376:
377: if (NULL != ps->cur)
378: TAILQ_INSERT_TAIL(&ps->cur->childq, dat, child);
379:
380: ps->cur = dat;
381: if (NULL == ps->root)
382: ps->root = dat;
1.12 kristaps 383:
384: /*
385: * Process attributes.
386: */
387: for (att = atts; NULL != *att; att += 2) {
388: for (key = 0; key < ATTRKEY__MAX; key++)
389: if (0 == strcmp(*att, attrkeys[key]))
390: break;
391: if (ATTRKEY__MAX == key) {
1.44 schwarze 392: if (warn)
1.38 kristaps 393: fprintf(stderr, "%s:%zu:%zu: warning: "
1.44 schwarze 394: "unknown attribute \"%s\"\n",
395: ps->fname,
1.38 kristaps 396: XML_GetCurrentLineNumber(ps->xml),
1.44 schwarze 397: XML_GetCurrentColumnNumber(ps->xml),
1.38 kristaps 398: *att);
1.12 kristaps 399: continue;
400: }
401: for (val = 0; val < ATTRVAL__MAX; val++)
402: if (0 == strcmp(*(att + 1), attrvals[val]))
403: break;
404: pattr = calloc(1, sizeof(struct pattr));
405: pattr->key = key;
406: pattr->val = val;
407: if (ATTRVAL__MAX == val)
408: pattr->rawval = strdup(*(att + 1));
409: TAILQ_INSERT_TAIL(&dat->attrq, pattr, child);
410: }
411:
1.1 kristaps 412: }
413:
414: /*
415: * Roll up the parse tree.
1.8 kristaps 416: * If we're at a text node, roll that one up first.
1.1 kristaps 417: * If we hit the root, then assign ourselves as the NODE_ROOT.
418: */
419: static void
420: xml_elem_end(void *arg, const XML_Char *name)
421: {
422: struct parse *ps = arg;
423:
1.36 kristaps 424: /* FIXME: find a better way to ditch other namespaces. */
1.1 kristaps 425: if (ps->stop || NODE_ROOT == ps->node)
1.36 kristaps 426: return;
427: else if (0 == strcmp(name, "xi:include"))
1.1 kristaps 428: return;
429:
430: /* Close out text node, if applicable... */
431: if (NODE_TEXT == ps->node) {
432: assert(NULL != ps->cur);
1.10 kristaps 433: pnode_trim(ps->cur);
1.1 kristaps 434: ps->cur = ps->cur->parent;
435: assert(NULL != ps->cur);
436: ps->node = ps->cur->node;
437: }
438:
439: if (NULL == (ps->cur = ps->cur->parent))
440: ps->node = NODE_ROOT;
441: else
442: ps->node = ps->cur->node;
443: }
444:
1.8 kristaps 445: /*
446: * Recursively free a node (NULL is ok).
447: */
1.1 kristaps 448: static void
449: pnode_free(struct pnode *pn)
450: {
451: struct pnode *pp;
1.12 kristaps 452: struct pattr *ap;
1.1 kristaps 453:
454: if (NULL == pn)
455: return;
456:
457: while (NULL != (pp = TAILQ_FIRST(&pn->childq))) {
458: TAILQ_REMOVE(&pn->childq, pp, child);
459: pnode_free(pp);
460: }
461:
1.12 kristaps 462: while (NULL != (ap = TAILQ_FIRST(&pn->attrq))) {
463: TAILQ_REMOVE(&pn->attrq, ap, child);
464: free(ap->rawval);
465: free(ap);
466: }
467:
1.37 kristaps 468: free(pn->real);
1.1 kristaps 469: free(pn);
470: }
471:
1.8 kristaps 472: /*
473: * Unlink a node from its parent and pnode_free() it.
474: */
1.1 kristaps 475: static void
476: pnode_unlink(struct pnode *pn)
477: {
478:
479: if (NULL != pn->parent)
480: TAILQ_REMOVE(&pn->parent->childq, pn, child);
481: pnode_free(pn);
482: }
483:
1.8 kristaps 484: /*
485: * Unlink all children of a node and pnode_free() them.
486: */
1.1 kristaps 487: static void
1.4 kristaps 488: pnode_unlinksub(struct pnode *pn)
489: {
490:
491: while ( ! TAILQ_EMPTY(&pn->childq))
492: pnode_unlink(TAILQ_FIRST(&pn->childq));
493: }
494:
1.8 kristaps 495: /*
1.60 ! schwarze 496: * Retrieve an enumeration attribute from a node.
! 497: * Return ATTRVAL__MAX if the node has no such attribute.
! 498: */
! 499: enum attrval
! 500: pnode_getattr(struct pnode *pn, enum attrkey key)
! 501: {
! 502: struct pattr *ap;
! 503:
! 504: TAILQ_FOREACH(ap, &pn->attrq, child)
! 505: if (ap->key == key)
! 506: return ap->val;
! 507: return(ATTRVAL__MAX);
! 508: }
! 509:
! 510: /*
1.59 schwarze 511: * Retrieve an attribute string from a node.
512: * Return defval if the node has no such attribute.
513: */
514: const char *
515: pnode_getattr_raw(struct pnode *pn, enum attrkey key, const char *defval)
516: {
517: struct pattr *ap;
518:
519: TAILQ_FOREACH(ap, &pn->attrq, child)
520: if (ap->key == key)
521: return ATTRVAL__MAX == ap->val ? ap->rawval :
522: attrvals[ap->val];
523: return(defval);
524: }
525:
526: /*
1.8 kristaps 527: * Reset the lookaside buffer.
528: */
1.4 kristaps 529: static void
1.1 kristaps 530: bufclear(struct parse *p)
531: {
532:
533: p->b[p->bsz = 0] = '\0';
534: }
535:
1.8 kristaps 536: /*
537: * Append NODE_TEXT contents to the current buffer, reallocating its
538: * size if necessary.
1.47 schwarze 539: * The buffer is ALWAYS NUL-terminated.
1.8 kristaps 540: */
1.1 kristaps 541: static void
542: bufappend(struct parse *p, struct pnode *pn)
543: {
544:
545: assert(NODE_TEXT == pn->node);
546: if (p->bsz + pn->bsz + 1 > p->mbsz) {
547: p->mbsz = p->bsz + pn->bsz + 1;
548: if (NULL == (p->b = realloc(p->b, p->mbsz))) {
549: perror(NULL);
550: exit(EXIT_FAILURE);
551: }
552: }
553: memcpy(p->b + p->bsz, pn->b, pn->bsz);
554: p->bsz += pn->bsz;
555: p->b[p->bsz] = '\0';
556: }
557:
1.8 kristaps 558: /*
559: * Recursively append all NODE_TEXT nodes to the buffer.
560: * This descends into non-text nodes, but doesn't do anything beyond
561: * them.
562: * In other words, this is a recursive text grok.
563: */
1.3 kristaps 564: static void
565: bufappend_r(struct parse *p, struct pnode *pn)
566: {
567: struct pnode *pp;
568:
569: if (NODE_TEXT == pn->node)
570: bufappend(p, pn);
571: TAILQ_FOREACH(pp, &pn->childq, child)
572: bufappend_r(p, pp);
573: }
574:
1.44 schwarze 575: /*
1.25 kristaps 576: * Recursively search and return the first instance of "node".
577: */
578: static struct pnode *
579: pnode_findfirst(struct pnode *pn, enum nodeid node)
580: {
581: struct pnode *pp, *res;
582:
583: res = NULL;
584: TAILQ_FOREACH(pp, &pn->childq, child) {
585: res = pp->node == node ? pp :
586: pnode_findfirst(pp, node);
587: if (NULL != res)
588: break;
589: }
590:
591: return(res);
592: }
593:
1.12 kristaps 594: #define MACROLINE_NORM 0
595: #define MACROLINE_UPPER 1
1.32 kristaps 596: #define MACROLINE_NOWS 2
1.1 kristaps 597: /*
1.8 kristaps 598: * Recursively print text presumably on a macro line.
1.1 kristaps 599: * Convert all whitespace to regular spaces.
600: */
601: static void
1.12 kristaps 602: pnode_printmacrolinetext(struct parse *p, struct pnode *pn, int fl)
1.1 kristaps 603: {
604: char *cp;
605:
1.32 kristaps 606: if (0 == p->newln && ! (MACROLINE_NOWS & fl))
1.13 kristaps 607: putchar(' ');
608:
1.1 kristaps 609: bufclear(p);
1.3 kristaps 610: bufappend_r(p, pn);
1.1 kristaps 611:
612: /* Convert all space to spaces. */
613: for (cp = p->b; '\0' != *cp; cp++)
1.46 schwarze 614: if (isspace((unsigned char)*cp))
1.1 kristaps 615: *cp = ' ';
616:
1.46 schwarze 617: for (cp = p->b; isspace((unsigned char)*cp); cp++)
1.4 kristaps 618: /* Spin past whitespace (XXX: necessary?) */ ;
1.1 kristaps 619: for ( ; '\0' != *cp; cp++) {
620: /* Escape us if we look like a macro. */
621: if ((cp == p->b || ' ' == *(cp - 1)) &&
1.46 schwarze 622: isupper((unsigned char)*cp) &&
1.44 schwarze 623: '\0' != *(cp + 1) &&
1.46 schwarze 624: islower((unsigned char)*(cp + 1)) &&
1.44 schwarze 625: ('\0' == *(cp + 2) ||
1.1 kristaps 626: ' ' == *(cp + 2) ||
1.46 schwarze 627: (islower((unsigned char)*(cp + 2)) &&
1.44 schwarze 628: ('\0' == *(cp + 3) ||
1.1 kristaps 629: ' ' == *(cp + 3)))))
630: fputs("\\&", stdout);
1.12 kristaps 631: if (MACROLINE_UPPER & fl)
1.46 schwarze 632: putchar(toupper((unsigned char)*cp));
1.12 kristaps 633: else
1.46 schwarze 634: putchar(*cp);
1.1 kristaps 635: /* If we're a character escape, escape us. */
636: if ('\\' == *cp)
637: putchar('e');
638: }
639: }
640:
1.12 kristaps 641: static void
642: pnode_printmacrolinepart(struct parse *p, struct pnode *pn)
643: {
644:
645: pnode_printmacrolinetext(p, pn, 0);
646: }
647:
1.1 kristaps 648: /*
649: * Just pnode_printmacrolinepart() but with a newline.
650: * If no text, just the newline.
651: */
652: static void
653: pnode_printmacroline(struct parse *p, struct pnode *pn)
654: {
655:
1.13 kristaps 656: assert(0 == p->newln);
1.12 kristaps 657: pnode_printmacrolinetext(p, pn, 0);
1.1 kristaps 658: putchar('\n');
1.13 kristaps 659: p->newln = 1;
1.1 kristaps 660: }
661:
1.10 kristaps 662: static void
663: pnode_printmopen(struct parse *p)
664: {
665: if (p->newln) {
666: putchar('.');
667: p->newln = 0;
668: } else
669: putchar(' ');
670: }
671:
672: static void
673: pnode_printmclose(struct parse *p, int sv)
674: {
675:
676: if (sv && ! p->newln) {
677: putchar('\n');
678: p->newln = 1;
679: }
680: }
681:
1.8 kristaps 682: /*
1.37 kristaps 683: * Like pnode_printmclose() except we look to the next node, and, if
684: * found, see if it starts with punctuation.
685: * If it does, then we print that punctuation before the newline.
686: */
687: static void
688: pnode_printmclosepunct(struct parse *p, struct pnode *pn, int sv)
689: {
690: /* We wouldn't have done anything anyway. */
1.44 schwarze 691: if ( ! (sv && ! p->newln))
1.37 kristaps 692: return;
693:
694: /* No next node or it's not text. */
695: if (NULL == (pn = TAILQ_NEXT(pn, child))) {
696: pnode_printmclose(p, sv);
697: return;
698: } else if (NODE_TEXT != pn->node) {
699: pnode_printmclose(p, sv);
700: return;
1.44 schwarze 701: }
1.37 kristaps 702:
703: /* Only do this for the comma/period. */
704: if (pn->bsz > 0 &&
705: (',' == pn->b[0] || '.' == pn->b[0]) &&
1.46 schwarze 706: (1 == pn->bsz || isspace((unsigned char)pn->b[1]))) {
1.37 kristaps 707: putchar(' ');
708: putchar(pn->b[0]);
709: pn->b++;
710: pn->bsz--;
1.44 schwarze 711: }
1.37 kristaps 712:
713: putchar('\n');
714: p->newln = 1;
715: }
716:
1.54 schwarze 717: static void
718: pnode_printpara(struct parse *p, struct pnode *pn)
719: {
720: struct pnode *pp;
721:
722: assert(p->newln);
723: if (NULL == pn->parent || NODE_LISTITEM == pn->parent->node)
724: return;
725:
726: pp = TAILQ_PREV(pn, pnodeq, child);
727: if (NULL == pp)
728: pp = pn->parent;
1.56 schwarze 729: if ((NODE_SECTION != pp->node && NODE_PREFACE != pp->node) ||
730: 2 < p->level)
731: puts(".Pp");
1.54 schwarze 732: }
733:
1.37 kristaps 734: /*
1.10 kristaps 735: * If the SYNOPSIS macro has a superfluous title, kill it.
1.8 kristaps 736: */
1.1 kristaps 737: static void
1.6 kristaps 738: pnode_printrefsynopsisdiv(struct parse *p, struct pnode *pn)
739: {
740: struct pnode *pp;
741:
1.44 schwarze 742: TAILQ_FOREACH(pp, &pn->childq, child)
1.6 kristaps 743: if (NODE_TITLE == pp->node) {
744: pnode_unlink(pp);
1.10 kristaps 745: return;
1.6 kristaps 746: }
747: }
748:
1.8 kristaps 749: /*
750: * Start a hopefully-named `Sh' section.
751: */
1.6 kristaps 752: static void
1.1 kristaps 753: pnode_printrefsect(struct parse *p, struct pnode *pn)
754: {
755: struct pnode *pp;
1.52 schwarze 756: const char *title;
757: int flags, level;
758:
1.56 schwarze 759: if (NULL == pn->parent)
760: return;
761:
1.52 schwarze 762: level = ++p->level;
763: flags = 1 == level ? MACROLINE_UPPER : 0;
764: if (3 > level) {
765: switch (pn->node) {
766: case (NODE_CAUTION):
767: case (NODE_NOTE):
768: case (NODE_TIP):
769: case (NODE_WARNING):
770: level = 3;
771: break;
772: default:
773: break;
774: }
775: }
1.1 kristaps 776:
777: TAILQ_FOREACH(pp, &pn->childq, child)
778: if (NODE_TITLE == pp->node)
779: break;
780:
1.52 schwarze 781: if (NULL == pp) {
782: switch (pn->node) {
783: case (NODE_PREFACE):
784: title = "Preface";
785: break;
786: case (NODE_CAUTION):
787: title = "Caution";
788: break;
789: case (NODE_NOTE):
790: title = "Note";
791: break;
792: case (NODE_TIP):
793: title = "Tip";
794: break;
795: case (NODE_WARNING):
796: title = "Warning";
797: break;
798: default:
799: title = "Unknown";
800: break;
801: }
802: }
803:
804: switch (level) {
805: case (1):
1.20 kristaps 806: fputs(".Sh", stdout);
1.29 kristaps 807: break;
1.52 schwarze 808: case (2):
1.20 kristaps 809: fputs(".Ss", stdout);
1.29 kristaps 810: break;
1.52 schwarze 811: default:
1.54 schwarze 812: pnode_printpara(p, pn);
1.29 kristaps 813: fputs(".Sy", stdout);
814: break;
815: }
1.20 kristaps 816:
1.5 kristaps 817: if (NULL != pp) {
1.52 schwarze 818: p->newln = 0;
819: pnode_printmacrolinetext(p, pp, flags);
1.18 kristaps 820: pnode_printmclose(p, 1);
1.5 kristaps 821: pnode_unlink(pp);
1.52 schwarze 822: } else
823: printf(" %s\n", title);
1.1 kristaps 824: }
825:
1.8 kristaps 826: /*
827: * Start a reference, extracting the title and volume.
828: */
1.1 kristaps 829: static void
830: pnode_printciterefentry(struct parse *p, struct pnode *pn)
831: {
832: struct pnode *pp, *title, *manvol;
833:
834: title = manvol = NULL;
835: TAILQ_FOREACH(pp, &pn->childq, child)
836: if (NODE_MANVOLNUM == pp->node)
837: manvol = pp;
838: else if (NODE_REFENTRYTITLE == pp->node)
839: title = pp;
840:
841: if (NULL != title) {
842: pnode_printmacrolinepart(p, title);
843: } else
1.13 kristaps 844: fputs(" unknown ", stdout);
1.4 kristaps 845:
1.13 kristaps 846: if (NULL == manvol) {
847: puts(" 1");
848: p->newln = 1;
849: } else
1.34 kristaps 850: pnode_printmacrolinepart(p, manvol);
1.1 kristaps 851: }
852:
853: static void
854: pnode_printrefmeta(struct parse *p, struct pnode *pn)
855: {
856: struct pnode *pp, *title, *manvol;
857:
858: title = manvol = NULL;
1.13 kristaps 859: assert(p->newln);
1.1 kristaps 860: TAILQ_FOREACH(pp, &pn->childq, child)
861: if (NODE_MANVOLNUM == pp->node)
862: manvol = pp;
863: else if (NODE_REFENTRYTITLE == pp->node)
864: title = pp;
865:
1.13 kristaps 866: fputs(".Dt", stdout);
867: p->newln = 0;
1.1 kristaps 868:
1.13 kristaps 869: if (NULL != title)
1.12 kristaps 870: pnode_printmacrolinetext(p, title, MACROLINE_UPPER);
1.13 kristaps 871: else
872: fputs(" UNKNOWN ", stdout);
873:
874: if (NULL == manvol) {
875: puts(" 1");
876: p->newln = 1;
1.1 kristaps 877: } else
878: pnode_printmacroline(p, manvol);
879: }
880:
1.3 kristaps 881: static void
882: pnode_printfuncdef(struct parse *p, struct pnode *pn)
883: {
884: struct pnode *pp, *ftype, *func;
885:
1.13 kristaps 886: assert(p->newln);
1.3 kristaps 887: ftype = func = NULL;
888: TAILQ_FOREACH(pp, &pn->childq, child)
889: if (NODE_TEXT == pp->node)
890: ftype = pp;
891: else if (NODE_FUNCTION == pp->node)
892: func = pp;
893:
894: if (NULL != ftype) {
1.13 kristaps 895: fputs(".Ft", stdout);
896: p->newln = 0;
1.3 kristaps 897: pnode_printmacroline(p, ftype);
898: }
899:
900: if (NULL != func) {
1.13 kristaps 901: fputs(".Fo", stdout);
902: p->newln = 0;
1.3 kristaps 903: pnode_printmacroline(p, func);
1.13 kristaps 904: } else {
1.3 kristaps 905: puts(".Fo UNKNOWN");
1.13 kristaps 906: p->newln = 1;
907: }
1.3 kristaps 908: }
909:
910: static void
911: pnode_printparamdef(struct parse *p, struct pnode *pn)
912: {
913: struct pnode *pp, *ptype, *param;
914:
1.13 kristaps 915: assert(p->newln);
1.3 kristaps 916: ptype = param = NULL;
917: TAILQ_FOREACH(pp, &pn->childq, child)
918: if (NODE_TEXT == pp->node)
919: ptype = pp;
920: else if (NODE_PARAMETER == pp->node)
921: param = pp;
922:
923: fputs(".Fa \"", stdout);
1.13 kristaps 924: p->newln = 0;
1.3 kristaps 925: if (NULL != ptype) {
1.32 kristaps 926: pnode_printmacrolinetext(p, ptype, MACROLINE_NOWS);
1.3 kristaps 927: putchar(' ');
928: }
929:
930: if (NULL != param)
931: pnode_printmacrolinepart(p, param);
932:
933: puts("\"");
1.13 kristaps 934: p->newln = 1;
1.3 kristaps 935: }
936:
1.40 kristaps 937: /*
1.41 kristaps 938: * The <mml:mfenced> node is a little peculiar.
939: * First, it can have arbitrary open and closing tokens, which default
940: * to parentheses.
941: * Second, >1 arguments are separated by commas.
942: */
943: static void
944: pnode_printmathfenced(struct parse *p, struct pnode *pn)
945: {
946: struct pnode *pp;
947:
1.59 schwarze 948: printf("left %s ", pnode_getattr_raw(pn, ATTRKEY_OPEN, "("));
1.41 kristaps 949:
950: pp = TAILQ_FIRST(&pn->childq);
951: pnode_print(p, pp);
952:
953: while (NULL != (pp = TAILQ_NEXT(pp, child))) {
954: putchar(',');
955: pnode_print(p, pp);
956: }
957:
1.59 schwarze 958: printf("right %s ", pnode_getattr_raw(pn, ATTRKEY_CLOSE, ")"));
1.41 kristaps 959: }
960:
961: /*
1.40 kristaps 962: * These math nodes require special handling because they have infix
963: * syntax, instead of the usual prefix or prefix.
964: * So we need to break up the first and second child node with a
965: * particular eqn(7) word.
966: */
967: static void
968: pnode_printmath(struct parse *p, struct pnode *pn)
969: {
970: struct pnode *pp;
971:
972: pp = TAILQ_FIRST(&pn->childq);
973: pnode_print(p, pp);
974:
975: switch (pn->node) {
976: case (NODE_MML_MSUP):
1.42 kristaps 977: fputs(" sup ", stdout);
1.40 kristaps 978: break;
979: case (NODE_MML_MFRAC):
1.42 kristaps 980: fputs(" over ", stdout);
1.40 kristaps 981: break;
982: case (NODE_MML_MSUB):
1.42 kristaps 983: fputs(" sub ", stdout);
1.40 kristaps 984: break;
985: default:
986: break;
987: }
988:
989: pp = TAILQ_NEXT(pp, child);
990: pnode_print(p, pp);
991: }
992:
1.3 kristaps 993: static void
994: pnode_printfuncprototype(struct parse *p, struct pnode *pn)
995: {
996: struct pnode *pp, *fdef;
997:
1.13 kristaps 998: assert(p->newln);
1.3 kristaps 999: TAILQ_FOREACH(fdef, &pn->childq, child)
1.44 schwarze 1000: if (NODE_FUNCDEF == fdef->node)
1.3 kristaps 1001: break;
1002:
1.4 kristaps 1003: if (NULL != fdef)
1.3 kristaps 1004: pnode_printfuncdef(p, fdef);
1.4 kristaps 1005: else
1.3 kristaps 1006: puts(".Fo UNKNOWN");
1007:
1.44 schwarze 1008: TAILQ_FOREACH(pp, &pn->childq, child)
1.3 kristaps 1009: if (NODE_PARAMDEF == pp->node)
1010: pnode_printparamdef(p, pp);
1011:
1012: puts(".Fc");
1.13 kristaps 1013: p->newln = 1;
1.3 kristaps 1014: }
1015:
1.44 schwarze 1016: /*
1.10 kristaps 1017: * The <arg> element is more complicated than it should be because text
1018: * nodes are treated like ".Ar foo", but non-text nodes need to be
1019: * re-sent into the printer (i.e., without the preceding ".Ar").
1.12 kristaps 1020: * This also handles the case of "repetition" (or in other words, the
1021: * ellipsis following an argument) and optionality.
1.10 kristaps 1022: */
1.4 kristaps 1023: static void
1.10 kristaps 1024: pnode_printarg(struct parse *p, struct pnode *pn)
1.4 kristaps 1025: {
1026: struct pnode *pp;
1.12 kristaps 1027: struct pattr *ap;
1028: int isop, isrep;
1029:
1030: isop = 1;
1031: isrep = 0;
1.44 schwarze 1032: TAILQ_FOREACH(ap, &pn->attrq, child)
1.12 kristaps 1033: if (ATTRKEY_CHOICE == ap->key &&
1034: (ATTRVAL_PLAIN == ap->val ||
1.44 schwarze 1035: ATTRVAL_REQ == ap->val))
1.12 kristaps 1036: isop = 0;
1037: else if (ATTRKEY_REP == ap->key &&
1038: (ATTRVAL_REPEAT == ap->val))
1039: isrep = 1;
1040:
1041: if (isop) {
1042: pnode_printmopen(p);
1.13 kristaps 1043: fputs("Op", stdout);
1.12 kristaps 1044: }
1.4 kristaps 1045:
1.10 kristaps 1046: TAILQ_FOREACH(pp, &pn->childq, child) {
1047: if (NODE_TEXT == pp->node) {
1048: pnode_printmopen(p);
1.13 kristaps 1049: fputs("Ar", stdout);
1.44 schwarze 1050: }
1.10 kristaps 1051: pnode_print(p, pp);
1.44 schwarze 1052: if (NODE_TEXT == pp->node && isrep)
1.12 kristaps 1053: fputs("...", stdout);
1.10 kristaps 1054: }
1.4 kristaps 1055: }
1056:
1.24 kristaps 1057: static void
1058: pnode_printgroup(struct parse *p, struct pnode *pn)
1059: {
1060: struct pnode *pp, *np;
1061: struct pattr *ap;
1062: int isop, sv;
1063:
1064: isop = 1;
1.44 schwarze 1065: TAILQ_FOREACH(ap, &pn->attrq, child)
1.24 kristaps 1066: if (ATTRKEY_CHOICE == ap->key &&
1067: (ATTRVAL_PLAIN == ap->val ||
1068: ATTRVAL_REQ == ap->val)) {
1069: isop = 0;
1070: break;
1071: }
1072:
1.44 schwarze 1073: /*
1.24 kristaps 1074: * Make sure we're on a macro line.
1075: * This will prevent pnode_print() for putting us on a
1076: * subsequent line.
1077: */
1078: sv = p->newln;
1079: pnode_printmopen(p);
1.44 schwarze 1080: if (isop)
1.24 kristaps 1081: fputs("Op", stdout);
1082: else if (sv)
1083: fputs("No", stdout);
1084:
1085: /*
1086: * Keep on printing text separated by the vertical bar as long
1087: * as we're within the same origin node as the group.
1088: * This is kind of a nightmare.
1089: * Eh, DocBook...
1090: * FIXME: if there's a "Fl", we don't cut off the leading "-"
1091: * like we do in pnode_print().
1092: */
1093: TAILQ_FOREACH(pp, &pn->childq, child) {
1094: pnode_print(p, pp);
1095: np = TAILQ_NEXT(pp, child);
1096: while (NULL != np) {
1097: if (pp->node != np->node)
1098: break;
1099: fputs(" |", stdout);
1100: pnode_printmacrolinepart(p, np);
1101: pp = np;
1102: np = TAILQ_NEXT(np, child);
1103: }
1104: }
1105:
1106: pnode_printmclose(p, sv);
1107: }
1108:
1.7 kristaps 1109: static void
1110: pnode_printprologue(struct parse *p, struct pnode *pn)
1111: {
1112: struct pnode *pp;
1113:
1.9 kristaps 1114: pp = NULL == p->root ? NULL :
1115: pnode_findfirst(p->root, NODE_REFMETA);
1116:
1.51 schwarze 1117: puts(".Dd $Mdocdate" "$");
1.9 kristaps 1118: if (NULL != pp) {
1.7 kristaps 1119: pnode_printrefmeta(p, pp);
1120: pnode_unlink(pp);
1.59 schwarze 1121: } else
1122: printf(".Dt %s 1\n",
1123: pnode_getattr_raw(p->root, ATTRKEY_ID, "UNKNOWN"));
1.51 schwarze 1124: puts(".Os");
1.43 kristaps 1125:
1126: if (PARSE_EQN & p->flags) {
1127: puts(".EQ");
1128: puts("delim $$");
1129: puts(".EN");
1130: }
1.7 kristaps 1131: }
1132:
1.42 kristaps 1133: /*
1134: * We can have multiple <term> elements within a <varlistentry>, which
1135: * we should comma-separate as list headers.
1136: */
1.13 kristaps 1137: static void
1138: pnode_printvarlistentry(struct parse *p, struct pnode *pn)
1139: {
1140: struct pnode *pp;
1.42 kristaps 1141: int first = 1;
1.13 kristaps 1142:
1143: assert(p->newln);
1.42 kristaps 1144: fputs(".It", stdout);
1145: p->newln = 0;
1146:
1.13 kristaps 1147: TAILQ_FOREACH(pp, &pn->childq, child)
1148: if (NODE_TERM == pp->node) {
1.42 kristaps 1149: if ( ! first)
1150: putchar(',');
1.13 kristaps 1151: pnode_print(p, pp);
1152: pnode_unlink(pp);
1.42 kristaps 1153: first = 0;
1154: } else
1155: break;
1.13 kristaps 1156:
1.42 kristaps 1157: putchar('\n');
1.13 kristaps 1158: p->newln = 1;
1159: }
1160:
1161: static void
1.25 kristaps 1162: pnode_printrow(struct parse *p, struct pnode *pn)
1163: {
1164: struct pnode *pp;
1165:
1166: puts(".Bl -dash -compact");
1167:
1168: TAILQ_FOREACH(pp, &pn->childq, child) {
1169: assert(p->newln);
1170: puts(".It");
1171: pnode_print(p, pp);
1172: pnode_printmclose(p, 1);
1173: }
1174: assert(p->newln);
1175: puts(".El");
1176: }
1177:
1178: static void
1179: pnode_printtable(struct parse *p, struct pnode *pn)
1.16 kristaps 1180: {
1181: struct pnode *pp;
1182:
1183: assert(p->newln);
1184: TAILQ_FOREACH(pp, &pn->childq, child)
1185: if (NODE_TITLE == pp->node) {
1.54 schwarze 1186: pnode_printpara(p, pp);
1.16 kristaps 1187: pnode_print(p, pp);
1188: pnode_unlink(pp);
1189: }
1.25 kristaps 1190: assert(p->newln);
1191: puts(".Bl -ohang");
1192: while (NULL != (pp = pnode_findfirst(pn, NODE_ROW))) {
1193: puts(".It Table Row");
1194: pnode_printrow(p, pp);
1195: pnode_printmclose(p, 1);
1196: pnode_unlink(pp);
1197: }
1198: assert(p->newln);
1199: puts(".El");
1200: }
1201:
1202: static void
1203: pnode_printlist(struct parse *p, struct pnode *pn)
1204: {
1205: struct pnode *pp;
1.16 kristaps 1206:
1207: assert(p->newln);
1.25 kristaps 1208: TAILQ_FOREACH(pp, &pn->childq, child)
1209: if (NODE_TITLE == pp->node) {
1.54 schwarze 1210: pnode_printpara(p, pp);
1.25 kristaps 1211: pnode_print(p, pp);
1212: pnode_unlink(pp);
1213: }
1214: assert(p->newln);
1.21 kristaps 1215:
1216: if (NODE_ORDEREDLIST == pn->node)
1217: puts(".Bl -enum");
1218: else
1.53 schwarze 1219: puts(".Bl -bullet");
1.21 kristaps 1220:
1.16 kristaps 1221: TAILQ_FOREACH(pp, &pn->childq, child) {
1222: assert(p->newln);
1223: puts(".It");
1224: pnode_print(p, pp);
1225: pnode_printmclose(p, 1);
1226: }
1227: assert(p->newln);
1228: puts(".El");
1229: }
1230:
1231: static void
1.13 kristaps 1232: pnode_printvariablelist(struct parse *p, struct pnode *pn)
1233: {
1234: struct pnode *pp;
1235:
1236: assert(p->newln);
1237: TAILQ_FOREACH(pp, &pn->childq, child)
1238: if (NODE_TITLE == pp->node) {
1.54 schwarze 1239: pnode_printpara(p, pp);
1.13 kristaps 1240: pnode_print(p, pp);
1241: pnode_unlink(pp);
1242: }
1243:
1244: assert(p->newln);
1245: puts(".Bl -tag -width Ds");
1246: TAILQ_FOREACH(pp, &pn->childq, child)
1247: if (NODE_VARLISTENTRY != pp->node) {
1248: assert(p->newln);
1249: fputs(".It", stdout);
1250: pnode_printmacroline(p, pp);
1251: } else {
1252: assert(p->newln);
1253: pnode_print(p, pp);
1254: }
1255: assert(p->newln);
1256: puts(".El");
1257: }
1258:
1.1 kristaps 1259: /*
1260: * Print a parsed node (or ignore it--whatever).
1261: * This is a recursive function.
1.23 kristaps 1262: * FIXME: if we're in a literal context (<screen> or <programlisting> or
1263: * whatever), don't print inline macros.
1.1 kristaps 1264: */
1265: static void
1266: pnode_print(struct parse *p, struct pnode *pn)
1267: {
1268: struct pnode *pp;
1.59 schwarze 1269: const char *ccp;
1.1 kristaps 1270: char *cp;
1.10 kristaps 1271: int last, sv;
1.1 kristaps 1272:
1273: if (NULL == pn)
1274: return;
1275:
1.10 kristaps 1276: sv = p->newln;
1.1 kristaps 1277:
1.50 schwarze 1278: /* XXX fprintf(stderr, "NODE %s\n", nodes[pn->node].name); */
1.1 kristaps 1279: switch (pn->node) {
1.27 kristaps 1280: case (NODE_APPLICATION):
1281: pnode_printmopen(p);
1282: fputs("Nm", stdout);
1283: break;
1.30 kristaps 1284: case (NODE_ANCHOR):
1285: /* Don't print anything! */
1286: return;
1.4 kristaps 1287: case (NODE_ARG):
1.10 kristaps 1288: pnode_printarg(p, pn);
1.4 kristaps 1289: pnode_unlinksub(pn);
1290: break;
1.50 schwarze 1291: case (NODE_AUTHOR):
1292: pnode_printmopen(p);
1293: fputs("An", stdout);
1294: break;
1295: case (NODE_AUTHORGROUP):
1296: assert(p->newln);
1297: puts(".An -split");
1298: break;
1299: case (NODE_BOOKINFO):
1300: assert(p->newln);
1301: puts(".Sh NAME");
1302: break;
1.1 kristaps 1303: case (NODE_CITEREFENTRY):
1.34 kristaps 1304: pnode_printmopen(p);
1305: fputs("Xr", stdout);
1.1 kristaps 1306: pnode_printciterefentry(p, pn);
1.4 kristaps 1307: pnode_unlinksub(pn);
1.1 kristaps 1308: break;
1309: case (NODE_CODE):
1.10 kristaps 1310: pnode_printmopen(p);
1.13 kristaps 1311: fputs("Li", stdout);
1.4 kristaps 1312: break;
1313: case (NODE_COMMAND):
1.10 kristaps 1314: pnode_printmopen(p);
1.13 kristaps 1315: fputs("Nm", stdout);
1316: break;
1.33 kristaps 1317: case (NODE_CONSTANT):
1318: pnode_printmopen(p);
1319: fputs("Dv", stdout);
1320: break;
1.50 schwarze 1321: case (NODE_EDITOR):
1322: puts("editor: ");
1323: pnode_printmopen(p);
1324: fputs("An", stdout);
1325: break;
1.13 kristaps 1326: case (NODE_EMPHASIS):
1.57 schwarze 1327: case (NODE_FIRSTTERM):
1.13 kristaps 1328: pnode_printmopen(p);
1329: fputs("Em", stdout);
1.1 kristaps 1330: break;
1.21 kristaps 1331: case (NODE_ENVAR):
1332: pnode_printmopen(p);
1333: fputs("Ev", stdout);
1334: break;
1.17 kristaps 1335: case (NODE_FILENAME):
1336: pnode_printmopen(p);
1337: fputs("Pa", stdout);
1338: break;
1.3 kristaps 1339: case (NODE_FUNCTION):
1.10 kristaps 1340: pnode_printmopen(p);
1.13 kristaps 1341: fputs("Fn", stdout);
1.3 kristaps 1342: break;
1343: case (NODE_FUNCPROTOTYPE):
1.10 kristaps 1344: assert(p->newln);
1.3 kristaps 1345: pnode_printfuncprototype(p, pn);
1.4 kristaps 1346: pnode_unlinksub(pn);
1.3 kristaps 1347: break;
1.1 kristaps 1348: case (NODE_FUNCSYNOPSISINFO):
1.10 kristaps 1349: pnode_printmopen(p);
1.13 kristaps 1350: fputs("Fd", stdout);
1.16 kristaps 1351: break;
1.55 schwarze 1352: case (NODE_INDEXTERM):
1353: return;
1.43 kristaps 1354: case (NODE_INFORMALEQUATION):
1355: if ( ! p->newln)
1356: putchar('\n');
1357: puts(".EQ");
1358: p->newln = 0;
1359: break;
1360: case (NODE_INLINEEQUATION):
1361: fputc('$', stdout);
1362: p->newln = 0;
1363: break;
1.16 kristaps 1364: case (NODE_ITEMIZEDLIST):
1365: assert(p->newln);
1.25 kristaps 1366: pnode_printlist(p, pn);
1367: pnode_unlinksub(pn);
1.24 kristaps 1368: break;
1369: case (NODE_GROUP):
1370: pnode_printgroup(p, pn);
1371: pnode_unlinksub(pn);
1.10 kristaps 1372: break;
1.50 schwarze 1373: case (NODE_LEGALNOTICE):
1374: assert(p->newln);
1375: puts(".Sh LEGAL NOTICE");
1376: break;
1.58 schwarze 1377: case (NODE_LINK):
1.59 schwarze 1378: ccp = pnode_getattr_raw(pn, ATTRKEY_LINKEND, NULL);
1379: if (NULL == ccp)
1.58 schwarze 1380: break;
1381: pnode_printmopen(p);
1.59 schwarze 1382: printf("Sx %s\n", ccp);
1.58 schwarze 1383: p->newln = 1;
1384: return;
1.19 kristaps 1385: case (NODE_LITERAL):
1386: pnode_printmopen(p);
1387: fputs("Li", stdout);
1388: break;
1.60 ! schwarze 1389: case (NODE_LITERALLAYOUT):
! 1390: assert(p->newln);
! 1391: puts(".Bd -literal");
! 1392: break;
1.40 kristaps 1393: case (NODE_MML_MFENCED):
1.41 kristaps 1394: pnode_printmathfenced(p, pn);
1395: pnode_unlinksub(pn);
1.40 kristaps 1396: break;
1397: case (NODE_MML_MROW):
1398: case (NODE_MML_MI):
1399: case (NODE_MML_MN):
1400: case (NODE_MML_MO):
1.43 kristaps 1401: if (TAILQ_EMPTY(&pn->childq))
1402: break;
1403: fputs(" { ", stdout);
1.40 kristaps 1404: break;
1405: case (NODE_MML_MFRAC):
1406: case (NODE_MML_MSUB):
1407: case (NODE_MML_MSUP):
1408: pnode_printmath(p, pn);
1409: pnode_unlinksub(pn);
1410: break;
1.10 kristaps 1411: case (NODE_OPTION):
1412: pnode_printmopen(p);
1.13 kristaps 1413: fputs("Fl", stdout);
1.1 kristaps 1414: break;
1.25 kristaps 1415: case (NODE_ORDEREDLIST):
1416: assert(p->newln);
1417: pnode_printlist(p, pn);
1418: pnode_unlinksub(pn);
1419: break;
1.1 kristaps 1420: case (NODE_PARA):
1.54 schwarze 1421: pnode_printpara(p, pn);
1.3 kristaps 1422: break;
1423: case (NODE_PARAMETER):
1.10 kristaps 1424: /* Suppress non-text children... */
1425: pnode_printmopen(p);
1426: fputs("Fa \"", stdout);
1.32 kristaps 1427: pnode_printmacrolinetext(p, pn, MACROLINE_NOWS);
1428: fputs("\"", stdout);
1.4 kristaps 1429: pnode_unlinksub(pn);
1.1 kristaps 1430: break;
1.28 kristaps 1431: case (NODE_QUOTE):
1432: pnode_printmopen(p);
1433: fputs("Qo", stdout);
1434: break;
1.1 kristaps 1435: case (NODE_PROGRAMLISTING):
1.22 kristaps 1436: case (NODE_SCREEN):
1.10 kristaps 1437: assert(p->newln);
1.60 ! schwarze 1438: printf(".Bd %s\n", ATTRVAL_MONOSPACED ==
! 1439: pnode_getattr(pn, ATTRKEY_CLASS) ?
! 1440: "-literal" : "-unfilled");
1.15 kristaps 1441: break;
1442: case (NODE_REFENTRYINFO):
1443: /* Suppress. */
1444: pnode_unlinksub(pn);
1.1 kristaps 1445: break;
1446: case (NODE_REFMETA):
1.7 kristaps 1447: abort();
1.1 kristaps 1448: break;
1449: case (NODE_REFNAME):
1.10 kristaps 1450: /* Suppress non-text children... */
1451: pnode_printmopen(p);
1.13 kristaps 1452: fputs("Nm", stdout);
1453: p->newln = 0;
1.10 kristaps 1454: pnode_printmacrolinepart(p, pn);
1.4 kristaps 1455: pnode_unlinksub(pn);
1.10 kristaps 1456: break;
1.1 kristaps 1457: case (NODE_REFNAMEDIV):
1.10 kristaps 1458: assert(p->newln);
1.1 kristaps 1459: puts(".Sh NAME");
1460: break;
1461: case (NODE_REFPURPOSE):
1.10 kristaps 1462: assert(p->newln);
1.13 kristaps 1463: pnode_printmopen(p);
1464: fputs("Nd", stdout);
1.10 kristaps 1465: break;
1.1 kristaps 1466: case (NODE_REFSYNOPSISDIV):
1.10 kristaps 1467: assert(p->newln);
1.6 kristaps 1468: pnode_printrefsynopsisdiv(p, pn);
1.10 kristaps 1469: puts(".Sh SYNOPSIS");
1.1 kristaps 1470: break;
1.52 schwarze 1471: case (NODE_PREFACE):
1472: case (NODE_SECTION):
1.29 kristaps 1473: case (NODE_NOTE):
1474: case (NODE_TIP):
1475: case (NODE_CAUTION):
1476: case (NODE_WARNING):
1.10 kristaps 1477: assert(p->newln);
1.1 kristaps 1478: pnode_printrefsect(p, pn);
1479: break;
1.13 kristaps 1480: case (NODE_REPLACEABLE):
1481: pnode_printmopen(p);
1482: fputs("Ar", stdout);
1483: break;
1.19 kristaps 1484: case (NODE_SBR):
1485: assert(p->newln);
1486: puts(".br");
1487: break;
1.30 kristaps 1488: case (NODE_SGMLTAG):
1489: pnode_printmopen(p);
1490: fputs("Li", stdout);
1491: break;
1.8 kristaps 1492: case (NODE_STRUCTNAME):
1.10 kristaps 1493: pnode_printmopen(p);
1.13 kristaps 1494: fputs("Vt", stdout);
1.25 kristaps 1495: break;
1496: case (NODE_TABLE):
1.35 kristaps 1497: /* FALLTHROUGH */
1498: case (NODE_INFORMALTABLE):
1.25 kristaps 1499: assert(p->newln);
1500: pnode_printtable(p, pn);
1501: pnode_unlinksub(pn);
1.10 kristaps 1502: break;
1.1 kristaps 1503: case (NODE_TEXT):
1.13 kristaps 1504: if (0 == p->newln)
1505: putchar(' ');
1.37 kristaps 1506:
1.1 kristaps 1507: bufclear(p);
1508: bufappend(p, pn);
1.37 kristaps 1509:
1510: if (0 == p->bsz) {
1511: assert(pn->real != pn->b);
1512: break;
1513: }
1514:
1.1 kristaps 1515: /*
1516: * Output all characters, squeezing out whitespace
1.44 schwarze 1517: * between newlines.
1.1 kristaps 1518: * XXX: all whitespace, including tabs (?).
1519: * Remember to escape control characters and escapes.
1520: */
1.10 kristaps 1521: assert(p->bsz);
1.20 kristaps 1522: cp = p->b;
1.37 kristaps 1523:
1.20 kristaps 1524: /*
1525: * There's often a superfluous "-" in its <option> tags
1526: * before the actual flags themselves.
1527: * "Fl" does this for us, so remove it.
1528: */
1529: if (NULL != pn->parent &&
1530: NODE_OPTION == pn->parent->node &&
1531: '-' == *cp)
1532: cp++;
1533: for (last = '\n'; '\0' != *cp; ) {
1.1 kristaps 1534: if ('\n' == last) {
1535: /* Consume all whitespace. */
1.46 schwarze 1536: if (isspace((unsigned char)*cp)) {
1537: while (isspace((unsigned char)*cp))
1.1 kristaps 1538: cp++;
1539: continue;
1540: } else if ('\'' == *cp || '.' == *cp)
1541: fputs("\\&", stdout);
1542: }
1543: putchar(last = *cp++);
1544: /* If we're a character escape, escape us. */
1545: if ('\\' == last)
1546: putchar('e');
1547: }
1.10 kristaps 1548: p->newln = 0;
1.1 kristaps 1549: break;
1.50 schwarze 1550: case (NODE_TITLE):
1551: if (pn->parent->node == NODE_BOOKINFO) {
1552: pnode_printmopen(p);
1553: fputs("Nd", stdout);
1554: }
1555: break;
1.39 kristaps 1556: case (NODE_TYPE):
1557: pnode_printmopen(p);
1558: fputs("Vt", stdout);
1559: break;
1.26 kristaps 1560: case (NODE_USERINPUT):
1561: pnode_printmopen(p);
1562: fputs("Li", stdout);
1563: break;
1.13 kristaps 1564: case (NODE_VARIABLELIST):
1565: assert(p->newln);
1566: pnode_printvariablelist(p, pn);
1567: pnode_unlinksub(pn);
1568: break;
1569: case (NODE_VARLISTENTRY):
1570: assert(p->newln);
1571: pnode_printvarlistentry(p, pn);
1572: break;
1.26 kristaps 1573: case (NODE_VARNAME):
1.23 kristaps 1574: pnode_printmopen(p);
1.26 kristaps 1575: fputs("Va", stdout);
1.23 kristaps 1576: break;
1.1 kristaps 1577: default:
1578: break;
1579: }
1580:
1581: TAILQ_FOREACH(pp, &pn->childq, child)
1582: pnode_print(p, pp);
1583:
1584: switch (pn->node) {
1.43 kristaps 1585: case (NODE_INFORMALEQUATION):
1.40 kristaps 1586: if ( ! p->newln)
1587: putchar('\n');
1588: puts(".EN");
1589: p->newln = 1;
1590: break;
1.43 kristaps 1591: case (NODE_INLINEEQUATION):
1592: fputs("$ ", stdout);
1593: p->newln = sv;
1594: break;
1.40 kristaps 1595: case (NODE_MML_MROW):
1596: case (NODE_MML_MI):
1597: case (NODE_MML_MN):
1598: case (NODE_MML_MO):
1.43 kristaps 1599: if (TAILQ_EMPTY(&pn->childq))
1600: break;
1601: fputs(" } ", stdout);
1.40 kristaps 1602: break;
1.27 kristaps 1603: case (NODE_APPLICATION):
1.10 kristaps 1604: case (NODE_ARG):
1.50 schwarze 1605: case (NODE_AUTHOR):
1.34 kristaps 1606: case (NODE_CITEREFENTRY):
1.10 kristaps 1607: case (NODE_CODE):
1608: case (NODE_COMMAND):
1.33 kristaps 1609: case (NODE_CONSTANT):
1.50 schwarze 1610: case (NODE_EDITOR):
1.13 kristaps 1611: case (NODE_EMPHASIS):
1.21 kristaps 1612: case (NODE_ENVAR):
1.17 kristaps 1613: case (NODE_FILENAME):
1.57 schwarze 1614: case (NODE_FIRSTTERM):
1.10 kristaps 1615: case (NODE_FUNCTION):
1616: case (NODE_FUNCSYNOPSISINFO):
1.19 kristaps 1617: case (NODE_LITERAL):
1.10 kristaps 1618: case (NODE_OPTION):
1619: case (NODE_PARAMETER):
1.13 kristaps 1620: case (NODE_REPLACEABLE):
1621: case (NODE_REFPURPOSE):
1.30 kristaps 1622: case (NODE_SGMLTAG):
1.10 kristaps 1623: case (NODE_STRUCTNAME):
1624: case (NODE_TEXT):
1.39 kristaps 1625: case (NODE_TYPE):
1.23 kristaps 1626: case (NODE_USERINPUT):
1.26 kristaps 1627: case (NODE_VARNAME):
1.37 kristaps 1628: pnode_printmclosepunct(p, pn, sv);
1.28 kristaps 1629: break;
1630: case (NODE_QUOTE):
1631: pnode_printmclose(p, sv);
1632: sv = p->newln;
1633: pnode_printmopen(p);
1634: fputs("Qc", stdout);
1.10 kristaps 1635: pnode_printmclose(p, sv);
1636: break;
1.12 kristaps 1637: case (NODE_REFNAME):
1638: /*
1639: * If we're in the NAME macro and we have multiple
1640: * <refname> macros in sequence, then print out a
1641: * trailing comma before the newline.
1642: */
1.44 schwarze 1643: if (NULL != pn->parent &&
1.12 kristaps 1644: NODE_REFNAMEDIV == pn->parent->node &&
1645: NULL != TAILQ_NEXT(pn, child) &&
1.44 schwarze 1646: NODE_REFNAME == TAILQ_NEXT(pn, child)->node)
1.12 kristaps 1647: fputs(" ,", stdout);
1648: pnode_printmclose(p, sv);
1.52 schwarze 1649: break;
1650: case (NODE_PREFACE):
1651: case (NODE_SECTION):
1652: case (NODE_NOTE):
1653: case (NODE_TIP):
1654: case (NODE_CAUTION):
1655: case (NODE_WARNING):
1656: p->level--;
1.12 kristaps 1657: break;
1.50 schwarze 1658: case (NODE_LITERALLAYOUT):
1659: /* FALLTHROUGH */
1.1 kristaps 1660: case (NODE_PROGRAMLISTING):
1.22 kristaps 1661: /* FALLTHROUGH */
1662: case (NODE_SCREEN):
1.10 kristaps 1663: assert(p->newln);
1.1 kristaps 1664: puts(".Ed");
1.10 kristaps 1665: p->newln = 1;
1.50 schwarze 1666: break;
1667: case (NODE_TITLE):
1668: if (pn->parent->node == NODE_BOOKINFO) {
1669: pnode_printmclose(p, 1);
1670: puts(".Sh AUTHORS");
1671: }
1.1 kristaps 1672: break;
1673: default:
1674: break;
1675: }
1676: }
1677:
1678: /*
1679: * Loop around the read buffer until we've drained it of all data.
1680: * Invoke the parser context with each buffer fill.
1681: */
1682: static int
1.44 schwarze 1683: readfile(XML_Parser xp, int fd,
1.1 kristaps 1684: char *b, size_t bsz, const char *fn)
1685: {
1686: struct parse p;
1687: int rc;
1688: ssize_t ssz;
1689:
1690: memset(&p, 0, sizeof(struct parse));
1691:
1692: p.b = malloc(p.bsz = p.mbsz = 1024);
1.12 kristaps 1693: p.fname = fn;
1694: p.xml = xp;
1.1 kristaps 1695:
1696: XML_SetCharacterDataHandler(xp, xml_char);
1697: XML_SetElementHandler(xp, xml_elem_start, xml_elem_end);
1698: XML_SetUserData(xp, &p);
1699:
1700: while ((ssz = read(fd, b, bsz)) >= 0) {
1701: if (0 == (rc = XML_Parse(xp, b, ssz, 0 == ssz)))
1.30 kristaps 1702: fprintf(stderr, "%s:%zu:%zu: %s\n", fn,
1703: XML_GetCurrentLineNumber(xp),
1.44 schwarze 1704: XML_GetCurrentColumnNumber(xp),
1.1 kristaps 1705: XML_ErrorString
1706: (XML_GetErrorCode(xp)));
1707: else if ( ! p.stop && ssz > 0)
1708: continue;
1.44 schwarze 1709: /*
1.1 kristaps 1710: * Exit when we've read all or errors have occured
1711: * during the parse sequence.
1712: */
1.10 kristaps 1713: p.newln = 1;
1.7 kristaps 1714: pnode_printprologue(&p, p.root);
1.1 kristaps 1715: pnode_print(&p, p.root);
1716: pnode_free(p.root);
1717: free(p.b);
1718: return(0 != rc && ! p.stop);
1719: }
1720:
1721: /* Read error has occured. */
1722: perror(fn);
1723: pnode_free(p.root);
1724: free(p.b);
1725: return(0);
1726: }
1727:
1728: int
1729: main(int argc, char *argv[])
1730: {
1731: XML_Parser xp;
1732: const char *fname;
1733: char *buf;
1.38 kristaps 1734: int fd, rc, ch;
1735: const char *progname;
1736:
1737: progname = strrchr(argv[0], '/');
1738: if (progname == NULL)
1739: progname = argv[0];
1740: else
1741: ++progname;
1.1 kristaps 1742:
1743: fname = "-";
1744: xp = NULL;
1745: buf = NULL;
1746: rc = 0;
1747:
1.38 kristaps 1748: while (-1 != (ch = getopt(argc, argv, "W")))
1749: switch (ch) {
1750: case ('W'):
1751: warn = 1;
1752: break;
1753: default:
1754: goto usage;
1755: }
1.1 kristaps 1756:
1757: argc -= optind;
1758: argv += optind;
1759:
1.45 schwarze 1760: if (argc > 1) {
1761: fprintf(stderr, "%s: Too many arguments\n", argv[1]);
1762: goto usage;
1763: } else if (argc > 0)
1.1 kristaps 1764: fname = argv[0];
1765:
1766: /* Read from stdin or a file. */
1.44 schwarze 1767: fd = 0 == strcmp(fname, "-") ?
1.1 kristaps 1768: STDIN_FILENO : open(fname, O_RDONLY, 0);
1769:
1770: /*
1771: * Open file for reading.
1772: * Allocate a read buffer.
1773: * Create the parser context.
1774: * Dive directly into the parse.
1775: */
1776: if (-1 == fd)
1777: perror(fname);
1778: else if (NULL == (buf = malloc(4096)))
1779: perror(NULL);
1780: else if (NULL == (xp = XML_ParserCreate(NULL)))
1781: perror(NULL);
1782: else if ( ! readfile(xp, fd, buf, 4096, fname))
1783: rc = 1;
1784:
1785: XML_ParserFree(xp);
1786: free(buf);
1787: if (STDIN_FILENO != fd)
1788: close(fd);
1789: return(rc ? EXIT_SUCCESS : EXIT_FAILURE);
1.38 kristaps 1790:
1791: usage:
1.45 schwarze 1792: fprintf(stderr, "usage: %s [-W] [input_filename]\n", progname);
1.38 kristaps 1793: return(EXIT_FAILURE);
1.1 kristaps 1794: }
CVSweb