1 /* $OpenBSD: html.c,v 1.121 2019/01/11 12:44:10 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2011-2015, 2017-2019 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 #include <sys/stat.h> 20 21 #include <assert.h> 22 #include <ctype.h> 23 #include <stdarg.h> 24 #include <stddef.h> 25 #include <stdio.h> 26 #include <stdint.h> 27 #include <stdlib.h> 28 #include <string.h> 29 #include <unistd.h> 30 31 #include "mandoc_aux.h" 32 #include "mandoc_ohash.h" 33 #include "mandoc.h" 34 #include "roff.h" 35 #include "out.h" 36 #include "html.h" 37 #include "manconf.h" 38 #include "main.h" 39 40 struct htmldata { 41 const char *name; 42 int flags; 43 #define HTML_NOSTACK (1 << 0) 44 #define HTML_AUTOCLOSE (1 << 1) 45 #define HTML_NLBEFORE (1 << 2) 46 #define HTML_NLBEGIN (1 << 3) 47 #define HTML_NLEND (1 << 4) 48 #define HTML_NLAFTER (1 << 5) 49 #define HTML_NLAROUND (HTML_NLBEFORE | HTML_NLAFTER) 50 #define HTML_NLINSIDE (HTML_NLBEGIN | HTML_NLEND) 51 #define HTML_NLALL (HTML_NLAROUND | HTML_NLINSIDE) 52 #define HTML_INDENT (1 << 6) 53 #define HTML_NOINDENT (1 << 7) 54 }; 55 56 static const struct htmldata htmltags[TAG_MAX] = { 57 {"html", HTML_NLALL}, 58 {"head", HTML_NLALL | HTML_INDENT}, 59 {"body", HTML_NLALL}, 60 {"meta", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, 61 {"title", HTML_NLAROUND}, 62 {"div", HTML_NLAROUND}, 63 {"div", 0}, 64 {"h1", HTML_NLAROUND}, 65 {"h2", HTML_NLAROUND}, 66 {"span", 0}, 67 {"link", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, 68 {"br", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, 69 {"a", 0}, 70 {"table", HTML_NLALL | HTML_INDENT}, 71 {"tr", HTML_NLALL | HTML_INDENT}, 72 {"td", HTML_NLAROUND}, 73 {"li", HTML_NLAROUND | HTML_INDENT}, 74 {"ul", HTML_NLALL | HTML_INDENT}, 75 {"ol", HTML_NLALL | HTML_INDENT}, 76 {"dl", HTML_NLALL | HTML_INDENT}, 77 {"dt", HTML_NLAROUND}, 78 {"dd", HTML_NLAROUND | HTML_INDENT}, 79 {"p", HTML_NLAROUND | HTML_INDENT}, 80 {"pre", HTML_NLALL | HTML_NOINDENT}, 81 {"var", 0}, 82 {"cite", 0}, 83 {"b", 0}, 84 {"i", 0}, 85 {"code", 0}, 86 {"small", 0}, 87 {"style", HTML_NLALL | HTML_INDENT}, 88 {"math", HTML_NLALL | HTML_INDENT}, 89 {"mrow", 0}, 90 {"mi", 0}, 91 {"mn", 0}, 92 {"mo", 0}, 93 {"msup", 0}, 94 {"msub", 0}, 95 {"msubsup", 0}, 96 {"mfrac", 0}, 97 {"msqrt", 0}, 98 {"mfenced", 0}, 99 {"mtable", 0}, 100 {"mtr", 0}, 101 {"mtd", 0}, 102 {"munderover", 0}, 103 {"munder", 0}, 104 {"mover", 0}, 105 }; 106 107 /* Avoid duplicate HTML id= attributes. */ 108 static struct ohash id_unique; 109 110 static void print_byte(struct html *, char); 111 static void print_endword(struct html *); 112 static void print_indent(struct html *); 113 static void print_word(struct html *, const char *); 114 115 static void print_ctag(struct html *, struct tag *); 116 static int print_escape(struct html *, char); 117 static int print_encode(struct html *, const char *, const char *, int); 118 static void print_href(struct html *, const char *, const char *, int); 119 120 121 void * 122 html_alloc(const struct manoutput *outopts) 123 { 124 struct html *h; 125 126 h = mandoc_calloc(1, sizeof(struct html)); 127 128 h->tag = NULL; 129 h->style = outopts->style; 130 if ((h->base_man1 = outopts->man) == NULL) 131 h->base_man2 = NULL; 132 else if ((h->base_man2 = strchr(h->base_man1, ';')) != NULL) 133 *h->base_man2++ = '\0'; 134 h->base_includes = outopts->includes; 135 if (outopts->fragment) 136 h->oflags |= HTML_FRAGMENT; 137 if (outopts->toc) 138 h->oflags |= HTML_TOC; 139 140 mandoc_ohash_init(&id_unique, 4, 0); 141 142 return h; 143 } 144 145 void 146 html_free(void *p) 147 { 148 struct tag *tag; 149 struct html *h; 150 char *cp; 151 unsigned int slot; 152 153 h = (struct html *)p; 154 while ((tag = h->tag) != NULL) { 155 h->tag = tag->next; 156 free(tag); 157 } 158 free(h); 159 160 cp = ohash_first(&id_unique, &slot); 161 while (cp != NULL) { 162 free(cp); 163 cp = ohash_next(&id_unique, &slot); 164 } 165 ohash_delete(&id_unique); 166 } 167 168 void 169 print_gen_head(struct html *h) 170 { 171 struct tag *t; 172 173 print_otag(h, TAG_META, "?", "charset", "utf-8"); 174 if (h->style != NULL) { 175 print_otag(h, TAG_LINK, "?h??", "rel", "stylesheet", 176 h->style, "type", "text/css", "media", "all"); 177 return; 178 } 179 180 /* 181 * Print a minimal embedded style sheet. 182 */ 183 184 t = print_otag(h, TAG_STYLE, ""); 185 print_text(h, "table.head, table.foot { width: 100%; }"); 186 print_endline(h); 187 print_text(h, "td.head-rtitle, td.foot-os { text-align: right; }"); 188 print_endline(h); 189 print_text(h, "td.head-vol { text-align: center; }"); 190 print_endline(h); 191 print_text(h, "div.Pp { margin: 1ex 0ex; }"); 192 print_endline(h); 193 print_text(h, "div.Nd, div.Bf, div.Op { display: inline; }"); 194 print_endline(h); 195 print_text(h, "span.Pa, span.Ad { font-style: italic; }"); 196 print_endline(h); 197 print_text(h, "span.Ms { font-weight: bold; }"); 198 print_endline(h); 199 print_text(h, "dl.Bl-diag "); 200 print_byte(h, '>'); 201 print_text(h, " dt { font-weight: bold; }"); 202 print_endline(h); 203 print_text(h, "code.Nm, code.Fl, code.Cm, code.Ic, " 204 "code.In, code.Fd, code.Fn,"); 205 print_endline(h); 206 print_text(h, "code.Cd { font-weight: bold; " 207 "font-family: inherit; }"); 208 print_tagq(h, t); 209 } 210 211 void 212 print_metaf(struct html *h, enum mandoc_esc deco) 213 { 214 enum htmlfont font; 215 216 switch (deco) { 217 case ESCAPE_FONTPREV: 218 font = h->metal; 219 break; 220 case ESCAPE_FONTITALIC: 221 font = HTMLFONT_ITALIC; 222 break; 223 case ESCAPE_FONTBOLD: 224 font = HTMLFONT_BOLD; 225 break; 226 case ESCAPE_FONTBI: 227 font = HTMLFONT_BI; 228 break; 229 case ESCAPE_FONTCW: 230 font = HTMLFONT_CW; 231 break; 232 case ESCAPE_FONT: 233 case ESCAPE_FONTROMAN: 234 font = HTMLFONT_NONE; 235 break; 236 default: 237 return; 238 } 239 240 if (h->metaf) { 241 print_tagq(h, h->metaf); 242 h->metaf = NULL; 243 } 244 245 h->metal = h->metac; 246 h->metac = font; 247 248 switch (font) { 249 case HTMLFONT_ITALIC: 250 h->metaf = print_otag(h, TAG_I, ""); 251 break; 252 case HTMLFONT_BOLD: 253 h->metaf = print_otag(h, TAG_B, ""); 254 break; 255 case HTMLFONT_BI: 256 h->metaf = print_otag(h, TAG_B, ""); 257 print_otag(h, TAG_I, ""); 258 break; 259 case HTMLFONT_CW: 260 h->metaf = print_otag(h, TAG_SPAN, "c", "Li"); 261 break; 262 default: 263 break; 264 } 265 } 266 267 void 268 html_close_paragraph(struct html *h) 269 { 270 struct tag *t; 271 272 for (t = h->tag; t != NULL; t = t->next) { 273 if (t->tag == TAG_P || t->tag == TAG_PRE) { 274 print_tagq(h, t); 275 break; 276 } 277 } 278 } 279 280 /* 281 * ROFF_nf switches to no-fill mode, ROFF_fi to fill mode. 282 * TOKEN_NONE does not switch. The old mode is returned. 283 */ 284 enum roff_tok 285 html_fillmode(struct html *h, enum roff_tok want) 286 { 287 struct tag *t; 288 enum roff_tok had; 289 290 for (t = h->tag; t != NULL; t = t->next) 291 if (t->tag == TAG_PRE) 292 break; 293 294 had = t == NULL ? ROFF_fi : ROFF_nf; 295 296 if (want != had) { 297 switch (want) { 298 case ROFF_fi: 299 print_tagq(h, t); 300 break; 301 case ROFF_nf: 302 html_close_paragraph(h); 303 print_otag(h, TAG_PRE, ""); 304 break; 305 case TOKEN_NONE: 306 break; 307 default: 308 abort(); 309 } 310 } 311 return had; 312 } 313 314 char * 315 html_make_id(const struct roff_node *n, int unique) 316 { 317 const struct roff_node *nch; 318 char *buf, *bufs, *cp; 319 unsigned int slot; 320 int suffix; 321 322 for (nch = n->child; nch != NULL; nch = nch->next) 323 if (nch->type != ROFFT_TEXT) 324 return NULL; 325 326 buf = NULL; 327 deroff(&buf, n); 328 if (buf == NULL) 329 return NULL; 330 331 /* 332 * In ID attributes, only use ASCII characters that are 333 * permitted in URL-fragment strings according to the 334 * explicit list at: 335 * https://url.spec.whatwg.org/#url-fragment-string 336 */ 337 338 for (cp = buf; *cp != '\0'; cp++) 339 if (isalnum((unsigned char)*cp) == 0 && 340 strchr("!$&'()*+,-./:;=?@_~", *cp) == NULL) 341 *cp = '_'; 342 343 if (unique == 0) 344 return buf; 345 346 /* Avoid duplicate HTML id= attributes. */ 347 348 bufs = NULL; 349 suffix = 1; 350 slot = ohash_qlookup(&id_unique, buf); 351 cp = ohash_find(&id_unique, slot); 352 if (cp != NULL) { 353 while (cp != NULL) { 354 free(bufs); 355 if (++suffix > 127) { 356 free(buf); 357 return NULL; 358 } 359 mandoc_asprintf(&bufs, "%s_%d", buf, suffix); 360 slot = ohash_qlookup(&id_unique, bufs); 361 cp = ohash_find(&id_unique, slot); 362 } 363 free(buf); 364 buf = bufs; 365 } 366 ohash_insert(&id_unique, slot, buf); 367 return buf; 368 } 369 370 static int 371 print_escape(struct html *h, char c) 372 { 373 374 switch (c) { 375 case '<': 376 print_word(h, "<"); 377 break; 378 case '>': 379 print_word(h, ">"); 380 break; 381 case '&': 382 print_word(h, "&"); 383 break; 384 case '"': 385 print_word(h, """); 386 break; 387 case ASCII_NBRSP: 388 print_word(h, " "); 389 break; 390 case ASCII_HYPH: 391 print_byte(h, '-'); 392 break; 393 case ASCII_BREAK: 394 break; 395 default: 396 return 0; 397 } 398 return 1; 399 } 400 401 static int 402 print_encode(struct html *h, const char *p, const char *pend, int norecurse) 403 { 404 char numbuf[16]; 405 const char *seq; 406 size_t sz; 407 int c, len, breakline, nospace; 408 enum mandoc_esc esc; 409 static const char rejs[10] = { ' ', '\\', '<', '>', '&', '"', 410 ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' }; 411 412 if (pend == NULL) 413 pend = strchr(p, '\0'); 414 415 breakline = 0; 416 nospace = 0; 417 418 while (p < pend) { 419 if (HTML_SKIPCHAR & h->flags && '\\' != *p) { 420 h->flags &= ~HTML_SKIPCHAR; 421 p++; 422 continue; 423 } 424 425 for (sz = strcspn(p, rejs); sz-- && p < pend; p++) 426 print_byte(h, *p); 427 428 if (breakline && 429 (p >= pend || *p == ' ' || *p == ASCII_NBRSP)) { 430 print_otag(h, TAG_BR, ""); 431 breakline = 0; 432 while (p < pend && (*p == ' ' || *p == ASCII_NBRSP)) 433 p++; 434 continue; 435 } 436 437 if (p >= pend) 438 break; 439 440 if (*p == ' ') { 441 print_endword(h); 442 p++; 443 continue; 444 } 445 446 if (print_escape(h, *p++)) 447 continue; 448 449 esc = mandoc_escape(&p, &seq, &len); 450 switch (esc) { 451 case ESCAPE_FONT: 452 case ESCAPE_FONTPREV: 453 case ESCAPE_FONTBOLD: 454 case ESCAPE_FONTITALIC: 455 case ESCAPE_FONTBI: 456 case ESCAPE_FONTCW: 457 case ESCAPE_FONTROMAN: 458 if (0 == norecurse) { 459 h->flags |= HTML_NOSPACE; 460 print_metaf(h, esc); 461 h->flags &= ~HTML_NOSPACE; 462 } 463 continue; 464 case ESCAPE_SKIPCHAR: 465 h->flags |= HTML_SKIPCHAR; 466 continue; 467 case ESCAPE_ERROR: 468 continue; 469 default: 470 break; 471 } 472 473 if (h->flags & HTML_SKIPCHAR) { 474 h->flags &= ~HTML_SKIPCHAR; 475 continue; 476 } 477 478 switch (esc) { 479 case ESCAPE_UNICODE: 480 /* Skip past "u" header. */ 481 c = mchars_num2uc(seq + 1, len - 1); 482 break; 483 case ESCAPE_NUMBERED: 484 c = mchars_num2char(seq, len); 485 if (c < 0) 486 continue; 487 break; 488 case ESCAPE_SPECIAL: 489 c = mchars_spec2cp(seq, len); 490 if (c <= 0) 491 continue; 492 break; 493 case ESCAPE_UNDEF: 494 c = *seq; 495 break; 496 case ESCAPE_DEVICE: 497 print_word(h, "html"); 498 continue; 499 case ESCAPE_BREAK: 500 breakline = 1; 501 continue; 502 case ESCAPE_NOSPACE: 503 if ('\0' == *p) 504 nospace = 1; 505 continue; 506 case ESCAPE_OVERSTRIKE: 507 if (len == 0) 508 continue; 509 c = seq[len - 1]; 510 break; 511 default: 512 continue; 513 } 514 if ((c < 0x20 && c != 0x09) || 515 (c > 0x7E && c < 0xA0)) 516 c = 0xFFFD; 517 if (c > 0x7E) { 518 (void)snprintf(numbuf, sizeof(numbuf), "&#x%.4X;", c); 519 print_word(h, numbuf); 520 } else if (print_escape(h, c) == 0) 521 print_byte(h, c); 522 } 523 524 return nospace; 525 } 526 527 static void 528 print_href(struct html *h, const char *name, const char *sec, int man) 529 { 530 struct stat sb; 531 const char *p, *pp; 532 char *filename; 533 534 if (man) { 535 pp = h->base_man1; 536 if (h->base_man2 != NULL) { 537 mandoc_asprintf(&filename, "%s.%s", name, sec); 538 if (stat(filename, &sb) == -1) 539 pp = h->base_man2; 540 free(filename); 541 } 542 } else 543 pp = h->base_includes; 544 545 while ((p = strchr(pp, '%')) != NULL) { 546 print_encode(h, pp, p, 1); 547 if (man && p[1] == 'S') { 548 if (sec == NULL) 549 print_byte(h, '1'); 550 else 551 print_encode(h, sec, NULL, 1); 552 } else if ((man && p[1] == 'N') || 553 (man == 0 && p[1] == 'I')) 554 print_encode(h, name, NULL, 1); 555 else 556 print_encode(h, p, p + 2, 1); 557 pp = p + 2; 558 } 559 if (*pp != '\0') 560 print_encode(h, pp, NULL, 1); 561 } 562 563 struct tag * 564 print_otag(struct html *h, enum htmltag tag, const char *fmt, ...) 565 { 566 va_list ap; 567 struct tag *t; 568 const char *attr; 569 char *arg1, *arg2; 570 int style_written, tflags; 571 572 tflags = htmltags[tag].flags; 573 574 /* Push this tag onto the stack of open scopes. */ 575 576 if ((tflags & HTML_NOSTACK) == 0) { 577 t = mandoc_malloc(sizeof(struct tag)); 578 t->tag = tag; 579 t->next = h->tag; 580 h->tag = t; 581 } else 582 t = NULL; 583 584 if (tflags & HTML_NLBEFORE) 585 print_endline(h); 586 if (h->col == 0) 587 print_indent(h); 588 else if ((h->flags & HTML_NOSPACE) == 0) { 589 if (h->flags & HTML_KEEP) 590 print_word(h, " "); 591 else { 592 if (h->flags & HTML_PREKEEP) 593 h->flags |= HTML_KEEP; 594 print_endword(h); 595 } 596 } 597 598 if ( ! (h->flags & HTML_NONOSPACE)) 599 h->flags &= ~HTML_NOSPACE; 600 else 601 h->flags |= HTML_NOSPACE; 602 603 /* Print out the tag name and attributes. */ 604 605 print_byte(h, '<'); 606 print_word(h, htmltags[tag].name); 607 608 va_start(ap, fmt); 609 610 while (*fmt != '\0' && *fmt != 's') { 611 612 /* Parse attributes and arguments. */ 613 614 arg1 = va_arg(ap, char *); 615 arg2 = NULL; 616 switch (*fmt++) { 617 case 'c': 618 attr = "class"; 619 break; 620 case 'h': 621 attr = "href"; 622 break; 623 case 'i': 624 attr = "id"; 625 break; 626 case '?': 627 attr = arg1; 628 arg1 = va_arg(ap, char *); 629 break; 630 default: 631 abort(); 632 } 633 if (*fmt == 'M') 634 arg2 = va_arg(ap, char *); 635 if (arg1 == NULL) 636 continue; 637 638 /* Print the attributes. */ 639 640 print_byte(h, ' '); 641 print_word(h, attr); 642 print_byte(h, '='); 643 print_byte(h, '"'); 644 switch (*fmt) { 645 case 'I': 646 print_href(h, arg1, NULL, 0); 647 fmt++; 648 break; 649 case 'M': 650 print_href(h, arg1, arg2, 1); 651 fmt++; 652 break; 653 case 'R': 654 print_byte(h, '#'); 655 print_encode(h, arg1, NULL, 1); 656 fmt++; 657 break; 658 default: 659 print_encode(h, arg1, NULL, 1); 660 break; 661 } 662 print_byte(h, '"'); 663 } 664 665 style_written = 0; 666 while (*fmt++ == 's') { 667 arg1 = va_arg(ap, char *); 668 arg2 = va_arg(ap, char *); 669 if (arg2 == NULL) 670 continue; 671 print_byte(h, ' '); 672 if (style_written == 0) { 673 print_word(h, "style=\""); 674 style_written = 1; 675 } 676 print_word(h, arg1); 677 print_byte(h, ':'); 678 print_byte(h, ' '); 679 print_word(h, arg2); 680 print_byte(h, ';'); 681 } 682 if (style_written) 683 print_byte(h, '"'); 684 685 va_end(ap); 686 687 /* Accommodate for "well-formed" singleton escaping. */ 688 689 if (HTML_AUTOCLOSE & htmltags[tag].flags) 690 print_byte(h, '/'); 691 692 print_byte(h, '>'); 693 694 if (tflags & HTML_NLBEGIN) 695 print_endline(h); 696 else 697 h->flags |= HTML_NOSPACE; 698 699 if (tflags & HTML_INDENT) 700 h->indent++; 701 if (tflags & HTML_NOINDENT) 702 h->noindent++; 703 704 return t; 705 } 706 707 static void 708 print_ctag(struct html *h, struct tag *tag) 709 { 710 int tflags; 711 712 /* 713 * Remember to close out and nullify the current 714 * meta-font and table, if applicable. 715 */ 716 if (tag == h->metaf) 717 h->metaf = NULL; 718 if (tag == h->tblt) 719 h->tblt = NULL; 720 721 tflags = htmltags[tag->tag].flags; 722 723 if (tflags & HTML_INDENT) 724 h->indent--; 725 if (tflags & HTML_NOINDENT) 726 h->noindent--; 727 if (tflags & HTML_NLEND) 728 print_endline(h); 729 print_indent(h); 730 print_byte(h, '<'); 731 print_byte(h, '/'); 732 print_word(h, htmltags[tag->tag].name); 733 print_byte(h, '>'); 734 if (tflags & HTML_NLAFTER) 735 print_endline(h); 736 737 h->tag = tag->next; 738 free(tag); 739 } 740 741 void 742 print_gen_decls(struct html *h) 743 { 744 print_word(h, "<!DOCTYPE html>"); 745 print_endline(h); 746 } 747 748 void 749 print_gen_comment(struct html *h, struct roff_node *n) 750 { 751 int wantblank; 752 753 print_word(h, "<!-- This is an automatically generated file." 754 " Do not edit."); 755 h->indent = 1; 756 wantblank = 0; 757 while (n != NULL && n->type == ROFFT_COMMENT) { 758 if (strstr(n->string, "-->") == NULL && 759 (wantblank || *n->string != '\0')) { 760 print_endline(h); 761 print_indent(h); 762 print_word(h, n->string); 763 wantblank = *n->string != '\0'; 764 } 765 n = n->next; 766 } 767 if (wantblank) 768 print_endline(h); 769 print_word(h, " -->"); 770 print_endline(h); 771 h->indent = 0; 772 } 773 774 void 775 print_text(struct html *h, const char *word) 776 { 777 if (h->col && (h->flags & HTML_NOSPACE) == 0) { 778 if ( ! (HTML_KEEP & h->flags)) { 779 if (HTML_PREKEEP & h->flags) 780 h->flags |= HTML_KEEP; 781 print_endword(h); 782 } else 783 print_word(h, " "); 784 } 785 786 assert(NULL == h->metaf); 787 switch (h->metac) { 788 case HTMLFONT_ITALIC: 789 h->metaf = print_otag(h, TAG_I, ""); 790 break; 791 case HTMLFONT_BOLD: 792 h->metaf = print_otag(h, TAG_B, ""); 793 break; 794 case HTMLFONT_BI: 795 h->metaf = print_otag(h, TAG_B, ""); 796 print_otag(h, TAG_I, ""); 797 break; 798 case HTMLFONT_CW: 799 h->metaf = print_otag(h, TAG_SPAN, "c", "Li"); 800 break; 801 default: 802 print_indent(h); 803 break; 804 } 805 806 assert(word); 807 if ( ! print_encode(h, word, NULL, 0)) { 808 if ( ! (h->flags & HTML_NONOSPACE)) 809 h->flags &= ~HTML_NOSPACE; 810 h->flags &= ~HTML_NONEWLINE; 811 } else 812 h->flags |= HTML_NOSPACE | HTML_NONEWLINE; 813 814 if (h->metaf) { 815 print_tagq(h, h->metaf); 816 h->metaf = NULL; 817 } 818 819 h->flags &= ~HTML_IGNDELIM; 820 } 821 822 void 823 print_tagq(struct html *h, const struct tag *until) 824 { 825 struct tag *tag; 826 827 while ((tag = h->tag) != NULL) { 828 print_ctag(h, tag); 829 if (tag == until) 830 return; 831 } 832 } 833 834 /* 835 * Close out all open elements up to but excluding suntil. 836 * Note that a paragraph just inside stays open together with it 837 * because paragraphs include subsequent phrasing content. 838 */ 839 void 840 print_stagq(struct html *h, const struct tag *suntil) 841 { 842 struct tag *tag; 843 844 while ((tag = h->tag) != NULL) { 845 if (tag == suntil || 846 (tag->next == suntil && 847 (tag->tag == TAG_P || tag->tag == TAG_PRE))) 848 return; 849 print_ctag(h, tag); 850 } 851 } 852 853 854 /*********************************************************************** 855 * Low level output functions. 856 * They implement line breaking using a short static buffer. 857 ***********************************************************************/ 858 859 /* 860 * Buffer one HTML output byte. 861 * If the buffer is full, flush and deactivate it and start a new line. 862 * If the buffer is inactive, print directly. 863 */ 864 static void 865 print_byte(struct html *h, char c) 866 { 867 if ((h->flags & HTML_BUFFER) == 0) { 868 putchar(c); 869 h->col++; 870 return; 871 } 872 873 if (h->col + h->bufcol < sizeof(h->buf)) { 874 h->buf[h->bufcol++] = c; 875 return; 876 } 877 878 putchar('\n'); 879 h->col = 0; 880 print_indent(h); 881 putchar(' '); 882 putchar(' '); 883 fwrite(h->buf, h->bufcol, 1, stdout); 884 putchar(c); 885 h->col = (h->indent + 1) * 2 + h->bufcol + 1; 886 h->bufcol = 0; 887 h->flags &= ~HTML_BUFFER; 888 } 889 890 /* 891 * If something was printed on the current output line, end it. 892 * Not to be called right after print_indent(). 893 */ 894 void 895 print_endline(struct html *h) 896 { 897 if (h->col == 0) 898 return; 899 900 if (h->bufcol) { 901 putchar(' '); 902 fwrite(h->buf, h->bufcol, 1, stdout); 903 h->bufcol = 0; 904 } 905 putchar('\n'); 906 h->col = 0; 907 h->flags |= HTML_NOSPACE; 908 h->flags &= ~HTML_BUFFER; 909 } 910 911 /* 912 * Flush the HTML output buffer. 913 * If it is inactive, activate it. 914 */ 915 static void 916 print_endword(struct html *h) 917 { 918 if (h->noindent) { 919 print_byte(h, ' '); 920 return; 921 } 922 923 if ((h->flags & HTML_BUFFER) == 0) { 924 h->col++; 925 h->flags |= HTML_BUFFER; 926 } else if (h->bufcol) { 927 putchar(' '); 928 fwrite(h->buf, h->bufcol, 1, stdout); 929 h->col += h->bufcol + 1; 930 } 931 h->bufcol = 0; 932 } 933 934 /* 935 * If at the beginning of a new output line, 936 * perform indentation and mark the line as containing output. 937 * Make sure to really produce some output right afterwards, 938 * but do not use print_otag() for producing it. 939 */ 940 static void 941 print_indent(struct html *h) 942 { 943 size_t i; 944 945 if (h->col) 946 return; 947 948 if (h->noindent == 0) { 949 h->col = h->indent * 2; 950 for (i = 0; i < h->col; i++) 951 putchar(' '); 952 } 953 h->flags &= ~HTML_NOSPACE; 954 } 955 956 /* 957 * Print or buffer some characters 958 * depending on the current HTML output buffer state. 959 */ 960 static void 961 print_word(struct html *h, const char *cp) 962 { 963 while (*cp != '\0') 964 print_byte(h, *cp++); 965 } 966