1 /* $OpenBSD: mdoc_html.c,v 1.228 2025/01/25 00:19:23 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2014-2022, 2025 Ingo Schwarze <schwarze@openbsd.org> 4 * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv> 5 * Copyright (c) 2022 Anna Vyalkova <cyber@sysrq.in> 6 * 7 * Permission to use, copy, modify, and distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 * 19 * HTML formatter for mdoc(7) used by mandoc(1). 20 */ 21 #include <sys/types.h> 22 23 #include <assert.h> 24 #include <ctype.h> 25 #include <stdio.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <unistd.h> 29 30 #include "mandoc_aux.h" 31 #include "mandoc.h" 32 #include "roff.h" 33 #include "mdoc.h" 34 #include "out.h" 35 #include "html.h" 36 #include "main.h" 37 38 #define MDOC_ARGS const struct roff_meta *meta, \ 39 struct roff_node *n, \ 40 struct html *h 41 42 #ifndef MIN 43 #define MIN(a,b) ((/*CONSTCOND*/(a)<(b))?(a):(b)) 44 #endif 45 46 struct mdoc_html_act { 47 int (*pre)(MDOC_ARGS); 48 void (*post)(MDOC_ARGS); 49 }; 50 51 static void print_mdoc_head(const struct roff_meta *, 52 struct html *); 53 static void print_mdoc_node(MDOC_ARGS); 54 static void print_mdoc_nodelist(MDOC_ARGS); 55 static void synopsis_pre(struct html *, struct roff_node *); 56 57 static void mdoc_root_post(const struct roff_meta *, 58 struct html *); 59 static int mdoc_root_pre(const struct roff_meta *, 60 struct html *); 61 62 static void mdoc__x_post(MDOC_ARGS); 63 static int mdoc__x_pre(MDOC_ARGS); 64 static int mdoc_abort_pre(MDOC_ARGS); 65 static int mdoc_ad_pre(MDOC_ARGS); 66 static int mdoc_an_pre(MDOC_ARGS); 67 static int mdoc_ap_pre(MDOC_ARGS); 68 static int mdoc_ar_pre(MDOC_ARGS); 69 static int mdoc_bd_pre(MDOC_ARGS); 70 static int mdoc_bf_pre(MDOC_ARGS); 71 static void mdoc_bk_post(MDOC_ARGS); 72 static int mdoc_bk_pre(MDOC_ARGS); 73 static int mdoc_bl_pre(MDOC_ARGS); 74 static int mdoc_cd_pre(MDOC_ARGS); 75 static int mdoc_code_pre(MDOC_ARGS); 76 static int mdoc_d1_pre(MDOC_ARGS); 77 static int mdoc_fa_pre(MDOC_ARGS); 78 static int mdoc_fd_pre(MDOC_ARGS); 79 static int mdoc_fl_pre(MDOC_ARGS); 80 static int mdoc_fn_pre(MDOC_ARGS); 81 static int mdoc_ft_pre(MDOC_ARGS); 82 static int mdoc_em_pre(MDOC_ARGS); 83 static void mdoc_eo_post(MDOC_ARGS); 84 static int mdoc_eo_pre(MDOC_ARGS); 85 static int mdoc_ex_pre(MDOC_ARGS); 86 static void mdoc_fo_post(MDOC_ARGS); 87 static int mdoc_fo_pre(MDOC_ARGS); 88 static int mdoc_igndelim_pre(MDOC_ARGS); 89 static int mdoc_in_pre(MDOC_ARGS); 90 static int mdoc_it_pre(MDOC_ARGS); 91 static int mdoc_lb_pre(MDOC_ARGS); 92 static int mdoc_lk_pre(MDOC_ARGS); 93 static int mdoc_mt_pre(MDOC_ARGS); 94 static int mdoc_nd_pre(MDOC_ARGS); 95 static int mdoc_nm_pre(MDOC_ARGS); 96 static int mdoc_no_pre(MDOC_ARGS); 97 static int mdoc_ns_pre(MDOC_ARGS); 98 static int mdoc_pa_pre(MDOC_ARGS); 99 static void mdoc_pf_post(MDOC_ARGS); 100 static int mdoc_pp_pre(MDOC_ARGS); 101 static void mdoc_quote_post(MDOC_ARGS); 102 static int mdoc_quote_pre(MDOC_ARGS); 103 static int mdoc_rs_pre(MDOC_ARGS); 104 static int mdoc_sh_pre(MDOC_ARGS); 105 static int mdoc_skip_pre(MDOC_ARGS); 106 static int mdoc_sm_pre(MDOC_ARGS); 107 static int mdoc_ss_pre(MDOC_ARGS); 108 static int mdoc_st_pre(MDOC_ARGS); 109 static int mdoc_sx_pre(MDOC_ARGS); 110 static int mdoc_sy_pre(MDOC_ARGS); 111 static int mdoc_tg_pre(MDOC_ARGS); 112 static int mdoc_va_pre(MDOC_ARGS); 113 static int mdoc_vt_pre(MDOC_ARGS); 114 static int mdoc_xr_pre(MDOC_ARGS); 115 static int mdoc_xx_pre(MDOC_ARGS); 116 117 static const struct mdoc_html_act mdoc_html_acts[MDOC_MAX - MDOC_Dd] = { 118 {NULL, NULL}, /* Dd */ 119 {NULL, NULL}, /* Dt */ 120 {NULL, NULL}, /* Os */ 121 {mdoc_sh_pre, NULL }, /* Sh */ 122 {mdoc_ss_pre, NULL }, /* Ss */ 123 {mdoc_pp_pre, NULL}, /* Pp */ 124 {mdoc_d1_pre, NULL}, /* D1 */ 125 {mdoc_d1_pre, NULL}, /* Dl */ 126 {mdoc_bd_pre, NULL}, /* Bd */ 127 {NULL, NULL}, /* Ed */ 128 {mdoc_bl_pre, NULL}, /* Bl */ 129 {NULL, NULL}, /* El */ 130 {mdoc_it_pre, NULL}, /* It */ 131 {mdoc_ad_pre, NULL}, /* Ad */ 132 {mdoc_an_pre, NULL}, /* An */ 133 {mdoc_ap_pre, NULL}, /* Ap */ 134 {mdoc_ar_pre, NULL}, /* Ar */ 135 {mdoc_cd_pre, NULL}, /* Cd */ 136 {mdoc_code_pre, NULL}, /* Cm */ 137 {mdoc_code_pre, NULL}, /* Dv */ 138 {mdoc_code_pre, NULL}, /* Er */ 139 {mdoc_code_pre, NULL}, /* Ev */ 140 {mdoc_ex_pre, NULL}, /* Ex */ 141 {mdoc_fa_pre, NULL}, /* Fa */ 142 {mdoc_fd_pre, NULL}, /* Fd */ 143 {mdoc_fl_pre, NULL}, /* Fl */ 144 {mdoc_fn_pre, NULL}, /* Fn */ 145 {mdoc_ft_pre, NULL}, /* Ft */ 146 {mdoc_code_pre, NULL}, /* Ic */ 147 {mdoc_in_pre, NULL}, /* In */ 148 {mdoc_code_pre, NULL}, /* Li */ 149 {mdoc_nd_pre, NULL}, /* Nd */ 150 {mdoc_nm_pre, NULL}, /* Nm */ 151 {mdoc_quote_pre, mdoc_quote_post}, /* Op */ 152 {mdoc_abort_pre, NULL}, /* Ot */ 153 {mdoc_pa_pre, NULL}, /* Pa */ 154 {mdoc_ex_pre, NULL}, /* Rv */ 155 {mdoc_st_pre, NULL}, /* St */ 156 {mdoc_va_pre, NULL}, /* Va */ 157 {mdoc_vt_pre, NULL}, /* Vt */ 158 {mdoc_xr_pre, NULL}, /* Xr */ 159 {mdoc__x_pre, mdoc__x_post}, /* %A */ 160 {mdoc__x_pre, mdoc__x_post}, /* %B */ 161 {mdoc__x_pre, mdoc__x_post}, /* %D */ 162 {mdoc__x_pre, mdoc__x_post}, /* %I */ 163 {mdoc__x_pre, mdoc__x_post}, /* %J */ 164 {mdoc__x_pre, mdoc__x_post}, /* %N */ 165 {mdoc__x_pre, mdoc__x_post}, /* %O */ 166 {mdoc__x_pre, mdoc__x_post}, /* %P */ 167 {mdoc__x_pre, mdoc__x_post}, /* %R */ 168 {mdoc__x_pre, mdoc__x_post}, /* %T */ 169 {mdoc__x_pre, mdoc__x_post}, /* %V */ 170 {NULL, NULL}, /* Ac */ 171 {mdoc_quote_pre, mdoc_quote_post}, /* Ao */ 172 {mdoc_quote_pre, mdoc_quote_post}, /* Aq */ 173 {mdoc_xx_pre, NULL}, /* At */ 174 {NULL, NULL}, /* Bc */ 175 {mdoc_bf_pre, NULL}, /* Bf */ 176 {mdoc_quote_pre, mdoc_quote_post}, /* Bo */ 177 {mdoc_quote_pre, mdoc_quote_post}, /* Bq */ 178 {mdoc_xx_pre, NULL}, /* Bsx */ 179 {mdoc_xx_pre, NULL}, /* Bx */ 180 {mdoc_skip_pre, NULL}, /* Db */ 181 {NULL, NULL}, /* Dc */ 182 {mdoc_quote_pre, mdoc_quote_post}, /* Do */ 183 {mdoc_quote_pre, mdoc_quote_post}, /* Dq */ 184 {NULL, NULL}, /* Ec */ /* FIXME: no space */ 185 {NULL, NULL}, /* Ef */ 186 {mdoc_em_pre, NULL}, /* Em */ 187 {mdoc_eo_pre, mdoc_eo_post}, /* Eo */ 188 {mdoc_xx_pre, NULL}, /* Fx */ 189 {mdoc_no_pre, NULL}, /* Ms */ 190 {mdoc_no_pre, NULL}, /* No */ 191 {mdoc_ns_pre, NULL}, /* Ns */ 192 {mdoc_xx_pre, NULL}, /* Nx */ 193 {mdoc_xx_pre, NULL}, /* Ox */ 194 {NULL, NULL}, /* Pc */ 195 {mdoc_igndelim_pre, mdoc_pf_post}, /* Pf */ 196 {mdoc_quote_pre, mdoc_quote_post}, /* Po */ 197 {mdoc_quote_pre, mdoc_quote_post}, /* Pq */ 198 {NULL, NULL}, /* Qc */ 199 {mdoc_quote_pre, mdoc_quote_post}, /* Ql */ 200 {mdoc_quote_pre, mdoc_quote_post}, /* Qo */ 201 {mdoc_quote_pre, mdoc_quote_post}, /* Qq */ 202 {NULL, NULL}, /* Re */ 203 {mdoc_rs_pre, NULL}, /* Rs */ 204 {NULL, NULL}, /* Sc */ 205 {mdoc_quote_pre, mdoc_quote_post}, /* So */ 206 {mdoc_quote_pre, mdoc_quote_post}, /* Sq */ 207 {mdoc_sm_pre, NULL}, /* Sm */ 208 {mdoc_sx_pre, NULL}, /* Sx */ 209 {mdoc_sy_pre, NULL}, /* Sy */ 210 {NULL, NULL}, /* Tn */ 211 {mdoc_xx_pre, NULL}, /* Ux */ 212 {NULL, NULL}, /* Xc */ 213 {NULL, NULL}, /* Xo */ 214 {mdoc_fo_pre, mdoc_fo_post}, /* Fo */ 215 {NULL, NULL}, /* Fc */ 216 {mdoc_quote_pre, mdoc_quote_post}, /* Oo */ 217 {NULL, NULL}, /* Oc */ 218 {mdoc_bk_pre, mdoc_bk_post}, /* Bk */ 219 {NULL, NULL}, /* Ek */ 220 {NULL, NULL}, /* Bt */ 221 {NULL, NULL}, /* Hf */ 222 {mdoc_em_pre, NULL}, /* Fr */ 223 {NULL, NULL}, /* Ud */ 224 {mdoc_lb_pre, NULL}, /* Lb */ 225 {mdoc_abort_pre, NULL}, /* Lp */ 226 {mdoc_lk_pre, NULL}, /* Lk */ 227 {mdoc_mt_pre, NULL}, /* Mt */ 228 {mdoc_quote_pre, mdoc_quote_post}, /* Brq */ 229 {mdoc_quote_pre, mdoc_quote_post}, /* Bro */ 230 {NULL, NULL}, /* Brc */ 231 {mdoc__x_pre, mdoc__x_post}, /* %C */ 232 {mdoc_skip_pre, NULL}, /* Es */ 233 {mdoc_quote_pre, mdoc_quote_post}, /* En */ 234 {mdoc_xx_pre, NULL}, /* Dx */ 235 {mdoc__x_pre, mdoc__x_post}, /* %Q */ 236 {mdoc__x_pre, mdoc__x_post}, /* %U */ 237 {NULL, NULL}, /* Ta */ 238 {mdoc_tg_pre, NULL}, /* Tg */ 239 }; 240 241 242 /* 243 * See the same function in mdoc_term.c for documentation. 244 */ 245 static void 246 synopsis_pre(struct html *h, struct roff_node *n) 247 { 248 struct roff_node *np; 249 250 if ((n->flags & NODE_SYNPRETTY) == 0 || 251 (np = roff_node_prev(n)) == NULL) 252 return; 253 254 if (np->tok == n->tok && 255 MDOC_Fo != n->tok && 256 MDOC_Ft != n->tok && 257 MDOC_Fn != n->tok) { 258 print_otag(h, TAG_BR, ""); 259 return; 260 } 261 262 switch (np->tok) { 263 case MDOC_Fd: 264 case MDOC_Fn: 265 case MDOC_Fo: 266 case MDOC_In: 267 case MDOC_Vt: 268 break; 269 case MDOC_Ft: 270 if (n->tok != MDOC_Fn && n->tok != MDOC_Fo) 271 break; 272 /* FALLTHROUGH */ 273 default: 274 print_otag(h, TAG_BR, ""); 275 return; 276 } 277 html_close_paragraph(h); 278 print_otag(h, TAG_P, "c", "Pp"); 279 } 280 281 void 282 html_mdoc(void *arg, const struct roff_meta *mdoc) 283 { 284 struct html *h; 285 struct roff_node *n; 286 struct tag *t; 287 288 h = (struct html *)arg; 289 n = mdoc->first->child; 290 291 if ((h->oflags & HTML_FRAGMENT) == 0) { 292 print_gen_decls(h); 293 print_otag(h, TAG_HTML, ""); 294 t = print_otag(h, TAG_HEAD, ""); 295 print_mdoc_head(mdoc, h); 296 print_tagq(h, t); 297 if (n != NULL && n->type == ROFFT_COMMENT) 298 print_gen_comment(h, n); 299 print_otag(h, TAG_BODY, ""); 300 } 301 302 mdoc_root_pre(mdoc, h); 303 t = print_otag(h, TAG_MAIN, "c", "manual-text"); 304 print_mdoc_nodelist(mdoc, n, h); 305 print_tagq(h, t); 306 mdoc_root_post(mdoc, h); 307 print_tagq(h, NULL); 308 } 309 310 static void 311 print_mdoc_head(const struct roff_meta *meta, struct html *h) 312 { 313 char *cp; 314 315 print_gen_head(h); 316 317 if (meta->arch != NULL && meta->msec != NULL) 318 mandoc_asprintf(&cp, "%s(%s) (%s)", meta->title, 319 meta->msec, meta->arch); 320 else if (meta->msec != NULL) 321 mandoc_asprintf(&cp, "%s(%s)", meta->title, meta->msec); 322 else if (meta->arch != NULL) 323 mandoc_asprintf(&cp, "%s (%s)", meta->title, meta->arch); 324 else 325 cp = mandoc_strdup(meta->title); 326 327 print_otag(h, TAG_TITLE, ""); 328 print_text(h, cp); 329 free(cp); 330 } 331 332 static void 333 print_mdoc_nodelist(MDOC_ARGS) 334 { 335 336 while (n != NULL) { 337 print_mdoc_node(meta, n, h); 338 n = n->next; 339 } 340 } 341 342 static void 343 print_mdoc_node(MDOC_ARGS) 344 { 345 struct tag *t; 346 int child; 347 348 if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT) 349 return; 350 351 if ((n->flags & NODE_NOFILL) == 0) 352 html_fillmode(h, ROFF_fi); 353 else if (html_fillmode(h, ROFF_nf) == ROFF_nf && 354 n->tok != ROFF_fi && n->flags & NODE_LINE) 355 print_endline(h); 356 357 child = 1; 358 n->flags &= ~NODE_ENDED; 359 switch (n->type) { 360 case ROFFT_TEXT: 361 if (n->flags & NODE_LINE) { 362 switch (*n->string) { 363 case '\0': 364 h->col = 1; 365 print_endline(h); 366 return; 367 case ' ': 368 if ((h->flags & HTML_NONEWLINE) == 0 && 369 (n->flags & NODE_NOFILL) == 0) 370 print_otag(h, TAG_BR, ""); 371 break; 372 default: 373 break; 374 } 375 } 376 t = h->tag; 377 t->refcnt++; 378 if (n->flags & NODE_DELIMC) 379 h->flags |= HTML_NOSPACE; 380 if (n->flags & NODE_HREF) 381 print_tagged_text(h, n->string, n); 382 else 383 print_text(h, n->string); 384 if (n->flags & NODE_DELIMO) 385 h->flags |= HTML_NOSPACE; 386 break; 387 case ROFFT_EQN: 388 t = h->tag; 389 t->refcnt++; 390 print_eqn(h, n->eqn); 391 break; 392 case ROFFT_TBL: 393 /* 394 * This will take care of initialising all of the table 395 * state data for the first table, then tearing it down 396 * for the last one. 397 */ 398 print_tbl(h, n->span); 399 return; 400 default: 401 /* 402 * Close out the current table, if it's open, and unset 403 * the "meta" table state. This will be reopened on the 404 * next table element. 405 */ 406 if (h->tblt != NULL) 407 print_tblclose(h); 408 assert(h->tblt == NULL); 409 t = h->tag; 410 t->refcnt++; 411 if (n->tok < ROFF_MAX) { 412 roff_html_pre(h, n); 413 t->refcnt--; 414 print_stagq(h, t); 415 return; 416 } 417 assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX); 418 if (mdoc_html_acts[n->tok - MDOC_Dd].pre != NULL && 419 (n->end == ENDBODY_NOT || n->child != NULL)) 420 child = (*mdoc_html_acts[n->tok - MDOC_Dd].pre)(meta, 421 n, h); 422 break; 423 } 424 425 if (h->flags & HTML_KEEP && n->flags & NODE_LINE) { 426 h->flags &= ~HTML_KEEP; 427 h->flags |= HTML_PREKEEP; 428 } 429 430 if (child && n->child != NULL) 431 print_mdoc_nodelist(meta, n->child, h); 432 433 t->refcnt--; 434 print_stagq(h, t); 435 436 switch (n->type) { 437 case ROFFT_TEXT: 438 case ROFFT_EQN: 439 break; 440 default: 441 if (mdoc_html_acts[n->tok - MDOC_Dd].post == NULL || 442 n->flags & NODE_ENDED) 443 break; 444 (*mdoc_html_acts[n->tok - MDOC_Dd].post)(meta, n, h); 445 if (n->end != ENDBODY_NOT) 446 n->body->flags |= NODE_ENDED; 447 break; 448 } 449 } 450 451 static void 452 mdoc_root_post(const struct roff_meta *meta, struct html *h) 453 { 454 struct tag *t; 455 456 t = print_otag(h, TAG_DIV, "cr?", "foot", "doc-pagefooter", 457 "aria-label", "Manual footer line"); 458 459 print_otag(h, TAG_SPAN, "c", "foot-left"); 460 print_stagq(h, t); 461 462 print_otag(h, TAG_SPAN, "c", "foot-date"); 463 print_text(h, meta->date); 464 print_stagq(h, t); 465 466 print_otag(h, TAG_SPAN, "c", "foot-os"); 467 print_text(h, meta->os); 468 print_tagq(h, t); 469 } 470 471 static int 472 mdoc_root_pre(const struct roff_meta *meta, struct html *h) 473 { 474 struct tag *t; 475 char *volume, *title; 476 477 if (NULL == meta->arch) 478 volume = mandoc_strdup(meta->vol); 479 else 480 mandoc_asprintf(&volume, "%s (%s)", 481 meta->vol, meta->arch); 482 483 if (NULL == meta->msec) 484 title = mandoc_strdup(meta->title); 485 else 486 mandoc_asprintf(&title, "%s(%s)", 487 meta->title, meta->msec); 488 489 t = print_otag(h, TAG_DIV, "cr?", "head", "doc-pageheader", 490 "aria-label", "Manual header line"); 491 492 print_otag(h, TAG_SPAN, "c", "head-ltitle"); 493 print_text(h, title); 494 print_stagq(h, t); 495 496 print_otag(h, TAG_SPAN, "c", "head-vol"); 497 print_text(h, volume); 498 print_stagq(h, t); 499 500 print_otag(h, TAG_SPAN, "c", "head-rtitle"); 501 print_text(h, title); 502 print_tagq(h, t); 503 504 free(title); 505 free(volume); 506 return 1; 507 } 508 509 static int 510 mdoc_code_pre(MDOC_ARGS) 511 { 512 print_otag_id(h, TAG_CODE, roff_name[n->tok], n); 513 return 1; 514 } 515 516 static int 517 mdoc_sh_pre(MDOC_ARGS) 518 { 519 struct roff_node *sn, *subn; 520 struct tag *t, *tnav, *tsec, *tsub; 521 char *id; 522 int sc; 523 524 switch (n->type) { 525 case ROFFT_BLOCK: 526 html_close_paragraph(h); 527 if ((h->oflags & HTML_TOC) == 0 || 528 h->flags & HTML_TOCDONE || 529 n->sec <= SEC_SYNOPSIS) { 530 print_otag(h, TAG_SECTION, "c", "Sh"); 531 break; 532 } 533 h->flags |= HTML_TOCDONE; 534 sc = 0; 535 for (sn = n->next; sn != NULL; sn = sn->next) 536 if (sn->sec == SEC_CUSTOM) 537 if (++sc == 2) 538 break; 539 if (sc < 2) 540 break; 541 tnav = print_otag(h, TAG_NAV, "r", "doc-toc"); 542 t = print_otag(h, TAG_H2, "c", "Sh"); 543 print_text(h, "TABLE OF CONTENTS"); 544 print_tagq(h, t); 545 t = print_otag(h, TAG_UL, "c", "Bl-compact"); 546 for (sn = n; sn != NULL; sn = sn->next) { 547 tsec = print_otag(h, TAG_LI, ""); 548 id = html_make_id(sn->head, 0); 549 tsub = print_otag(h, TAG_A, "hR", id); 550 free(id); 551 print_mdoc_nodelist(meta, sn->head->child, h); 552 print_tagq(h, tsub); 553 tsub = NULL; 554 for (subn = sn->body->child; subn != NULL; 555 subn = subn->next) { 556 if (subn->tok != MDOC_Ss) 557 continue; 558 id = html_make_id(subn->head, 0); 559 if (id == NULL) 560 continue; 561 if (tsub == NULL) 562 print_otag(h, TAG_UL, 563 "c", "Bl-compact"); 564 tsub = print_otag(h, TAG_LI, ""); 565 print_otag(h, TAG_A, "hR", id); 566 free(id); 567 print_mdoc_nodelist(meta, 568 subn->head->child, h); 569 print_tagq(h, tsub); 570 } 571 print_tagq(h, tsec); 572 } 573 print_tagq(h, tnav); 574 print_otag(h, TAG_SECTION, "c", "Sh"); 575 break; 576 case ROFFT_HEAD: 577 print_otag_id(h, TAG_H2, "Sh", n); 578 break; 579 case ROFFT_BODY: 580 if (n->sec == SEC_AUTHORS) 581 h->flags &= ~(HTML_SPLIT|HTML_NOSPLIT); 582 break; 583 default: 584 break; 585 } 586 return 1; 587 } 588 589 static int 590 mdoc_ss_pre(MDOC_ARGS) 591 { 592 switch (n->type) { 593 case ROFFT_BLOCK: 594 html_close_paragraph(h); 595 print_otag(h, TAG_SECTION, "c", "Ss"); 596 break; 597 case ROFFT_HEAD: 598 print_otag_id(h, TAG_H3, "Ss", n); 599 break; 600 case ROFFT_BODY: 601 break; 602 default: 603 abort(); 604 } 605 return 1; 606 } 607 608 static int 609 mdoc_fl_pre(MDOC_ARGS) 610 { 611 struct roff_node *nn; 612 613 print_otag_id(h, TAG_CODE, "Fl", n); 614 print_text(h, "\\-"); 615 if (n->child != NULL || 616 ((nn = roff_node_next(n)) != NULL && 617 nn->type != ROFFT_TEXT && 618 (nn->flags & NODE_LINE) == 0)) 619 h->flags |= HTML_NOSPACE; 620 621 return 1; 622 } 623 624 static int 625 mdoc_nd_pre(MDOC_ARGS) 626 { 627 switch (n->type) { 628 case ROFFT_BLOCK: 629 return 1; 630 case ROFFT_HEAD: 631 return 0; 632 case ROFFT_BODY: 633 break; 634 default: 635 abort(); 636 } 637 print_text(h, "\\(em"); 638 print_otag(h, TAG_SPAN, "cr", "Nd", "doc-subtitle"); 639 return 1; 640 } 641 642 static int 643 mdoc_nm_pre(MDOC_ARGS) 644 { 645 switch (n->type) { 646 case ROFFT_BLOCK: 647 break; 648 case ROFFT_HEAD: 649 print_otag(h, TAG_TD, ""); 650 /* FALLTHROUGH */ 651 case ROFFT_ELEM: 652 print_otag(h, TAG_CODE, "c", "Nm"); 653 return 1; 654 case ROFFT_BODY: 655 print_otag(h, TAG_TD, ""); 656 return 1; 657 default: 658 abort(); 659 } 660 html_close_paragraph(h); 661 synopsis_pre(h, n); 662 print_otag(h, TAG_TABLE, "c", "Nm"); 663 print_otag(h, TAG_TR, ""); 664 return 1; 665 } 666 667 static int 668 mdoc_xr_pre(MDOC_ARGS) 669 { 670 char *name, *section, *label; 671 672 if (n->child == NULL) 673 return 0; 674 675 name = n->child->string; 676 if (n->child->next != NULL) { 677 section = n->child->next->string; 678 mandoc_asprintf(&label, "%s, section %s", name, section); 679 } else 680 section = label = NULL; 681 682 if (h->base_man1) 683 print_otag(h, TAG_A, "chM?", "Xr", 684 name, section, "aria-label", label); 685 else 686 print_otag(h, TAG_A, "c?", "Xr", "aria-label", label); 687 688 free(label); 689 print_text(h, name); 690 691 if (section == NULL) 692 return 0; 693 694 h->flags |= HTML_NOSPACE; 695 print_text(h, "("); 696 h->flags |= HTML_NOSPACE; 697 print_text(h, section); 698 h->flags |= HTML_NOSPACE; 699 print_text(h, ")"); 700 return 0; 701 } 702 703 static int 704 mdoc_tg_pre(MDOC_ARGS) 705 { 706 char *id; 707 708 if ((id = html_make_id(n, 1)) != NULL) { 709 print_tagq(h, print_otag(h, TAG_MARK, "i", id)); 710 free(id); 711 } 712 return 0; 713 } 714 715 static int 716 mdoc_ns_pre(MDOC_ARGS) 717 { 718 719 if ( ! (NODE_LINE & n->flags)) 720 h->flags |= HTML_NOSPACE; 721 return 1; 722 } 723 724 static int 725 mdoc_ar_pre(MDOC_ARGS) 726 { 727 print_otag(h, TAG_VAR, "c", "Ar"); 728 return 1; 729 } 730 731 static int 732 mdoc_xx_pre(MDOC_ARGS) 733 { 734 print_otag(h, TAG_SPAN, "c", "Ux"); 735 return 1; 736 } 737 738 static int 739 mdoc_it_pre(MDOC_ARGS) 740 { 741 const struct roff_node *bl; 742 enum mdoc_list type; 743 744 bl = n->parent; 745 while (bl->tok != MDOC_Bl) 746 bl = bl->parent; 747 type = bl->norm->Bl.type; 748 749 switch (type) { 750 case LIST_bullet: 751 case LIST_dash: 752 case LIST_hyphen: 753 case LIST_item: 754 case LIST_enum: 755 switch (n->type) { 756 case ROFFT_HEAD: 757 return 0; 758 case ROFFT_BODY: 759 print_otag_id(h, TAG_LI, NULL, n); 760 break; 761 default: 762 break; 763 } 764 break; 765 case LIST_diag: 766 case LIST_hang: 767 case LIST_inset: 768 case LIST_ohang: 769 switch (n->type) { 770 case ROFFT_HEAD: 771 print_otag_id(h, TAG_DT, NULL, n); 772 break; 773 case ROFFT_BODY: 774 print_otag(h, TAG_DD, ""); 775 break; 776 default: 777 break; 778 } 779 break; 780 case LIST_tag: 781 switch (n->type) { 782 case ROFFT_HEAD: 783 print_otag_id(h, TAG_DT, NULL, n); 784 break; 785 case ROFFT_BODY: 786 if (n->child == NULL) { 787 print_otag(h, TAG_DD, "s", "width", "auto"); 788 print_text(h, "\\ "); 789 } else 790 print_otag(h, TAG_DD, ""); 791 break; 792 default: 793 break; 794 } 795 break; 796 case LIST_column: 797 switch (n->type) { 798 case ROFFT_HEAD: 799 break; 800 case ROFFT_BODY: 801 print_otag(h, TAG_TD, ""); 802 break; 803 default: 804 print_otag_id(h, TAG_TR, NULL, n); 805 } 806 default: 807 break; 808 } 809 810 return 1; 811 } 812 813 static int 814 mdoc_bl_pre(MDOC_ARGS) 815 { 816 char cattr[32]; 817 struct mdoc_bl *bl; 818 enum htmltag elemtype; 819 820 switch (n->type) { 821 case ROFFT_BLOCK: 822 html_close_paragraph(h); 823 break; 824 case ROFFT_HEAD: 825 return 0; 826 case ROFFT_BODY: 827 return 1; 828 default: 829 abort(); 830 } 831 832 bl = &n->norm->Bl; 833 switch (bl->type) { 834 case LIST_bullet: 835 elemtype = TAG_UL; 836 (void)strlcpy(cattr, "Bl-bullet", sizeof(cattr)); 837 break; 838 case LIST_dash: 839 case LIST_hyphen: 840 elemtype = TAG_UL; 841 (void)strlcpy(cattr, "Bl-dash", sizeof(cattr)); 842 break; 843 case LIST_item: 844 elemtype = TAG_UL; 845 (void)strlcpy(cattr, "Bl-item", sizeof(cattr)); 846 break; 847 case LIST_enum: 848 elemtype = TAG_OL; 849 (void)strlcpy(cattr, "Bl-enum", sizeof(cattr)); 850 break; 851 case LIST_diag: 852 elemtype = TAG_DL; 853 (void)strlcpy(cattr, "Bl-diag", sizeof(cattr)); 854 break; 855 case LIST_hang: 856 elemtype = TAG_DL; 857 (void)strlcpy(cattr, "Bl-hang", sizeof(cattr)); 858 break; 859 case LIST_inset: 860 elemtype = TAG_DL; 861 (void)strlcpy(cattr, "Bl-inset", sizeof(cattr)); 862 break; 863 case LIST_ohang: 864 elemtype = TAG_DL; 865 (void)strlcpy(cattr, "Bl-ohang", sizeof(cattr)); 866 break; 867 case LIST_tag: 868 if (bl->offs) 869 print_otag(h, TAG_DIV, "c", "Bd-indent"); 870 print_otag_id(h, TAG_DL, 871 bl->comp ? "Bl-tag Bl-compact" : "Bl-tag", n->body); 872 return 1; 873 case LIST_column: 874 elemtype = TAG_TABLE; 875 (void)strlcpy(cattr, "Bl-column", sizeof(cattr)); 876 break; 877 default: 878 abort(); 879 } 880 if (bl->offs != NULL) 881 (void)strlcat(cattr, " Bd-indent", sizeof(cattr)); 882 if (bl->comp) 883 (void)strlcat(cattr, " Bl-compact", sizeof(cattr)); 884 print_otag_id(h, elemtype, cattr, n->body); 885 return 1; 886 } 887 888 static int 889 mdoc_ex_pre(MDOC_ARGS) 890 { 891 if (roff_node_prev(n) != NULL) 892 print_otag(h, TAG_BR, ""); 893 return 1; 894 } 895 896 static int 897 mdoc_st_pre(MDOC_ARGS) 898 { 899 print_otag(h, TAG_SPAN, "c", "St"); 900 return 1; 901 } 902 903 static int 904 mdoc_em_pre(MDOC_ARGS) 905 { 906 print_otag_id(h, TAG_I, "Em", n); 907 return 1; 908 } 909 910 static int 911 mdoc_d1_pre(MDOC_ARGS) 912 { 913 switch (n->type) { 914 case ROFFT_BLOCK: 915 html_close_paragraph(h); 916 return 1; 917 case ROFFT_HEAD: 918 return 0; 919 case ROFFT_BODY: 920 break; 921 default: 922 abort(); 923 } 924 print_otag_id(h, TAG_DIV, "Bd Bd-indent", n); 925 if (n->tok == MDOC_Dl) 926 print_otag(h, TAG_CODE, "c", "Li"); 927 return 1; 928 } 929 930 static int 931 mdoc_sx_pre(MDOC_ARGS) 932 { 933 char *id; 934 935 id = html_make_id(n, 0); 936 print_otag(h, TAG_A, "chR", "Sx", id); 937 free(id); 938 return 1; 939 } 940 941 static int 942 mdoc_bd_pre(MDOC_ARGS) 943 { 944 char buf[20]; 945 struct roff_node *nn; 946 int comp; 947 948 switch (n->type) { 949 case ROFFT_BLOCK: 950 html_close_paragraph(h); 951 return 1; 952 case ROFFT_HEAD: 953 return 0; 954 case ROFFT_BODY: 955 break; 956 default: 957 abort(); 958 } 959 960 /* Handle preceding whitespace. */ 961 962 comp = n->norm->Bd.comp; 963 for (nn = n; nn != NULL && comp == 0; nn = nn->parent) { 964 if (nn->type != ROFFT_BLOCK) 965 continue; 966 if (nn->tok == MDOC_Sh || nn->tok == MDOC_Ss) 967 comp = 1; 968 if (roff_node_prev(nn) != NULL) 969 break; 970 } 971 (void)strlcpy(buf, "Bd", sizeof(buf)); 972 if (comp == 0) 973 (void)strlcat(buf, " Pp", sizeof(buf)); 974 975 /* Handle the -offset argument. */ 976 977 if (n->norm->Bd.offs != NULL && 978 strcmp(n->norm->Bd.offs, "left") != 0) 979 (void)strlcat(buf, " Bd-indent", sizeof(buf)); 980 981 if (n->norm->Bd.type == DISP_literal) 982 (void)strlcat(buf, " Li", sizeof(buf)); 983 984 print_otag_id(h, TAG_DIV, buf, n); 985 return 1; 986 } 987 988 static int 989 mdoc_pa_pre(MDOC_ARGS) 990 { 991 print_otag(h, TAG_SPAN, "c", "Pa"); 992 return 1; 993 } 994 995 static int 996 mdoc_ad_pre(MDOC_ARGS) 997 { 998 print_otag(h, TAG_SPAN, "c", "Ad"); 999 return 1; 1000 } 1001 1002 static int 1003 mdoc_an_pre(MDOC_ARGS) 1004 { 1005 if (n->norm->An.auth == AUTH_split) { 1006 h->flags &= ~HTML_NOSPLIT; 1007 h->flags |= HTML_SPLIT; 1008 return 0; 1009 } 1010 if (n->norm->An.auth == AUTH_nosplit) { 1011 h->flags &= ~HTML_SPLIT; 1012 h->flags |= HTML_NOSPLIT; 1013 return 0; 1014 } 1015 1016 if (h->flags & HTML_SPLIT) 1017 print_otag(h, TAG_BR, ""); 1018 1019 if (n->sec == SEC_AUTHORS && ! (h->flags & HTML_NOSPLIT)) 1020 h->flags |= HTML_SPLIT; 1021 1022 print_otag(h, TAG_SPAN, "c", "An"); 1023 return 1; 1024 } 1025 1026 static int 1027 mdoc_cd_pre(MDOC_ARGS) 1028 { 1029 synopsis_pre(h, n); 1030 print_otag(h, TAG_CODE, "c", "Cd"); 1031 return 1; 1032 } 1033 1034 static int 1035 mdoc_fa_pre(MDOC_ARGS) 1036 { 1037 const struct roff_node *nn; 1038 struct tag *t; 1039 1040 if (n->parent->tok != MDOC_Fo) { 1041 print_otag(h, TAG_VAR, "c", "Fa"); 1042 return 1; 1043 } 1044 for (nn = n->child; nn != NULL; nn = nn->next) { 1045 t = print_otag(h, TAG_VAR, "c", "Fa"); 1046 print_text(h, nn->string); 1047 print_tagq(h, t); 1048 if (nn->next != NULL) { 1049 h->flags |= HTML_NOSPACE; 1050 print_text(h, ","); 1051 } 1052 } 1053 if (n->child != NULL && 1054 (nn = roff_node_next(n)) != NULL && 1055 nn->tok == MDOC_Fa) { 1056 h->flags |= HTML_NOSPACE; 1057 print_text(h, ","); 1058 } 1059 return 0; 1060 } 1061 1062 static int 1063 mdoc_fd_pre(MDOC_ARGS) 1064 { 1065 struct tag *t; 1066 char *buf, *cp; 1067 1068 synopsis_pre(h, n); 1069 1070 if (NULL == (n = n->child)) 1071 return 0; 1072 1073 assert(n->type == ROFFT_TEXT); 1074 1075 if (strcmp(n->string, "#include")) { 1076 print_otag(h, TAG_CODE, "c", "Fd"); 1077 return 1; 1078 } 1079 1080 print_otag(h, TAG_CODE, "c", "In"); 1081 print_text(h, n->string); 1082 1083 if (NULL != (n = n->next)) { 1084 assert(n->type == ROFFT_TEXT); 1085 1086 if (h->base_includes) { 1087 cp = n->string; 1088 if (*cp == '<' || *cp == '"') 1089 cp++; 1090 buf = mandoc_strdup(cp); 1091 cp = strchr(buf, '\0') - 1; 1092 if (cp >= buf && (*cp == '>' || *cp == '"')) 1093 *cp = '\0'; 1094 t = print_otag(h, TAG_A, "chI", "In", buf); 1095 free(buf); 1096 } else 1097 t = print_otag(h, TAG_A, "c", "In"); 1098 1099 print_text(h, n->string); 1100 print_tagq(h, t); 1101 1102 n = n->next; 1103 } 1104 1105 for ( ; n; n = n->next) { 1106 assert(n->type == ROFFT_TEXT); 1107 print_text(h, n->string); 1108 } 1109 1110 return 0; 1111 } 1112 1113 static int 1114 mdoc_vt_pre(MDOC_ARGS) 1115 { 1116 if (n->type == ROFFT_BLOCK) { 1117 synopsis_pre(h, n); 1118 return 1; 1119 } else if (n->type == ROFFT_ELEM) { 1120 synopsis_pre(h, n); 1121 } else if (n->type == ROFFT_HEAD) 1122 return 0; 1123 1124 print_otag(h, TAG_VAR, "c", "Vt"); 1125 return 1; 1126 } 1127 1128 static int 1129 mdoc_ft_pre(MDOC_ARGS) 1130 { 1131 synopsis_pre(h, n); 1132 print_otag(h, TAG_VAR, "c", "Ft"); 1133 return 1; 1134 } 1135 1136 static int 1137 mdoc_fn_pre(MDOC_ARGS) 1138 { 1139 struct tag *t; 1140 char nbuf[BUFSIZ]; 1141 const char *sp, *ep; 1142 int sz, pretty; 1143 1144 pretty = NODE_SYNPRETTY & n->flags; 1145 synopsis_pre(h, n); 1146 1147 /* Split apart into type and name. */ 1148 assert(n->child->string); 1149 sp = n->child->string; 1150 1151 ep = strchr(sp, ' '); 1152 if (NULL != ep) { 1153 t = print_otag(h, TAG_VAR, "c", "Ft"); 1154 1155 while (ep) { 1156 sz = MIN((int)(ep - sp), BUFSIZ - 1); 1157 (void)memcpy(nbuf, sp, (size_t)sz); 1158 nbuf[sz] = '\0'; 1159 print_text(h, nbuf); 1160 sp = ++ep; 1161 ep = strchr(sp, ' '); 1162 } 1163 print_tagq(h, t); 1164 } 1165 1166 t = print_otag_id(h, TAG_CODE, "Fn", n); 1167 1168 if (sp) 1169 print_text(h, sp); 1170 1171 print_tagq(h, t); 1172 1173 h->flags |= HTML_NOSPACE; 1174 print_text(h, "("); 1175 h->flags |= HTML_NOSPACE; 1176 1177 for (n = n->child->next; n; n = n->next) { 1178 if (NODE_SYNPRETTY & n->flags) 1179 t = print_otag(h, TAG_VAR, "cs", "Fa", 1180 "white-space", "nowrap"); 1181 else 1182 t = print_otag(h, TAG_VAR, "c", "Fa"); 1183 print_text(h, n->string); 1184 print_tagq(h, t); 1185 if (n->next) { 1186 h->flags |= HTML_NOSPACE; 1187 print_text(h, ","); 1188 } 1189 } 1190 1191 h->flags |= HTML_NOSPACE; 1192 print_text(h, ")"); 1193 1194 if (pretty) { 1195 h->flags |= HTML_NOSPACE; 1196 print_text(h, ";"); 1197 } 1198 1199 return 0; 1200 } 1201 1202 static int 1203 mdoc_sm_pre(MDOC_ARGS) 1204 { 1205 1206 if (NULL == n->child) 1207 h->flags ^= HTML_NONOSPACE; 1208 else if (0 == strcmp("on", n->child->string)) 1209 h->flags &= ~HTML_NONOSPACE; 1210 else 1211 h->flags |= HTML_NONOSPACE; 1212 1213 if ( ! (HTML_NONOSPACE & h->flags)) 1214 h->flags &= ~HTML_NOSPACE; 1215 1216 return 0; 1217 } 1218 1219 static int 1220 mdoc_skip_pre(MDOC_ARGS) 1221 { 1222 1223 return 0; 1224 } 1225 1226 static int 1227 mdoc_pp_pre(MDOC_ARGS) 1228 { 1229 char *id; 1230 1231 if (n->flags & NODE_NOFILL) { 1232 print_endline(h); 1233 if (n->flags & NODE_ID) 1234 mdoc_tg_pre(meta, n, h); 1235 else { 1236 h->col = 1; 1237 print_endline(h); 1238 } 1239 } else { 1240 html_close_paragraph(h); 1241 id = n->flags & NODE_ID ? html_make_id(n, 1) : NULL; 1242 print_otag(h, TAG_P, "ci", "Pp", id); 1243 free(id); 1244 } 1245 return 0; 1246 } 1247 1248 static int 1249 mdoc_lk_pre(MDOC_ARGS) 1250 { 1251 const struct roff_node *link, *descr, *punct; 1252 struct tag *t; 1253 1254 if ((link = n->child) == NULL) 1255 return 0; 1256 1257 /* Find beginning of trailing punctuation. */ 1258 punct = n->last; 1259 while (punct != link && punct->flags & NODE_DELIMC) 1260 punct = punct->prev; 1261 punct = punct->next; 1262 1263 /* Link target and link text. */ 1264 descr = link->next; 1265 if (descr == punct) 1266 descr = link; /* no text */ 1267 t = print_otag(h, TAG_A, "ch", "Lk", link->string); 1268 do { 1269 if (descr->flags & (NODE_DELIMC | NODE_DELIMO)) 1270 h->flags |= HTML_NOSPACE; 1271 print_text(h, descr->string); 1272 descr = descr->next; 1273 } while (descr != punct); 1274 print_tagq(h, t); 1275 1276 /* Trailing punctuation. */ 1277 while (punct != NULL) { 1278 h->flags |= HTML_NOSPACE; 1279 print_text(h, punct->string); 1280 punct = punct->next; 1281 } 1282 return 0; 1283 } 1284 1285 static int 1286 mdoc_mt_pre(MDOC_ARGS) 1287 { 1288 struct tag *t; 1289 char *cp; 1290 1291 for (n = n->child; n; n = n->next) { 1292 assert(n->type == ROFFT_TEXT); 1293 mandoc_asprintf(&cp, "mailto:%s", n->string); 1294 t = print_otag(h, TAG_A, "ch", "Mt", cp); 1295 print_text(h, n->string); 1296 print_tagq(h, t); 1297 free(cp); 1298 } 1299 return 0; 1300 } 1301 1302 static int 1303 mdoc_fo_pre(MDOC_ARGS) 1304 { 1305 struct tag *t; 1306 1307 switch (n->type) { 1308 case ROFFT_BLOCK: 1309 synopsis_pre(h, n); 1310 return 1; 1311 case ROFFT_HEAD: 1312 if (n->child != NULL) { 1313 t = print_otag_id(h, TAG_CODE, "Fn", n); 1314 print_text(h, n->child->string); 1315 print_tagq(h, t); 1316 } 1317 return 0; 1318 case ROFFT_BODY: 1319 h->flags |= HTML_NOSPACE; 1320 print_text(h, "("); 1321 h->flags |= HTML_NOSPACE; 1322 return 1; 1323 default: 1324 abort(); 1325 } 1326 } 1327 1328 static void 1329 mdoc_fo_post(MDOC_ARGS) 1330 { 1331 if (n->type != ROFFT_BODY) 1332 return; 1333 h->flags |= HTML_NOSPACE; 1334 print_text(h, ")"); 1335 h->flags |= HTML_NOSPACE; 1336 print_text(h, ";"); 1337 } 1338 1339 static int 1340 mdoc_in_pre(MDOC_ARGS) 1341 { 1342 struct tag *t; 1343 1344 synopsis_pre(h, n); 1345 print_otag(h, TAG_CODE, "c", "In"); 1346 1347 /* 1348 * The first argument of the `In' gets special treatment as 1349 * being a linked value. Subsequent values are printed 1350 * afterward. groff does similarly. This also handles the case 1351 * of no children. 1352 */ 1353 1354 if (NODE_SYNPRETTY & n->flags && NODE_LINE & n->flags) 1355 print_text(h, "#include"); 1356 1357 print_text(h, "<"); 1358 h->flags |= HTML_NOSPACE; 1359 1360 if (NULL != (n = n->child)) { 1361 assert(n->type == ROFFT_TEXT); 1362 1363 if (h->base_includes) 1364 t = print_otag(h, TAG_A, "chI", "In", n->string); 1365 else 1366 t = print_otag(h, TAG_A, "c", "In"); 1367 print_text(h, n->string); 1368 print_tagq(h, t); 1369 1370 n = n->next; 1371 } 1372 1373 h->flags |= HTML_NOSPACE; 1374 print_text(h, ">"); 1375 1376 for ( ; n; n = n->next) { 1377 assert(n->type == ROFFT_TEXT); 1378 print_text(h, n->string); 1379 } 1380 return 0; 1381 } 1382 1383 static int 1384 mdoc_va_pre(MDOC_ARGS) 1385 { 1386 print_otag(h, TAG_VAR, "c", "Va"); 1387 return 1; 1388 } 1389 1390 static int 1391 mdoc_ap_pre(MDOC_ARGS) 1392 { 1393 h->flags |= HTML_NOSPACE; 1394 print_text(h, "\\(aq"); 1395 h->flags |= HTML_NOSPACE; 1396 return 1; 1397 } 1398 1399 static int 1400 mdoc_bf_pre(MDOC_ARGS) 1401 { 1402 const char *cattr; 1403 1404 switch (n->type) { 1405 case ROFFT_BLOCK: 1406 html_close_paragraph(h); 1407 return 1; 1408 case ROFFT_HEAD: 1409 return 0; 1410 case ROFFT_BODY: 1411 break; 1412 default: 1413 abort(); 1414 } 1415 1416 if (FONT_Em == n->norm->Bf.font) 1417 cattr = "Bf Em"; 1418 else if (FONT_Sy == n->norm->Bf.font) 1419 cattr = "Bf Sy"; 1420 else if (FONT_Li == n->norm->Bf.font) 1421 cattr = "Bf Li"; 1422 else 1423 cattr = "Bf No"; 1424 1425 /* Cannot use TAG_SPAN because it may contain blocks. */ 1426 print_otag(h, TAG_DIV, "c", cattr); 1427 return 1; 1428 } 1429 1430 static int 1431 mdoc_igndelim_pre(MDOC_ARGS) 1432 { 1433 h->flags |= HTML_IGNDELIM; 1434 return 1; 1435 } 1436 1437 static void 1438 mdoc_pf_post(MDOC_ARGS) 1439 { 1440 if ( ! (n->next == NULL || n->next->flags & NODE_LINE)) 1441 h->flags |= HTML_NOSPACE; 1442 } 1443 1444 static int 1445 mdoc_rs_pre(MDOC_ARGS) 1446 { 1447 switch (n->type) { 1448 case ROFFT_BLOCK: 1449 if (n->sec == SEC_SEE_ALSO) 1450 html_close_paragraph(h); 1451 break; 1452 case ROFFT_HEAD: 1453 return 0; 1454 case ROFFT_BODY: 1455 if (n->sec == SEC_SEE_ALSO) 1456 print_otag(h, TAG_P, "c", "Pp"); 1457 print_otag(h, TAG_SPAN, "c", "Rs"); 1458 break; 1459 default: 1460 abort(); 1461 } 1462 return 1; 1463 } 1464 1465 static int 1466 mdoc_no_pre(MDOC_ARGS) 1467 { 1468 print_otag_id(h, TAG_SPAN, roff_name[n->tok], n); 1469 return 1; 1470 } 1471 1472 static int 1473 mdoc_sy_pre(MDOC_ARGS) 1474 { 1475 print_otag_id(h, TAG_B, "Sy", n); 1476 return 1; 1477 } 1478 1479 static int 1480 mdoc_lb_pre(MDOC_ARGS) 1481 { 1482 if (n->sec == SEC_LIBRARY && 1483 n->flags & NODE_LINE && 1484 roff_node_prev(n) != NULL) 1485 print_otag(h, TAG_BR, ""); 1486 1487 print_otag(h, TAG_SPAN, "c", "Lb"); 1488 return 1; 1489 } 1490 1491 static int 1492 mdoc__x_pre(MDOC_ARGS) 1493 { 1494 struct roff_node *nn; 1495 const unsigned char *cp; 1496 const char *cattr, *arg; 1497 char *url; 1498 enum htmltag t; 1499 1500 t = TAG_SPAN; 1501 arg = n->child->string; 1502 1503 switch (n->tok) { 1504 case MDOC__A: 1505 cattr = "RsA"; 1506 if ((nn = roff_node_prev(n)) != NULL && nn->tok == MDOC__A && 1507 ((nn = roff_node_next(n)) == NULL || nn->tok != MDOC__A)) 1508 print_text(h, "and"); 1509 break; 1510 case MDOC__B: 1511 t = TAG_CITE; 1512 cattr = "RsB"; 1513 break; 1514 case MDOC__C: 1515 cattr = "RsC"; 1516 break; 1517 case MDOC__D: 1518 cattr = "RsD"; 1519 break; 1520 case MDOC__I: 1521 t = TAG_I; 1522 cattr = "RsI"; 1523 break; 1524 case MDOC__J: 1525 t = TAG_I; 1526 cattr = "RsJ"; 1527 break; 1528 case MDOC__N: 1529 cattr = "RsN"; 1530 break; 1531 case MDOC__O: 1532 cattr = "RsO"; 1533 break; 1534 case MDOC__P: 1535 cattr = "RsP"; 1536 break; 1537 case MDOC__Q: 1538 cattr = "RsQ"; 1539 break; 1540 case MDOC__R: 1541 if (strncmp(arg, "RFC ", 4) == 0) { 1542 cp = arg += 4; 1543 while (isdigit(*cp)) 1544 cp++; 1545 if (*cp == '\0') { 1546 mandoc_asprintf(&url, "https://www.rfc-" 1547 "editor.org/rfc/rfc%s.html", arg); 1548 print_otag(h, TAG_A, "ch", "RsR", url); 1549 free(url); 1550 return 1; 1551 } 1552 } 1553 cattr = "RsR"; 1554 break; 1555 case MDOC__T: 1556 t = TAG_CITE; 1557 if (n->parent != NULL && n->parent->tok == MDOC_Rs && 1558 n->parent->norm->Rs.quote_T) { 1559 print_text(h, "\\(lq"); 1560 h->flags |= HTML_NOSPACE; 1561 cattr = "RsT"; 1562 } else 1563 cattr = "RsB"; 1564 break; 1565 case MDOC__U: 1566 print_otag(h, TAG_A, "ch", "RsU", arg); 1567 return 1; 1568 case MDOC__V: 1569 cattr = "RsV"; 1570 break; 1571 default: 1572 abort(); 1573 } 1574 1575 print_otag(h, t, "c", cattr); 1576 return 1; 1577 } 1578 1579 static void 1580 mdoc__x_post(MDOC_ARGS) 1581 { 1582 struct roff_node *nn; 1583 1584 switch (n->tok) { 1585 case MDOC__A: 1586 if ((nn = roff_node_next(n)) != NULL && nn->tok == MDOC__A && 1587 ((nn = roff_node_next(nn)) == NULL || nn->tok != MDOC__A) && 1588 ((nn = roff_node_prev(n)) == NULL || nn->tok != MDOC__A)) 1589 return; 1590 break; 1591 case MDOC__T: 1592 if (n->parent != NULL && n->parent->tok == MDOC_Rs && 1593 n->parent->norm->Rs.quote_T) { 1594 h->flags |= HTML_NOSPACE; 1595 print_text(h, "\\(rq"); 1596 } 1597 break; 1598 default: 1599 break; 1600 } 1601 if (n->parent == NULL || n->parent->tok != MDOC_Rs) 1602 return; 1603 1604 h->flags |= HTML_NOSPACE; 1605 print_text(h, roff_node_next(n) ? "," : "."); 1606 } 1607 1608 static int 1609 mdoc_bk_pre(MDOC_ARGS) 1610 { 1611 1612 switch (n->type) { 1613 case ROFFT_BLOCK: 1614 break; 1615 case ROFFT_HEAD: 1616 return 0; 1617 case ROFFT_BODY: 1618 if (n->parent->args != NULL || n->prev->child == NULL) 1619 h->flags |= HTML_PREKEEP; 1620 break; 1621 default: 1622 abort(); 1623 } 1624 1625 return 1; 1626 } 1627 1628 static void 1629 mdoc_bk_post(MDOC_ARGS) 1630 { 1631 1632 if (n->type == ROFFT_BODY) 1633 h->flags &= ~(HTML_KEEP | HTML_PREKEEP); 1634 } 1635 1636 static int 1637 mdoc_quote_pre(MDOC_ARGS) 1638 { 1639 if (n->type != ROFFT_BODY) 1640 return 1; 1641 1642 switch (n->tok) { 1643 case MDOC_Ao: 1644 case MDOC_Aq: 1645 print_text(h, n->child != NULL && n->child->next == NULL && 1646 n->child->tok == MDOC_Mt ? "<" : "\\(la"); 1647 break; 1648 case MDOC_Bro: 1649 case MDOC_Brq: 1650 print_text(h, "\\(lC"); 1651 break; 1652 case MDOC_Bo: 1653 case MDOC_Bq: 1654 print_text(h, "\\(lB"); 1655 break; 1656 case MDOC_Oo: 1657 case MDOC_Op: 1658 print_text(h, "\\(lB"); 1659 /* 1660 * Give up on semantic markup for now. 1661 * We cannot use TAG_SPAN because .Oo may contain blocks. 1662 * We cannot use TAG_DIV because we might be in a 1663 * phrasing context (like .Dl or .Pp); we cannot 1664 * close out a .Pp at this point either because 1665 * that would break the line. 1666 */ 1667 /* XXX print_otag(h, TAG_???, "c", "Op"); */ 1668 break; 1669 case MDOC_En: 1670 if (NULL == n->norm->Es || 1671 NULL == n->norm->Es->child) 1672 return 1; 1673 print_text(h, n->norm->Es->child->string); 1674 break; 1675 case MDOC_Do: 1676 case MDOC_Dq: 1677 print_text(h, "\\(lq"); 1678 break; 1679 case MDOC_Qo: 1680 case MDOC_Qq: 1681 print_text(h, "\""); 1682 break; 1683 case MDOC_Po: 1684 case MDOC_Pq: 1685 print_text(h, "("); 1686 break; 1687 case MDOC_Ql: 1688 print_text(h, "\\(oq"); 1689 h->flags |= HTML_NOSPACE; 1690 print_otag(h, TAG_CODE, "c", "Li"); 1691 break; 1692 case MDOC_So: 1693 case MDOC_Sq: 1694 print_text(h, "\\(oq"); 1695 break; 1696 default: 1697 abort(); 1698 } 1699 1700 h->flags |= HTML_NOSPACE; 1701 return 1; 1702 } 1703 1704 static void 1705 mdoc_quote_post(MDOC_ARGS) 1706 { 1707 1708 if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM) 1709 return; 1710 1711 h->flags |= HTML_NOSPACE; 1712 1713 switch (n->tok) { 1714 case MDOC_Ao: 1715 case MDOC_Aq: 1716 print_text(h, n->child != NULL && n->child->next == NULL && 1717 n->child->tok == MDOC_Mt ? ">" : "\\(ra"); 1718 break; 1719 case MDOC_Bro: 1720 case MDOC_Brq: 1721 print_text(h, "\\(rC"); 1722 break; 1723 case MDOC_Oo: 1724 case MDOC_Op: 1725 case MDOC_Bo: 1726 case MDOC_Bq: 1727 print_text(h, "\\(rB"); 1728 break; 1729 case MDOC_En: 1730 if (n->norm->Es == NULL || 1731 n->norm->Es->child == NULL || 1732 n->norm->Es->child->next == NULL) 1733 h->flags &= ~HTML_NOSPACE; 1734 else 1735 print_text(h, n->norm->Es->child->next->string); 1736 break; 1737 case MDOC_Do: 1738 case MDOC_Dq: 1739 print_text(h, "\\(rq"); 1740 break; 1741 case MDOC_Qo: 1742 case MDOC_Qq: 1743 print_text(h, "\""); 1744 break; 1745 case MDOC_Po: 1746 case MDOC_Pq: 1747 print_text(h, ")"); 1748 break; 1749 case MDOC_Ql: 1750 case MDOC_So: 1751 case MDOC_Sq: 1752 print_text(h, "\\(cq"); 1753 break; 1754 default: 1755 abort(); 1756 } 1757 } 1758 1759 static int 1760 mdoc_eo_pre(MDOC_ARGS) 1761 { 1762 1763 if (n->type != ROFFT_BODY) 1764 return 1; 1765 1766 if (n->end == ENDBODY_NOT && 1767 n->parent->head->child == NULL && 1768 n->child != NULL && 1769 n->child->end != ENDBODY_NOT) 1770 print_text(h, "\\&"); 1771 else if (n->end != ENDBODY_NOT ? n->child != NULL : 1772 n->parent->head->child != NULL && (n->child != NULL || 1773 (n->parent->tail != NULL && n->parent->tail->child != NULL))) 1774 h->flags |= HTML_NOSPACE; 1775 return 1; 1776 } 1777 1778 static void 1779 mdoc_eo_post(MDOC_ARGS) 1780 { 1781 int body, tail; 1782 1783 if (n->type != ROFFT_BODY) 1784 return; 1785 1786 if (n->end != ENDBODY_NOT) { 1787 h->flags &= ~HTML_NOSPACE; 1788 return; 1789 } 1790 1791 body = n->child != NULL || n->parent->head->child != NULL; 1792 tail = n->parent->tail != NULL && n->parent->tail->child != NULL; 1793 1794 if (body && tail) 1795 h->flags |= HTML_NOSPACE; 1796 else if ( ! tail) 1797 h->flags &= ~HTML_NOSPACE; 1798 } 1799 1800 static int 1801 mdoc_abort_pre(MDOC_ARGS) 1802 { 1803 abort(); 1804 } 1805