]> git.cameronkatri.com Git - mandoc.git/blob - mdoc.c
Delete the wrapper functions mdoc_meta(), man_meta(), mdoc_node(),
[mandoc.git] / mdoc.c
1 /* $Id: mdoc.c,v 1.246 2015/04/18 17:53:21 schwarze Exp $ */
2 /*
3 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010, 2012-2015 Ingo Schwarze <schwarze@openbsd.org>
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 "config.h"
19
20 #include <sys/types.h>
21
22 #include <assert.h>
23 #include <ctype.h>
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <time.h>
29
30 #include "mandoc_aux.h"
31 #include "mandoc.h"
32 #include "roff.h"
33 #include "mdoc.h"
34 #include "libmandoc.h"
35 #include "libmdoc.h"
36
37 const char *const __mdoc_macronames[MDOC_MAX + 1] = {
38 "Ap", "Dd", "Dt", "Os",
39 "Sh", "Ss", "Pp", "D1",
40 "Dl", "Bd", "Ed", "Bl",
41 "El", "It", "Ad", "An",
42 "Ar", "Cd", "Cm", "Dv",
43 "Er", "Ev", "Ex", "Fa",
44 "Fd", "Fl", "Fn", "Ft",
45 "Ic", "In", "Li", "Nd",
46 "Nm", "Op", "Ot", "Pa",
47 "Rv", "St", "Va", "Vt",
48 "Xr", "%A", "%B", "%D",
49 "%I", "%J", "%N", "%O",
50 "%P", "%R", "%T", "%V",
51 "Ac", "Ao", "Aq", "At",
52 "Bc", "Bf", "Bo", "Bq",
53 "Bsx", "Bx", "Db", "Dc",
54 "Do", "Dq", "Ec", "Ef",
55 "Em", "Eo", "Fx", "Ms",
56 "No", "Ns", "Nx", "Ox",
57 "Pc", "Pf", "Po", "Pq",
58 "Qc", "Ql", "Qo", "Qq",
59 "Re", "Rs", "Sc", "So",
60 "Sq", "Sm", "Sx", "Sy",
61 "Tn", "Ux", "Xc", "Xo",
62 "Fo", "Fc", "Oo", "Oc",
63 "Bk", "Ek", "Bt", "Hf",
64 "Fr", "Ud", "Lb", "Lp",
65 "Lk", "Mt", "Brq", "Bro",
66 "Brc", "%C", "Es", "En",
67 "Dx", "%Q", "br", "sp",
68 "%U", "Ta", "ll", "text",
69 };
70
71 const char *const __mdoc_argnames[MDOC_ARG_MAX] = {
72 "split", "nosplit", "ragged",
73 "unfilled", "literal", "file",
74 "offset", "bullet", "dash",
75 "hyphen", "item", "enum",
76 "tag", "diag", "hang",
77 "ohang", "inset", "column",
78 "width", "compact", "std",
79 "filled", "words", "emphasis",
80 "symbolic", "nested", "centered"
81 };
82
83 const char * const *mdoc_macronames = __mdoc_macronames;
84 const char * const *mdoc_argnames = __mdoc_argnames;
85
86 static void mdoc_node_free(struct roff_node *);
87 static void mdoc_node_unlink(struct roff_man *,
88 struct roff_node *);
89 static struct roff_node *node_alloc(struct roff_man *, int, int,
90 int, enum roff_type);
91 static void node_append(struct roff_man *, struct roff_node *);
92 static int mdoc_ptext(struct roff_man *, int, char *, int);
93 static int mdoc_pmacro(struct roff_man *, int, char *, int);
94
95
96 void
97 mdoc_endparse(struct roff_man *mdoc)
98 {
99
100 mdoc_macroend(mdoc);
101 }
102
103 void
104 mdoc_addeqn(struct roff_man *mdoc, const struct eqn *ep)
105 {
106 struct roff_node *n;
107
108 n = node_alloc(mdoc, ep->ln, ep->pos, MDOC_MAX, ROFFT_EQN);
109 n->eqn = ep;
110 if (ep->ln > mdoc->last->line)
111 n->flags |= MDOC_LINE;
112 node_append(mdoc, n);
113 mdoc->next = ROFF_NEXT_SIBLING;
114 }
115
116 void
117 mdoc_addspan(struct roff_man *mdoc, const struct tbl_span *sp)
118 {
119 struct roff_node *n;
120
121 n = node_alloc(mdoc, sp->line, 0, MDOC_MAX, ROFFT_TBL);
122 n->span = sp;
123 node_append(mdoc, n);
124 mdoc->next = ROFF_NEXT_SIBLING;
125 }
126
127 /*
128 * Main parse routine. Parses a single line -- really just hands off to
129 * the macro (mdoc_pmacro()) or text parser (mdoc_ptext()).
130 */
131 int
132 mdoc_parseln(struct roff_man *mdoc, int ln, char *buf, int offs)
133 {
134
135 if (mdoc->last->type != ROFFT_EQN || ln > mdoc->last->line)
136 mdoc->flags |= MDOC_NEWLINE;
137
138 /*
139 * Let the roff nS register switch SYNOPSIS mode early,
140 * such that the parser knows at all times
141 * whether this mode is on or off.
142 * Note that this mode is also switched by the Sh macro.
143 */
144 if (roff_getreg(mdoc->roff, "nS"))
145 mdoc->flags |= MDOC_SYNOPSIS;
146 else
147 mdoc->flags &= ~MDOC_SYNOPSIS;
148
149 return(roff_getcontrol(mdoc->roff, buf, &offs) ?
150 mdoc_pmacro(mdoc, ln, buf, offs) :
151 mdoc_ptext(mdoc, ln, buf, offs));
152 }
153
154 void
155 mdoc_macro(MACRO_PROT_ARGS)
156 {
157 assert(tok < MDOC_MAX);
158
159 if (mdoc->flags & MDOC_PBODY) {
160 if (tok == MDOC_Dt) {
161 mandoc_vmsg(MANDOCERR_DT_LATE,
162 mdoc->parse, line, ppos,
163 "Dt %s", buf + *pos);
164 return;
165 }
166 } else if ( ! (mdoc_macros[tok].flags & MDOC_PROLOGUE)) {
167 if (mdoc->meta.title == NULL) {
168 mandoc_vmsg(MANDOCERR_DT_NOTITLE,
169 mdoc->parse, line, ppos, "%s %s",
170 mdoc_macronames[tok], buf + *pos);
171 mdoc->meta.title = mandoc_strdup("UNTITLED");
172 }
173 if (NULL == mdoc->meta.vol)
174 mdoc->meta.vol = mandoc_strdup("LOCAL");
175 mdoc->flags |= MDOC_PBODY;
176 }
177 (*mdoc_macros[tok].fp)(mdoc, tok, line, ppos, pos, buf);
178 }
179
180
181 static void
182 node_append(struct roff_man *mdoc, struct roff_node *p)
183 {
184
185 assert(mdoc->last);
186 assert(mdoc->first);
187 assert(p->type != ROFFT_ROOT);
188
189 switch (mdoc->next) {
190 case ROFF_NEXT_SIBLING:
191 mdoc->last->next = p;
192 p->prev = mdoc->last;
193 p->parent = mdoc->last->parent;
194 break;
195 case ROFF_NEXT_CHILD:
196 mdoc->last->child = p;
197 p->parent = mdoc->last;
198 break;
199 default:
200 abort();
201 /* NOTREACHED */
202 }
203
204 p->parent->nchild++;
205
206 /*
207 * Copy over the normalised-data pointer of our parent. Not
208 * everybody has one, but copying a null pointer is fine.
209 */
210
211 switch (p->type) {
212 case ROFFT_BODY:
213 if (ENDBODY_NOT != p->end)
214 break;
215 /* FALLTHROUGH */
216 case ROFFT_TAIL:
217 /* FALLTHROUGH */
218 case ROFFT_HEAD:
219 p->norm = p->parent->norm;
220 break;
221 default:
222 break;
223 }
224
225 mdoc_valid_pre(mdoc, p);
226
227 switch (p->type) {
228 case ROFFT_HEAD:
229 assert(p->parent->type == ROFFT_BLOCK);
230 p->parent->head = p;
231 break;
232 case ROFFT_TAIL:
233 assert(p->parent->type == ROFFT_BLOCK);
234 p->parent->tail = p;
235 break;
236 case ROFFT_BODY:
237 if (p->end)
238 break;
239 assert(p->parent->type == ROFFT_BLOCK);
240 p->parent->body = p;
241 break;
242 default:
243 break;
244 }
245
246 mdoc->last = p;
247
248 switch (p->type) {
249 case ROFFT_TBL:
250 /* FALLTHROUGH */
251 case ROFFT_TEXT:
252 mdoc_valid_post(mdoc);
253 break;
254 default:
255 break;
256 }
257 }
258
259 static struct roff_node *
260 node_alloc(struct roff_man *mdoc, int line, int pos,
261 int tok, enum roff_type type)
262 {
263 struct roff_node *p;
264
265 p = mandoc_calloc(1, sizeof(*p));
266 p->sec = mdoc->lastsec;
267 p->line = line;
268 p->pos = pos;
269 p->tok = tok;
270 p->type = type;
271
272 /* Flag analysis. */
273
274 if (MDOC_SYNOPSIS & mdoc->flags)
275 p->flags |= MDOC_SYNPRETTY;
276 else
277 p->flags &= ~MDOC_SYNPRETTY;
278 if (MDOC_NEWLINE & mdoc->flags)
279 p->flags |= MDOC_LINE;
280 mdoc->flags &= ~MDOC_NEWLINE;
281
282 return(p);
283 }
284
285 void
286 mdoc_tail_alloc(struct roff_man *mdoc, int line, int pos, int tok)
287 {
288 struct roff_node *p;
289
290 p = node_alloc(mdoc, line, pos, tok, ROFFT_TAIL);
291 node_append(mdoc, p);
292 mdoc->next = ROFF_NEXT_CHILD;
293 }
294
295 struct roff_node *
296 mdoc_head_alloc(struct roff_man *mdoc, int line, int pos, int tok)
297 {
298 struct roff_node *p;
299
300 assert(mdoc->first);
301 assert(mdoc->last);
302 p = node_alloc(mdoc, line, pos, tok, ROFFT_HEAD);
303 node_append(mdoc, p);
304 mdoc->next = ROFF_NEXT_CHILD;
305 return(p);
306 }
307
308 struct roff_node *
309 mdoc_body_alloc(struct roff_man *mdoc, int line, int pos, int tok)
310 {
311 struct roff_node *p;
312
313 p = node_alloc(mdoc, line, pos, tok, ROFFT_BODY);
314 node_append(mdoc, p);
315 mdoc->next = ROFF_NEXT_CHILD;
316 return(p);
317 }
318
319 struct roff_node *
320 mdoc_endbody_alloc(struct roff_man *mdoc, int line, int pos, int tok,
321 struct roff_node *body, enum mdoc_endbody end)
322 {
323 struct roff_node *p;
324
325 body->flags |= MDOC_ENDED;
326 body->parent->flags |= MDOC_ENDED;
327 p = node_alloc(mdoc, line, pos, tok, ROFFT_BODY);
328 p->body = body;
329 p->norm = body->norm;
330 p->end = end;
331 node_append(mdoc, p);
332 mdoc->next = ROFF_NEXT_SIBLING;
333 return(p);
334 }
335
336 struct roff_node *
337 mdoc_block_alloc(struct roff_man *mdoc, int line, int pos,
338 int tok, struct mdoc_arg *args)
339 {
340 struct roff_node *p;
341
342 p = node_alloc(mdoc, line, pos, tok, ROFFT_BLOCK);
343 p->args = args;
344 if (p->args)
345 (args->refcnt)++;
346
347 switch (tok) {
348 case MDOC_Bd:
349 /* FALLTHROUGH */
350 case MDOC_Bf:
351 /* FALLTHROUGH */
352 case MDOC_Bl:
353 /* FALLTHROUGH */
354 case MDOC_En:
355 /* FALLTHROUGH */
356 case MDOC_Rs:
357 p->norm = mandoc_calloc(1, sizeof(union mdoc_data));
358 break;
359 default:
360 break;
361 }
362 node_append(mdoc, p);
363 mdoc->next = ROFF_NEXT_CHILD;
364 return(p);
365 }
366
367 void
368 mdoc_elem_alloc(struct roff_man *mdoc, int line, int pos,
369 int tok, struct mdoc_arg *args)
370 {
371 struct roff_node *p;
372
373 p = node_alloc(mdoc, line, pos, tok, ROFFT_ELEM);
374 p->args = args;
375 if (p->args)
376 (args->refcnt)++;
377
378 switch (tok) {
379 case MDOC_An:
380 p->norm = mandoc_calloc(1, sizeof(union mdoc_data));
381 break;
382 default:
383 break;
384 }
385 node_append(mdoc, p);
386 mdoc->next = ROFF_NEXT_CHILD;
387 }
388
389 void
390 mdoc_word_alloc(struct roff_man *mdoc, int line, int pos, const char *p)
391 {
392 struct roff_node *n;
393
394 n = node_alloc(mdoc, line, pos, MDOC_MAX, ROFFT_TEXT);
395 n->string = roff_strdup(mdoc->roff, p);
396 node_append(mdoc, n);
397 mdoc->next = ROFF_NEXT_SIBLING;
398 }
399
400 void
401 mdoc_word_append(struct roff_man *mdoc, const char *p)
402 {
403 struct roff_node *n;
404 char *addstr, *newstr;
405
406 n = mdoc->last;
407 addstr = roff_strdup(mdoc->roff, p);
408 mandoc_asprintf(&newstr, "%s %s", n->string, addstr);
409 free(addstr);
410 free(n->string);
411 n->string = newstr;
412 mdoc->next = ROFF_NEXT_SIBLING;
413 }
414
415 static void
416 mdoc_node_free(struct roff_node *p)
417 {
418
419 if (p->type == ROFFT_BLOCK || p->type == ROFFT_ELEM)
420 free(p->norm);
421 if (p->string)
422 free(p->string);
423 if (p->args)
424 mdoc_argv_free(p->args);
425 free(p);
426 }
427
428 static void
429 mdoc_node_unlink(struct roff_man *mdoc, struct roff_node *n)
430 {
431
432 /* Adjust siblings. */
433
434 if (n->prev)
435 n->prev->next = n->next;
436 if (n->next)
437 n->next->prev = n->prev;
438
439 /* Adjust parent. */
440
441 if (n->parent) {
442 n->parent->nchild--;
443 if (n->parent->child == n)
444 n->parent->child = n->prev ? n->prev : n->next;
445 if (n->parent->last == n)
446 n->parent->last = n->prev ? n->prev : NULL;
447 }
448
449 /* Adjust parse point, if applicable. */
450
451 if (mdoc && mdoc->last == n) {
452 if (n->prev) {
453 mdoc->last = n->prev;
454 mdoc->next = ROFF_NEXT_SIBLING;
455 } else {
456 mdoc->last = n->parent;
457 mdoc->next = ROFF_NEXT_CHILD;
458 }
459 }
460
461 if (mdoc && mdoc->first == n)
462 mdoc->first = NULL;
463 }
464
465 void
466 mdoc_node_delete(struct roff_man *mdoc, struct roff_node *p)
467 {
468
469 while (p->child) {
470 assert(p->nchild);
471 mdoc_node_delete(mdoc, p->child);
472 }
473 assert(0 == p->nchild);
474
475 mdoc_node_unlink(mdoc, p);
476 mdoc_node_free(p);
477 }
478
479 void
480 mdoc_node_relink(struct roff_man *mdoc, struct roff_node *p)
481 {
482
483 mdoc_node_unlink(mdoc, p);
484 node_append(mdoc, p);
485 }
486
487 /*
488 * Parse free-form text, that is, a line that does not begin with the
489 * control character.
490 */
491 static int
492 mdoc_ptext(struct roff_man *mdoc, int line, char *buf, int offs)
493 {
494 struct roff_node *n;
495 char *c, *ws, *end;
496
497 assert(mdoc->last);
498 n = mdoc->last;
499
500 /*
501 * Divert directly to list processing if we're encountering a
502 * columnar ROFFT_BLOCK with or without a prior ROFFT_BLOCK entry
503 * (a ROFFT_BODY means it's already open, in which case we should
504 * process within its context in the normal way).
505 */
506
507 if (n->tok == MDOC_Bl && n->type == ROFFT_BODY &&
508 n->end == ENDBODY_NOT && n->norm->Bl.type == LIST_column) {
509 /* `Bl' is open without any children. */
510 mdoc->flags |= MDOC_FREECOL;
511 mdoc_macro(mdoc, MDOC_It, line, offs, &offs, buf);
512 return(1);
513 }
514
515 if (n->tok == MDOC_It && n->type == ROFFT_BLOCK &&
516 NULL != n->parent &&
517 MDOC_Bl == n->parent->tok &&
518 LIST_column == n->parent->norm->Bl.type) {
519 /* `Bl' has block-level `It' children. */
520 mdoc->flags |= MDOC_FREECOL;
521 mdoc_macro(mdoc, MDOC_It, line, offs, &offs, buf);
522 return(1);
523 }
524
525 /*
526 * Search for the beginning of unescaped trailing whitespace (ws)
527 * and for the first character not to be output (end).
528 */
529
530 /* FIXME: replace with strcspn(). */
531 ws = NULL;
532 for (c = end = buf + offs; *c; c++) {
533 switch (*c) {
534 case ' ':
535 if (NULL == ws)
536 ws = c;
537 continue;
538 case '\t':
539 /*
540 * Always warn about trailing tabs,
541 * even outside literal context,
542 * where they should be put on the next line.
543 */
544 if (NULL == ws)
545 ws = c;
546 /*
547 * Strip trailing tabs in literal context only;
548 * outside, they affect the next line.
549 */
550 if (MDOC_LITERAL & mdoc->flags)
551 continue;
552 break;
553 case '\\':
554 /* Skip the escaped character, too, if any. */
555 if (c[1])
556 c++;
557 /* FALLTHROUGH */
558 default:
559 ws = NULL;
560 break;
561 }
562 end = c + 1;
563 }
564 *end = '\0';
565
566 if (ws)
567 mandoc_msg(MANDOCERR_SPACE_EOL, mdoc->parse,
568 line, (int)(ws-buf), NULL);
569
570 if (buf[offs] == '\0' && ! (mdoc->flags & MDOC_LITERAL)) {
571 mandoc_msg(MANDOCERR_FI_BLANK, mdoc->parse,
572 line, (int)(c - buf), NULL);
573
574 /*
575 * Insert a `sp' in the case of a blank line. Technically,
576 * blank lines aren't allowed, but enough manuals assume this
577 * behaviour that we want to work around it.
578 */
579 mdoc_elem_alloc(mdoc, line, offs, MDOC_sp, NULL);
580 mdoc->next = ROFF_NEXT_SIBLING;
581 mdoc_valid_post(mdoc);
582 return(1);
583 }
584
585 mdoc_word_alloc(mdoc, line, offs, buf+offs);
586
587 if (mdoc->flags & MDOC_LITERAL)
588 return(1);
589
590 /*
591 * End-of-sentence check. If the last character is an unescaped
592 * EOS character, then flag the node as being the end of a
593 * sentence. The front-end will know how to interpret this.
594 */
595
596 assert(buf < end);
597
598 if (mandoc_eos(buf+offs, (size_t)(end-buf-offs)))
599 mdoc->last->flags |= MDOC_EOS;
600 return(1);
601 }
602
603 /*
604 * Parse a macro line, that is, a line beginning with the control
605 * character.
606 */
607 static int
608 mdoc_pmacro(struct roff_man *mdoc, int ln, char *buf, int offs)
609 {
610 struct roff_node *n;
611 const char *cp;
612 int tok;
613 int i, sv;
614 char mac[5];
615
616 sv = offs;
617
618 /*
619 * Copy the first word into a nil-terminated buffer.
620 * Stop when a space, tab, escape, or eoln is encountered.
621 */
622
623 i = 0;
624 while (i < 4 && strchr(" \t\\", buf[offs]) == NULL)
625 mac[i++] = buf[offs++];
626
627 mac[i] = '\0';
628
629 tok = (i > 1 && i < 4) ? mdoc_hash_find(mac) : MDOC_MAX;
630
631 if (tok == MDOC_MAX) {
632 mandoc_msg(MANDOCERR_MACRO, mdoc->parse,
633 ln, sv, buf + sv - 1);
634 return(1);
635 }
636
637 /* Skip a leading escape sequence or tab. */
638
639 switch (buf[offs]) {
640 case '\\':
641 cp = buf + offs + 1;
642 mandoc_escape(&cp, NULL, NULL);
643 offs = cp - buf;
644 break;
645 case '\t':
646 offs++;
647 break;
648 default:
649 break;
650 }
651
652 /* Jump to the next non-whitespace word. */
653
654 while (buf[offs] && ' ' == buf[offs])
655 offs++;
656
657 /*
658 * Trailing whitespace. Note that tabs are allowed to be passed
659 * into the parser as "text", so we only warn about spaces here.
660 */
661
662 if ('\0' == buf[offs] && ' ' == buf[offs - 1])
663 mandoc_msg(MANDOCERR_SPACE_EOL, mdoc->parse,
664 ln, offs - 1, NULL);
665
666 /*
667 * If an initial macro or a list invocation, divert directly
668 * into macro processing.
669 */
670
671 if (NULL == mdoc->last || MDOC_It == tok || MDOC_El == tok) {
672 mdoc_macro(mdoc, tok, ln, sv, &offs, buf);
673 return(1);
674 }
675
676 n = mdoc->last;
677 assert(mdoc->last);
678
679 /*
680 * If the first macro of a `Bl -column', open an `It' block
681 * context around the parsed macro.
682 */
683
684 if (n->tok == MDOC_Bl && n->type == ROFFT_BODY &&
685 n->end == ENDBODY_NOT && n->norm->Bl.type == LIST_column) {
686 mdoc->flags |= MDOC_FREECOL;
687 mdoc_macro(mdoc, MDOC_It, ln, sv, &sv, buf);
688 return(1);
689 }
690
691 /*
692 * If we're following a block-level `It' within a `Bl -column'
693 * context (perhaps opened in the above block or in ptext()),
694 * then open an `It' block context around the parsed macro.
695 */
696
697 if (n->tok == MDOC_It && n->type == ROFFT_BLOCK &&
698 NULL != n->parent &&
699 MDOC_Bl == n->parent->tok &&
700 LIST_column == n->parent->norm->Bl.type) {
701 mdoc->flags |= MDOC_FREECOL;
702 mdoc_macro(mdoc, MDOC_It, ln, sv, &sv, buf);
703 return(1);
704 }
705
706 /* Normal processing of a macro. */
707
708 mdoc_macro(mdoc, tok, ln, sv, &offs, buf);
709
710 /* In quick mode (for mandocdb), abort after the NAME section. */
711
712 if (mdoc->quick && MDOC_Sh == tok &&
713 SEC_NAME != mdoc->last->sec)
714 return(2);
715
716 return(1);
717 }
718
719 enum mdelim
720 mdoc_isdelim(const char *p)
721 {
722
723 if ('\0' == p[0])
724 return(DELIM_NONE);
725
726 if ('\0' == p[1])
727 switch (p[0]) {
728 case '(':
729 /* FALLTHROUGH */
730 case '[':
731 return(DELIM_OPEN);
732 case '|':
733 return(DELIM_MIDDLE);
734 case '.':
735 /* FALLTHROUGH */
736 case ',':
737 /* FALLTHROUGH */
738 case ';':
739 /* FALLTHROUGH */
740 case ':':
741 /* FALLTHROUGH */
742 case '?':
743 /* FALLTHROUGH */
744 case '!':
745 /* FALLTHROUGH */
746 case ')':
747 /* FALLTHROUGH */
748 case ']':
749 return(DELIM_CLOSE);
750 default:
751 return(DELIM_NONE);
752 }
753
754 if ('\\' != p[0])
755 return(DELIM_NONE);
756
757 if (0 == strcmp(p + 1, "."))
758 return(DELIM_CLOSE);
759 if (0 == strcmp(p + 1, "fR|\\fP"))
760 return(DELIM_MIDDLE);
761
762 return(DELIM_NONE);
763 }
764
765 void
766 mdoc_deroff(char **dest, const struct roff_node *n)
767 {
768 char *cp;
769 size_t sz;
770
771 if (n->type != ROFFT_TEXT) {
772 for (n = n->child; n; n = n->next)
773 mdoc_deroff(dest, n);
774 return;
775 }
776
777 /* Skip leading whitespace. */
778
779 for (cp = n->string; '\0' != *cp; cp++)
780 if (0 == isspace((unsigned char)*cp))
781 break;
782
783 /* Skip trailing whitespace. */
784
785 for (sz = strlen(cp); sz; sz--)
786 if (0 == isspace((unsigned char)cp[sz-1]))
787 break;
788
789 /* Skip empty strings. */
790
791 if (0 == sz)
792 return;
793
794 if (NULL == *dest) {
795 *dest = mandoc_strndup(cp, sz);
796 return;
797 }
798
799 mandoc_asprintf(&cp, "%s %*s", *dest, (int)sz, cp);
800 free(*dest);
801 *dest = cp;
802 }