Annotation of docbook2mdoc/macro.c, Revision 1.21
1.21 ! schwarze 1: /* $Id: macro.c,v 1.20 2019/05/02 12:40:42 schwarze Exp $ */
1.1 schwarze 2: /*
3: * Copyright (c) 2019 Ingo Schwarze <schwarze@openbsd.org>
4: *
5: * Permission to use, copy, modify, and distribute this software for any
6: * purpose with or without fee is hereby granted, provided that the above
7: * copyright notice and this permission notice appear in all copies.
8: *
9: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
10: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
12: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16: */
17: #include <assert.h>
18: #include <ctype.h>
19: #include <stdio.h>
1.6 schwarze 20: #include <string.h>
1.1 schwarze 21:
22: #include "node.h"
23: #include "macro.h"
24:
25: /*
26: * The implementation of the macro line formatter,
27: * a part of the mdoc(7) formatter.
28: */
29:
30: void
1.18 schwarze 31: para_check(struct format *f)
32: {
33: if (f->parastate != PARA_WANT)
34: return;
35: if (f->linestate != LINE_NEW) {
36: putchar('\n');
37: f->linestate = LINE_NEW;
38: }
39: puts(".Pp");
40: f->parastate = PARA_HAVE;
41: }
42:
43: void
1.1 schwarze 44: macro_open(struct format *f, const char *name)
45: {
1.18 schwarze 46: para_check(f);
1.1 schwarze 47: switch (f->linestate) {
1.9 schwarze 48: case LINE_MACRO:
49: if (f->flags & FMT_NOSPC) {
50: fputs(" Ns ", stdout);
51: break;
52: }
1.19 schwarze 53: if (f->nofill || f->flags & (FMT_CHILD | FMT_IMPL)) {
1.9 schwarze 54: putchar(' ');
55: break;
56: }
57: /* FALLTHROUGH */
1.1 schwarze 58: case LINE_TEXT:
1.18 schwarze 59: if (f->nofill && f->linestate == LINE_TEXT)
1.17 schwarze 60: fputs(" \\c", stdout);
1.1 schwarze 61: putchar('\n');
62: /* FALLTHROUGH */
63: case LINE_NEW:
64: putchar('.');
65: f->linestate = LINE_MACRO;
1.9 schwarze 66: f->flags = 0;
1.1 schwarze 67: break;
68: }
69: fputs(name, stdout);
1.9 schwarze 70: f->flags &= FMT_IMPL;
71: f->flags |= FMT_ARG;
1.14 schwarze 72: f->parastate = PARA_MID;
1.1 schwarze 73: }
74:
75: void
76: macro_close(struct format *f)
77: {
1.9 schwarze 78: if (f->linestate != LINE_NEW)
79: putchar('\n');
1.1 schwarze 80: f->linestate = LINE_NEW;
1.9 schwarze 81: f->flags = 0;
1.1 schwarze 82: }
83:
84: void
85: macro_line(struct format *f, const char *name)
86: {
1.7 schwarze 87: macro_close(f);
1.1 schwarze 88: macro_open(f, name);
89: macro_close(f);
90: }
91:
92: /*
93: * Print an argument string on a macro line, collapsing whitespace.
94: */
95: void
96: macro_addarg(struct format *f, const char *arg, int flags)
97: {
98: const char *cp;
1.13 schwarze 99: int quote_now;
1.1 schwarze 100:
101: assert(f->linestate == LINE_MACRO);
102:
103: /* Quote if requested and necessary. */
104:
1.13 schwarze 105: quote_now = 0;
1.1 schwarze 106: if ((flags & (ARG_SINGLE | ARG_QUOTED)) == ARG_SINGLE) {
107: for (cp = arg; *cp != '\0'; cp++)
108: if (isspace((unsigned char)*cp))
109: break;
110: if (*cp != '\0') {
111: if (flags & ARG_SPACE) {
112: putchar(' ');
113: flags &= ~ ARG_SPACE;
114: }
115: putchar('"');
116: flags = ARG_QUOTED;
1.13 schwarze 117: quote_now = 1;
1.1 schwarze 118: }
119: }
120:
121: for (cp = arg; *cp != '\0'; cp++) {
122:
123: /* Collapse whitespace. */
124:
125: if (isspace((unsigned char)*cp)) {
126: flags |= ARG_SPACE;
127: continue;
128: } else if (flags & ARG_SPACE) {
129: putchar(' ');
130: flags &= ~ ARG_SPACE;
131: }
132:
1.21 ! schwarze 133: /* For XML entities, skip escaping. */
! 134:
! 135: if (flags & ARG_RAW) {
! 136: fputs(arg, stdout);
! 137: break;
! 138: }
! 139:
1.1 schwarze 140: /* Escape us if we look like a macro. */
141:
1.12 schwarze 142: if ((flags & (ARG_QUOTED | ARG_UPPER)) == 0 &&
1.1 schwarze 143: (cp == arg || isspace((unsigned char)cp[-1])) &&
144: isupper((unsigned char)cp[0]) &&
145: islower((unsigned char)cp[1]) &&
146: (cp[2] == '\0' || cp[2] == ' ' ||
1.12 schwarze 147: ((cp[3] == '\0' || cp[3] == ' ') &&
148: (strncmp(cp, "Brq", 3) == 0 ||
149: strncmp(cp, "Bro", 3) == 0 ||
150: strncmp(cp, "Brc", 3) == 0 ||
151: strncmp(cp, "Bsx", 3) == 0))))
1.1 schwarze 152: fputs("\\&", stdout);
153:
154: if (*cp == '"')
155: fputs("\\(dq", stdout);
156: else if (flags & ARG_UPPER)
157: putchar(toupper((unsigned char)*cp));
158: else
159: putchar(*cp);
160: if (*cp == '\\')
161: putchar('e');
162: }
1.13 schwarze 163: if (quote_now)
164: putchar('"');
1.14 schwarze 165: f->parastate = PARA_MID;
1.1 schwarze 166: }
167:
168: void
169: macro_argline(struct format *f, const char *name, const char *arg)
170: {
171: macro_open(f, name);
172: macro_addarg(f, arg, ARG_SPACE);
173: macro_close(f);
174: }
175:
176: /*
177: * Recursively append text from the children of a node to a macro line.
178: */
179: void
1.8 schwarze 180: macro_addnode(struct format *f, struct pnode *n, int flags)
1.1 schwarze 181: {
1.5 schwarze 182: struct pnode *nc;
1.15 schwarze 183: int is_text, quote_now;
1.1 schwarze 184:
185: assert(f->linestate == LINE_MACRO);
186:
187: /*
1.2 schwarze 188: * If this node or its only child is a text node, just add
189: * that text, letting macro_addarg() decide about quoting.
1.1 schwarze 190: */
191:
1.8 schwarze 192: while ((nc = TAILQ_FIRST(&n->childq)) != NULL &&
1.5 schwarze 193: TAILQ_NEXT(nc, child) == NULL)
1.8 schwarze 194: n = nc;
1.5 schwarze 195:
1.21 ! schwarze 196: switch (n->node) {
! 197: case NODE_ESCAPE:
! 198: flags |= ARG_RAW;
! 199: /* FALLTHROUGH */
! 200: case NODE_TEXT:
1.8 schwarze 201: macro_addarg(f, n->b, flags);
1.14 schwarze 202: f->parastate = PARA_MID;
1.1 schwarze 203: return;
1.21 ! schwarze 204: default:
! 205: break;
1.1 schwarze 206: }
207:
208: /*
209: * If we want the argument quoted and are not already
210: * in a quoted context, quote now.
211: */
212:
213: quote_now = 0;
214: if (flags & ARG_SINGLE) {
215: if ((flags & ARG_QUOTED) == 0) {
216: if (flags & ARG_SPACE) {
217: putchar(' ');
218: flags &= ~ARG_SPACE;
219: }
220: putchar('"');
221: flags |= ARG_QUOTED;
222: quote_now = 1;
223: }
224: flags &= ~ARG_SINGLE;
225: }
226:
227: /*
228: * Iterate to child and sibling nodes,
229: * inserting whitespace between nodes.
230: */
231:
1.5 schwarze 232: while (nc != NULL) {
233: macro_addnode(f, nc, flags);
1.15 schwarze 234: is_text = pnode_class(nc->node) == CLASS_TEXT;
1.5 schwarze 235: nc = TAILQ_NEXT(nc, child);
1.15 schwarze 236: if (nc == NULL || pnode_class(nc->node) != CLASS_TEXT)
237: is_text = 0;
1.16 schwarze 238: if (is_text && (nc->flags & NFLAG_SPC) == 0)
1.15 schwarze 239: flags &= ~ARG_SPACE;
240: else
241: flags |= ARG_SPACE;
1.1 schwarze 242: }
243: if (quote_now)
244: putchar('"');
1.14 schwarze 245: f->parastate = PARA_MID;
1.1 schwarze 246: }
247:
248: void
1.8 schwarze 249: macro_nodeline(struct format *f, const char *name, struct pnode *n, int flags)
1.1 schwarze 250: {
251: macro_open(f, name);
1.8 schwarze 252: macro_addnode(f, n, ARG_SPACE | flags);
1.1 schwarze 253: macro_close(f);
1.2 schwarze 254: }
255:
256:
257: /*
258: * Print a word on the current text line if one is open, or on a new text
259: * line otherwise. The flag ARG_SPACE inserts spaces between words.
260: */
261: void
1.10 schwarze 262: print_text(struct format *f, const char *word, int flags)
263: {
1.20 schwarze 264: int ateos, inword;
265:
1.18 schwarze 266: para_check(f);
1.2 schwarze 267: switch (f->linestate) {
268: case LINE_NEW:
269: break;
270: case LINE_TEXT:
271: if (flags & ARG_SPACE)
272: putchar(' ');
273: break;
274: case LINE_MACRO:
275: macro_close(f);
276: break;
277: }
1.11 schwarze 278: if (f->linestate == LINE_NEW && (*word == '.' || *word == '\''))
279: fputs("\\&", stdout);
1.20 schwarze 280: ateos = inword = 0;
1.10 schwarze 281: while (*word != '\0') {
1.20 schwarze 282: if (f->nofill == 0) {
283: switch (*word) {
284: case ' ':
285: if (ateos == 0) {
286: inword = 0;
287: break;
288: }
289: ateos = inword = 0;
290: /* Handle the end of a sentence. */
291: while (*word == ' ')
292: word++;
293: switch (*word) {
294: case '\0':
295: break;
296: case '\'':
297: case '.':
298: fputs("\n\\&", stdout);
299: break;
300: default:
301: putchar('\n');
302: break;
303: }
304: continue;
305: /* Detect the end of a sentence. */
306: case '!':
307: case '.':
308: case '?':
309: if (inword > 1 &&
310: (word[-2] != 'n' || word[-1] != 'c') &&
311: (word[-2] != 'v' || word[-1] != 's'))
312: ateos = 1;
313: /* FALLTHROUGH */
314: case '"':
315: case '\'':
316: case ')':
317: case ']':
318: inword = 0;
319: break;
320: default:
321: if (isalnum((unsigned char)*word))
322: inword++;
323: ateos = 0;
324: break;
325: }
326: }
1.10 schwarze 327: putchar(*word);
328: if (*word++ == '\\')
329: putchar('e');
330: }
1.2 schwarze 331: f->linestate = LINE_TEXT;
1.14 schwarze 332: f->parastate = PARA_MID;
1.9 schwarze 333: f->flags = 0;
1.2 schwarze 334: }
335:
336: /*
337: * Recursively print the content of a node on a text line.
338: */
339: void
340: print_textnode(struct format *f, struct pnode *n)
341: {
342: struct pnode *nc;
343:
1.3 schwarze 344: if (n->node == NODE_TEXT || n->node == NODE_ESCAPE)
1.2 schwarze 345: print_text(f, n->b, ARG_SPACE);
346: else
347: TAILQ_FOREACH(nc, &n->childq, child)
348: print_textnode(f, nc);
1.1 schwarze 349: }
CVSweb