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