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