1 /* $OpenBSD: man_html.c,v 1.123 2019/01/18 14:36:16 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2013-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 20 #include <assert.h> 21 #include <ctype.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <string.h> 25 26 #include "mandoc_aux.h" 27 #include "mandoc.h" 28 #include "roff.h" 29 #include "man.h" 30 #include "out.h" 31 #include "html.h" 32 #include "main.h" 33 34 #define MAN_ARGS const struct roff_meta *man, \ 35 const struct roff_node *n, \ 36 struct html *h 37 38 struct man_html_act { 39 int (*pre)(MAN_ARGS); 40 int (*post)(MAN_ARGS); 41 }; 42 43 static void print_man_head(const struct roff_meta *, 44 struct html *); 45 static void print_man_nodelist(MAN_ARGS); 46 static void print_man_node(MAN_ARGS); 47 static int man_B_pre(MAN_ARGS); 48 static int man_IP_pre(MAN_ARGS); 49 static int man_I_pre(MAN_ARGS); 50 static int man_OP_pre(MAN_ARGS); 51 static int man_PP_pre(MAN_ARGS); 52 static int man_RS_pre(MAN_ARGS); 53 static int man_SH_pre(MAN_ARGS); 54 static int man_SM_pre(MAN_ARGS); 55 static int man_SY_pre(MAN_ARGS); 56 static int man_UR_pre(MAN_ARGS); 57 static int man_abort_pre(MAN_ARGS); 58 static int man_alt_pre(MAN_ARGS); 59 static int man_ign_pre(MAN_ARGS); 60 static int man_in_pre(MAN_ARGS); 61 static void man_root_post(const struct roff_meta *, 62 struct html *); 63 static void man_root_pre(const struct roff_meta *, 64 struct html *); 65 66 static const struct man_html_act man_html_acts[MAN_MAX - MAN_TH] = { 67 { NULL, NULL }, /* TH */ 68 { man_SH_pre, NULL }, /* SH */ 69 { man_SH_pre, NULL }, /* SS */ 70 { man_IP_pre, NULL }, /* TP */ 71 { man_IP_pre, NULL }, /* TQ */ 72 { man_abort_pre, NULL }, /* LP */ 73 { man_PP_pre, NULL }, /* PP */ 74 { man_abort_pre, NULL }, /* P */ 75 { man_IP_pre, NULL }, /* IP */ 76 { man_PP_pre, NULL }, /* HP */ 77 { man_SM_pre, NULL }, /* SM */ 78 { man_SM_pre, NULL }, /* SB */ 79 { man_alt_pre, NULL }, /* BI */ 80 { man_alt_pre, NULL }, /* IB */ 81 { man_alt_pre, NULL }, /* BR */ 82 { man_alt_pre, NULL }, /* RB */ 83 { NULL, NULL }, /* R */ 84 { man_B_pre, NULL }, /* B */ 85 { man_I_pre, NULL }, /* I */ 86 { man_alt_pre, NULL }, /* IR */ 87 { man_alt_pre, NULL }, /* RI */ 88 { NULL, NULL }, /* RE */ 89 { man_RS_pre, NULL }, /* RS */ 90 { man_ign_pre, NULL }, /* DT */ 91 { man_ign_pre, NULL }, /* UC */ 92 { man_ign_pre, NULL }, /* PD */ 93 { man_ign_pre, NULL }, /* AT */ 94 { man_in_pre, NULL }, /* in */ 95 { man_SY_pre, NULL }, /* SY */ 96 { NULL, NULL }, /* YS */ 97 { man_OP_pre, NULL }, /* OP */ 98 { NULL, NULL }, /* EX */ 99 { NULL, NULL }, /* EE */ 100 { man_UR_pre, NULL }, /* UR */ 101 { NULL, NULL }, /* UE */ 102 { man_UR_pre, NULL }, /* MT */ 103 { NULL, NULL }, /* ME */ 104 }; 105 106 107 void 108 html_man(void *arg, const struct roff_meta *man) 109 { 110 struct html *h; 111 struct roff_node *n; 112 struct tag *t; 113 114 h = (struct html *)arg; 115 n = man->first->child; 116 117 if ((h->oflags & HTML_FRAGMENT) == 0) { 118 print_gen_decls(h); 119 print_otag(h, TAG_HTML, ""); 120 if (n != NULL && n->type == ROFFT_COMMENT) 121 print_gen_comment(h, n); 122 t = print_otag(h, TAG_HEAD, ""); 123 print_man_head(man, h); 124 print_tagq(h, t); 125 print_otag(h, TAG_BODY, ""); 126 } 127 128 man_root_pre(man, h); 129 t = print_otag(h, TAG_DIV, "c", "manual-text"); 130 print_man_nodelist(man, n, h); 131 print_tagq(h, t); 132 man_root_post(man, h); 133 print_tagq(h, NULL); 134 } 135 136 static void 137 print_man_head(const struct roff_meta *man, struct html *h) 138 { 139 char *cp; 140 141 print_gen_head(h); 142 mandoc_asprintf(&cp, "%s(%s)", man->title, man->msec); 143 print_otag(h, TAG_TITLE, ""); 144 print_text(h, cp); 145 free(cp); 146 } 147 148 static void 149 print_man_nodelist(MAN_ARGS) 150 { 151 while (n != NULL) { 152 print_man_node(man, n, h); 153 n = n->next; 154 } 155 } 156 157 static void 158 print_man_node(MAN_ARGS) 159 { 160 struct tag *t; 161 int child; 162 163 if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT) 164 return; 165 166 html_fillmode(h, n->flags & NODE_NOFILL ? ROFF_nf : ROFF_fi); 167 168 child = 1; 169 switch (n->type) { 170 case ROFFT_TEXT: 171 if (*n->string == '\0') { 172 print_endline(h); 173 return; 174 } 175 if (*n->string == ' ' && n->flags & NODE_LINE && 176 (h->flags & HTML_NONEWLINE) == 0) 177 print_endline(h); 178 else if (n->flags & NODE_DELIMC) 179 h->flags |= HTML_NOSPACE; 180 t = h->tag; 181 t->refcnt++; 182 print_text(h, n->string); 183 break; 184 case ROFFT_EQN: 185 t = h->tag; 186 t->refcnt++; 187 print_eqn(h, n->eqn); 188 break; 189 case ROFFT_TBL: 190 /* 191 * This will take care of initialising all of the table 192 * state data for the first table, then tearing it down 193 * for the last one. 194 */ 195 print_tbl(h, n->span); 196 return; 197 default: 198 /* 199 * Close out scope of font prior to opening a macro 200 * scope. 201 */ 202 if (HTMLFONT_NONE != h->metac) { 203 h->metal = h->metac; 204 h->metac = HTMLFONT_NONE; 205 } 206 207 /* 208 * Close out the current table, if it's open, and unset 209 * the "meta" table state. This will be reopened on the 210 * next table element. 211 */ 212 if (h->tblt != NULL) 213 print_tblclose(h); 214 t = h->tag; 215 t->refcnt++; 216 if (n->tok < ROFF_MAX) { 217 roff_html_pre(h, n); 218 t->refcnt--; 219 print_stagq(h, t); 220 return; 221 } 222 assert(n->tok >= MAN_TH && n->tok < MAN_MAX); 223 if (man_html_acts[n->tok - MAN_TH].pre != NULL) 224 child = (*man_html_acts[n->tok - MAN_TH].pre)(man, 225 n, h); 226 break; 227 } 228 229 if (child && n->child != NULL) 230 print_man_nodelist(man, n->child, h); 231 232 /* This will automatically close out any font scope. */ 233 t->refcnt--; 234 print_stagq(h, t); 235 236 if (n->flags & NODE_NOFILL && n->tok != MAN_YS && 237 (n->next != NULL && n->next->flags & NODE_LINE)) { 238 /* In .nf = <pre>, print even empty lines. */ 239 h->col++; 240 print_endline(h); 241 } 242 } 243 244 static void 245 man_root_pre(const struct roff_meta *man, struct html *h) 246 { 247 struct tag *t, *tt; 248 char *title; 249 250 assert(man->title); 251 assert(man->msec); 252 mandoc_asprintf(&title, "%s(%s)", man->title, man->msec); 253 254 t = print_otag(h, TAG_TABLE, "c", "head"); 255 tt = print_otag(h, TAG_TR, ""); 256 257 print_otag(h, TAG_TD, "c", "head-ltitle"); 258 print_text(h, title); 259 print_stagq(h, tt); 260 261 print_otag(h, TAG_TD, "c", "head-vol"); 262 if (man->vol != NULL) 263 print_text(h, man->vol); 264 print_stagq(h, tt); 265 266 print_otag(h, TAG_TD, "c", "head-rtitle"); 267 print_text(h, title); 268 print_tagq(h, t); 269 free(title); 270 } 271 272 static void 273 man_root_post(const struct roff_meta *man, struct html *h) 274 { 275 struct tag *t, *tt; 276 277 t = print_otag(h, TAG_TABLE, "c", "foot"); 278 tt = print_otag(h, TAG_TR, ""); 279 280 print_otag(h, TAG_TD, "c", "foot-date"); 281 print_text(h, man->date); 282 print_stagq(h, tt); 283 284 print_otag(h, TAG_TD, "c", "foot-os"); 285 if (man->os != NULL) 286 print_text(h, man->os); 287 print_tagq(h, t); 288 } 289 290 static int 291 man_SH_pre(MAN_ARGS) 292 { 293 char *id; 294 295 switch (n->type) { 296 case ROFFT_BLOCK: 297 html_close_paragraph(h); 298 break; 299 case ROFFT_HEAD: 300 id = html_make_id(n, 1); 301 if (n->tok == MAN_SH) 302 print_otag(h, TAG_H1, "ci", "Sh", id); 303 else 304 print_otag(h, TAG_H2, "ci", "Ss", id); 305 if (id != NULL) 306 print_otag(h, TAG_A, "chR", "permalink", id); 307 break; 308 case ROFFT_BODY: 309 break; 310 default: 311 abort(); 312 } 313 return 1; 314 } 315 316 static int 317 man_alt_pre(MAN_ARGS) 318 { 319 const struct roff_node *nn; 320 struct tag *t; 321 int i; 322 enum htmltag fp; 323 324 for (i = 0, nn = n->child; nn != NULL; nn = nn->next, i++) { 325 switch (n->tok) { 326 case MAN_BI: 327 fp = i % 2 ? TAG_I : TAG_B; 328 break; 329 case MAN_IB: 330 fp = i % 2 ? TAG_B : TAG_I; 331 break; 332 case MAN_RI: 333 fp = i % 2 ? TAG_I : TAG_MAX; 334 break; 335 case MAN_IR: 336 fp = i % 2 ? TAG_MAX : TAG_I; 337 break; 338 case MAN_BR: 339 fp = i % 2 ? TAG_MAX : TAG_B; 340 break; 341 case MAN_RB: 342 fp = i % 2 ? TAG_B : TAG_MAX; 343 break; 344 default: 345 abort(); 346 } 347 348 if (i) 349 h->flags |= HTML_NOSPACE; 350 351 if (fp != TAG_MAX) 352 t = print_otag(h, fp, ""); 353 354 print_text(h, nn->string); 355 356 if (fp != TAG_MAX) 357 print_tagq(h, t); 358 } 359 return 0; 360 } 361 362 static int 363 man_SM_pre(MAN_ARGS) 364 { 365 print_otag(h, TAG_SMALL, ""); 366 if (n->tok == MAN_SB) 367 print_otag(h, TAG_B, ""); 368 return 1; 369 } 370 371 static int 372 man_PP_pre(MAN_ARGS) 373 { 374 switch (n->type) { 375 case ROFFT_BLOCK: 376 html_close_paragraph(h); 377 break; 378 case ROFFT_HEAD: 379 return 0; 380 case ROFFT_BODY: 381 if (n->child != NULL && 382 (n->child->flags & NODE_NOFILL) == 0) 383 print_otag(h, TAG_P, "c", 384 n->tok == MAN_PP ? "Pp" : "Pp HP"); 385 break; 386 default: 387 abort(); 388 } 389 return 1; 390 } 391 392 static int 393 man_IP_pre(MAN_ARGS) 394 { 395 const struct roff_node *nn; 396 397 switch (n->type) { 398 case ROFFT_BLOCK: 399 html_close_paragraph(h); 400 print_otag(h, TAG_DL, "c", "Bl-tag"); 401 return 1; 402 case ROFFT_HEAD: 403 print_otag(h, TAG_DT, ""); 404 break; 405 case ROFFT_BODY: 406 print_otag(h, TAG_DD, ""); 407 return 1; 408 default: 409 abort(); 410 } 411 412 switch(n->tok) { 413 case MAN_IP: /* Only print the first header element. */ 414 if (n->child != NULL) 415 print_man_node(man, n->child, h); 416 break; 417 case MAN_TP: /* Only print next-line header elements. */ 418 case MAN_TQ: 419 nn = n->child; 420 while (nn != NULL && (NODE_LINE & nn->flags) == 0) 421 nn = nn->next; 422 while (nn != NULL) { 423 print_man_node(man, nn, h); 424 nn = nn->next; 425 } 426 break; 427 default: 428 abort(); 429 } 430 return 0; 431 } 432 433 static int 434 man_OP_pre(MAN_ARGS) 435 { 436 struct tag *tt; 437 438 print_text(h, "["); 439 h->flags |= HTML_NOSPACE; 440 tt = print_otag(h, TAG_SPAN, "c", "Op"); 441 442 if ((n = n->child) != NULL) { 443 print_otag(h, TAG_B, ""); 444 print_text(h, n->string); 445 } 446 447 print_stagq(h, tt); 448 449 if (n != NULL && n->next != NULL) { 450 print_otag(h, TAG_I, ""); 451 print_text(h, n->next->string); 452 } 453 454 print_stagq(h, tt); 455 h->flags |= HTML_NOSPACE; 456 print_text(h, "]"); 457 return 0; 458 } 459 460 static int 461 man_B_pre(MAN_ARGS) 462 { 463 print_otag(h, TAG_B, ""); 464 return 1; 465 } 466 467 static int 468 man_I_pre(MAN_ARGS) 469 { 470 print_otag(h, TAG_I, ""); 471 return 1; 472 } 473 474 static int 475 man_in_pre(MAN_ARGS) 476 { 477 print_otag(h, TAG_BR, ""); 478 return 0; 479 } 480 481 static int 482 man_ign_pre(MAN_ARGS) 483 { 484 return 0; 485 } 486 487 static int 488 man_RS_pre(MAN_ARGS) 489 { 490 switch (n->type) { 491 case ROFFT_BLOCK: 492 html_close_paragraph(h); 493 break; 494 case ROFFT_HEAD: 495 return 0; 496 case ROFFT_BODY: 497 print_otag(h, TAG_DIV, "c", "Bd-indent"); 498 break; 499 default: 500 abort(); 501 } 502 return 1; 503 } 504 505 static int 506 man_SY_pre(MAN_ARGS) 507 { 508 switch (n->type) { 509 case ROFFT_BLOCK: 510 html_close_paragraph(h); 511 print_otag(h, TAG_TABLE, "c", "Nm"); 512 print_otag(h, TAG_TR, ""); 513 break; 514 case ROFFT_HEAD: 515 print_otag(h, TAG_TD, ""); 516 print_otag(h, TAG_CODE, "c", "Nm"); 517 break; 518 case ROFFT_BODY: 519 print_otag(h, TAG_TD, ""); 520 break; 521 default: 522 abort(); 523 } 524 return 1; 525 } 526 527 static int 528 man_UR_pre(MAN_ARGS) 529 { 530 char *cp; 531 532 n = n->child; 533 assert(n->type == ROFFT_HEAD); 534 if (n->child != NULL) { 535 assert(n->child->type == ROFFT_TEXT); 536 if (n->tok == MAN_MT) { 537 mandoc_asprintf(&cp, "mailto:%s", n->child->string); 538 print_otag(h, TAG_A, "ch", "Mt", cp); 539 free(cp); 540 } else 541 print_otag(h, TAG_A, "ch", "Lk", n->child->string); 542 } 543 544 assert(n->next->type == ROFFT_BODY); 545 if (n->next->child != NULL) 546 n = n->next; 547 548 print_man_nodelist(man, n->child, h); 549 return 0; 550 } 551 552 static int 553 man_abort_pre(MAN_ARGS) 554 { 555 abort(); 556 } 557