1 /* $OpenBSD: man_html.c,v 1.122 2019/01/11 16:35:39 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 t = h->tag; 170 if (t->tag == TAG_P || t->tag == TAG_PRE) 171 t = t->next; 172 173 switch (n->type) { 174 case ROFFT_TEXT: 175 if (*n->string == '\0') { 176 print_endline(h); 177 return; 178 } 179 if (*n->string == ' ' && n->flags & NODE_LINE && 180 (h->flags & HTML_NONEWLINE) == 0) 181 print_endline(h); 182 else if (n->flags & NODE_DELIMC) 183 h->flags |= HTML_NOSPACE; 184 print_text(h, n->string); 185 break; 186 case ROFFT_EQN: 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 } 216 if (n->tok < ROFF_MAX) { 217 roff_html_pre(h, n); 218 print_stagq(h, t); 219 return; 220 } 221 assert(n->tok >= MAN_TH && n->tok < MAN_MAX); 222 if (man_html_acts[n->tok - MAN_TH].pre != NULL) 223 child = (*man_html_acts[n->tok - MAN_TH].pre)(man, 224 n, h); 225 break; 226 } 227 228 if (child && n->child != NULL) 229 print_man_nodelist(man, n->child, h); 230 231 /* This will automatically close out any font scope. */ 232 print_stagq(h, t); 233 234 if (n->flags & NODE_NOFILL && n->tok != MAN_YS && 235 (n->next != NULL && n->next->flags & NODE_LINE)) { 236 /* In .nf = <pre>, print even empty lines. */ 237 h->col++; 238 print_endline(h); 239 } 240 } 241 242 static void 243 man_root_pre(const struct roff_meta *man, struct html *h) 244 { 245 struct tag *t, *tt; 246 char *title; 247 248 assert(man->title); 249 assert(man->msec); 250 mandoc_asprintf(&title, "%s(%s)", man->title, man->msec); 251 252 t = print_otag(h, TAG_TABLE, "c", "head"); 253 tt = print_otag(h, TAG_TR, ""); 254 255 print_otag(h, TAG_TD, "c", "head-ltitle"); 256 print_text(h, title); 257 print_stagq(h, tt); 258 259 print_otag(h, TAG_TD, "c", "head-vol"); 260 if (man->vol != NULL) 261 print_text(h, man->vol); 262 print_stagq(h, tt); 263 264 print_otag(h, TAG_TD, "c", "head-rtitle"); 265 print_text(h, title); 266 print_tagq(h, t); 267 free(title); 268 } 269 270 static void 271 man_root_post(const struct roff_meta *man, struct html *h) 272 { 273 struct tag *t, *tt; 274 275 t = print_otag(h, TAG_TABLE, "c", "foot"); 276 tt = print_otag(h, TAG_TR, ""); 277 278 print_otag(h, TAG_TD, "c", "foot-date"); 279 print_text(h, man->date); 280 print_stagq(h, tt); 281 282 print_otag(h, TAG_TD, "c", "foot-os"); 283 if (man->os != NULL) 284 print_text(h, man->os); 285 print_tagq(h, t); 286 } 287 288 static int 289 man_SH_pre(MAN_ARGS) 290 { 291 char *id; 292 293 switch (n->type) { 294 case ROFFT_BLOCK: 295 html_close_paragraph(h); 296 break; 297 case ROFFT_HEAD: 298 id = html_make_id(n, 1); 299 if (n->tok == MAN_SH) 300 print_otag(h, TAG_H1, "ci", "Sh", id); 301 else 302 print_otag(h, TAG_H2, "ci", "Ss", id); 303 if (id != NULL) 304 print_otag(h, TAG_A, "chR", "permalink", id); 305 break; 306 case ROFFT_BODY: 307 break; 308 default: 309 abort(); 310 } 311 return 1; 312 } 313 314 static int 315 man_alt_pre(MAN_ARGS) 316 { 317 const struct roff_node *nn; 318 struct tag *t; 319 int i; 320 enum htmltag fp; 321 322 for (i = 0, nn = n->child; nn != NULL; nn = nn->next, i++) { 323 switch (n->tok) { 324 case MAN_BI: 325 fp = i % 2 ? TAG_I : TAG_B; 326 break; 327 case MAN_IB: 328 fp = i % 2 ? TAG_B : TAG_I; 329 break; 330 case MAN_RI: 331 fp = i % 2 ? TAG_I : TAG_MAX; 332 break; 333 case MAN_IR: 334 fp = i % 2 ? TAG_MAX : TAG_I; 335 break; 336 case MAN_BR: 337 fp = i % 2 ? TAG_MAX : TAG_B; 338 break; 339 case MAN_RB: 340 fp = i % 2 ? TAG_B : TAG_MAX; 341 break; 342 default: 343 abort(); 344 } 345 346 if (i) 347 h->flags |= HTML_NOSPACE; 348 349 if (fp != TAG_MAX) 350 t = print_otag(h, fp, ""); 351 352 print_text(h, nn->string); 353 354 if (fp != TAG_MAX) 355 print_tagq(h, t); 356 } 357 return 0; 358 } 359 360 static int 361 man_SM_pre(MAN_ARGS) 362 { 363 print_otag(h, TAG_SMALL, ""); 364 if (n->tok == MAN_SB) 365 print_otag(h, TAG_B, ""); 366 return 1; 367 } 368 369 static int 370 man_PP_pre(MAN_ARGS) 371 { 372 switch (n->type) { 373 case ROFFT_BLOCK: 374 html_close_paragraph(h); 375 break; 376 case ROFFT_HEAD: 377 return 0; 378 case ROFFT_BODY: 379 if (n->child != NULL && 380 (n->child->flags & NODE_NOFILL) == 0) 381 print_otag(h, TAG_P, "c", 382 n->tok == MAN_PP ? "Pp" : "Pp HP"); 383 break; 384 default: 385 abort(); 386 } 387 return 1; 388 } 389 390 static int 391 man_IP_pre(MAN_ARGS) 392 { 393 const struct roff_node *nn; 394 395 switch (n->type) { 396 case ROFFT_BLOCK: 397 html_close_paragraph(h); 398 print_otag(h, TAG_DL, "c", "Bl-tag"); 399 return 1; 400 case ROFFT_HEAD: 401 print_otag(h, TAG_DT, ""); 402 break; 403 case ROFFT_BODY: 404 print_otag(h, TAG_DD, ""); 405 return 1; 406 default: 407 abort(); 408 } 409 410 switch(n->tok) { 411 case MAN_IP: /* Only print the first header element. */ 412 if (n->child != NULL) 413 print_man_node(man, n->child, h); 414 break; 415 case MAN_TP: /* Only print next-line header elements. */ 416 case MAN_TQ: 417 nn = n->child; 418 while (nn != NULL && (NODE_LINE & nn->flags) == 0) 419 nn = nn->next; 420 while (nn != NULL) { 421 print_man_node(man, nn, h); 422 nn = nn->next; 423 } 424 break; 425 default: 426 abort(); 427 } 428 return 0; 429 } 430 431 static int 432 man_OP_pre(MAN_ARGS) 433 { 434 struct tag *tt; 435 436 print_text(h, "["); 437 h->flags |= HTML_NOSPACE; 438 tt = print_otag(h, TAG_SPAN, "c", "Op"); 439 440 if ((n = n->child) != NULL) { 441 print_otag(h, TAG_B, ""); 442 print_text(h, n->string); 443 } 444 445 print_stagq(h, tt); 446 447 if (n != NULL && n->next != NULL) { 448 print_otag(h, TAG_I, ""); 449 print_text(h, n->next->string); 450 } 451 452 print_stagq(h, tt); 453 h->flags |= HTML_NOSPACE; 454 print_text(h, "]"); 455 return 0; 456 } 457 458 static int 459 man_B_pre(MAN_ARGS) 460 { 461 print_otag(h, TAG_B, ""); 462 return 1; 463 } 464 465 static int 466 man_I_pre(MAN_ARGS) 467 { 468 print_otag(h, TAG_I, ""); 469 return 1; 470 } 471 472 static int 473 man_in_pre(MAN_ARGS) 474 { 475 print_otag(h, TAG_BR, ""); 476 return 0; 477 } 478 479 static int 480 man_ign_pre(MAN_ARGS) 481 { 482 return 0; 483 } 484 485 static int 486 man_RS_pre(MAN_ARGS) 487 { 488 switch (n->type) { 489 case ROFFT_BLOCK: 490 html_close_paragraph(h); 491 break; 492 case ROFFT_HEAD: 493 return 0; 494 case ROFFT_BODY: 495 print_otag(h, TAG_DIV, "c", "Bd-indent"); 496 break; 497 default: 498 abort(); 499 } 500 return 1; 501 } 502 503 static int 504 man_SY_pre(MAN_ARGS) 505 { 506 switch (n->type) { 507 case ROFFT_BLOCK: 508 html_close_paragraph(h); 509 print_otag(h, TAG_TABLE, "c", "Nm"); 510 print_otag(h, TAG_TR, ""); 511 break; 512 case ROFFT_HEAD: 513 print_otag(h, TAG_TD, ""); 514 print_otag(h, TAG_CODE, "c", "Nm"); 515 break; 516 case ROFFT_BODY: 517 print_otag(h, TAG_TD, ""); 518 break; 519 default: 520 abort(); 521 } 522 return 1; 523 } 524 525 static int 526 man_UR_pre(MAN_ARGS) 527 { 528 char *cp; 529 530 n = n->child; 531 assert(n->type == ROFFT_HEAD); 532 if (n->child != NULL) { 533 assert(n->child->type == ROFFT_TEXT); 534 if (n->tok == MAN_MT) { 535 mandoc_asprintf(&cp, "mailto:%s", n->child->string); 536 print_otag(h, TAG_A, "ch", "Mt", cp); 537 free(cp); 538 } else 539 print_otag(h, TAG_A, "ch", "Lk", n->child->string); 540 } 541 542 assert(n->next->type == ROFFT_BODY); 543 if (n->next->child != NULL) 544 n = n->next; 545 546 print_man_nodelist(man, n->child, h); 547 return 0; 548 } 549 550 static int 551 man_abort_pre(MAN_ARGS) 552 { 553 abort(); 554 } 555