1 /* $OpenBSD: mdoc_html.c,v 1.115 2016/01/08 17:48:04 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2014, 2015, 2016 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 #include <unistd.h> 26 27 #include "mandoc_aux.h" 28 #include "roff.h" 29 #include "mdoc.h" 30 #include "out.h" 31 #include "html.h" 32 #include "main.h" 33 34 #define INDENT 5 35 36 #define MDOC_ARGS const struct roff_meta *meta, \ 37 struct roff_node *n, \ 38 struct html *h 39 40 #ifndef MIN 41 #define MIN(a,b) ((/*CONSTCOND*/(a)<(b))?(a):(b)) 42 #endif 43 44 struct htmlmdoc { 45 int (*pre)(MDOC_ARGS); 46 void (*post)(MDOC_ARGS); 47 }; 48 49 static void print_mdoc_head(MDOC_ARGS); 50 static void print_mdoc_node(MDOC_ARGS); 51 static void print_mdoc_nodelist(MDOC_ARGS); 52 static void synopsis_pre(struct html *, 53 const struct roff_node *); 54 55 static void a2width(const char *, struct roffsu *); 56 57 static void mdoc_root_post(MDOC_ARGS); 58 static int mdoc_root_pre(MDOC_ARGS); 59 60 static void mdoc__x_post(MDOC_ARGS); 61 static int mdoc__x_pre(MDOC_ARGS); 62 static int mdoc_ad_pre(MDOC_ARGS); 63 static int mdoc_an_pre(MDOC_ARGS); 64 static int mdoc_ap_pre(MDOC_ARGS); 65 static int mdoc_ar_pre(MDOC_ARGS); 66 static int mdoc_bd_pre(MDOC_ARGS); 67 static int mdoc_bf_pre(MDOC_ARGS); 68 static void mdoc_bk_post(MDOC_ARGS); 69 static int mdoc_bk_pre(MDOC_ARGS); 70 static int mdoc_bl_pre(MDOC_ARGS); 71 static int mdoc_bt_pre(MDOC_ARGS); 72 static int mdoc_bx_pre(MDOC_ARGS); 73 static int mdoc_cd_pre(MDOC_ARGS); 74 static int mdoc_d1_pre(MDOC_ARGS); 75 static int mdoc_dv_pre(MDOC_ARGS); 76 static int mdoc_fa_pre(MDOC_ARGS); 77 static int mdoc_fd_pre(MDOC_ARGS); 78 static int mdoc_fl_pre(MDOC_ARGS); 79 static int mdoc_fn_pre(MDOC_ARGS); 80 static int mdoc_ft_pre(MDOC_ARGS); 81 static int mdoc_em_pre(MDOC_ARGS); 82 static void mdoc_eo_post(MDOC_ARGS); 83 static int mdoc_eo_pre(MDOC_ARGS); 84 static int mdoc_er_pre(MDOC_ARGS); 85 static int mdoc_ev_pre(MDOC_ARGS); 86 static int mdoc_ex_pre(MDOC_ARGS); 87 static void mdoc_fo_post(MDOC_ARGS); 88 static int mdoc_fo_pre(MDOC_ARGS); 89 static int mdoc_ic_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_li_pre(MDOC_ARGS); 95 static int mdoc_lk_pre(MDOC_ARGS); 96 static int mdoc_mt_pre(MDOC_ARGS); 97 static int mdoc_ms_pre(MDOC_ARGS); 98 static int mdoc_nd_pre(MDOC_ARGS); 99 static int mdoc_nm_pre(MDOC_ARGS); 100 static int mdoc_no_pre(MDOC_ARGS); 101 static int mdoc_ns_pre(MDOC_ARGS); 102 static int mdoc_pa_pre(MDOC_ARGS); 103 static void mdoc_pf_post(MDOC_ARGS); 104 static int mdoc_pp_pre(MDOC_ARGS); 105 static void mdoc_quote_post(MDOC_ARGS); 106 static int mdoc_quote_pre(MDOC_ARGS); 107 static int mdoc_rs_pre(MDOC_ARGS); 108 static int mdoc_rv_pre(MDOC_ARGS); 109 static int mdoc_sh_pre(MDOC_ARGS); 110 static int mdoc_skip_pre(MDOC_ARGS); 111 static int mdoc_sm_pre(MDOC_ARGS); 112 static int mdoc_sp_pre(MDOC_ARGS); 113 static int mdoc_ss_pre(MDOC_ARGS); 114 static int mdoc_sx_pre(MDOC_ARGS); 115 static int mdoc_sy_pre(MDOC_ARGS); 116 static int mdoc_ud_pre(MDOC_ARGS); 117 static int mdoc_va_pre(MDOC_ARGS); 118 static int mdoc_vt_pre(MDOC_ARGS); 119 static int mdoc_xr_pre(MDOC_ARGS); 120 static int mdoc_xx_pre(MDOC_ARGS); 121 122 static const struct htmlmdoc mdocs[MDOC_MAX] = { 123 {mdoc_ap_pre, NULL}, /* Ap */ 124 {NULL, NULL}, /* Dd */ 125 {NULL, NULL}, /* Dt */ 126 {NULL, NULL}, /* Os */ 127 {mdoc_sh_pre, NULL }, /* Sh */ 128 {mdoc_ss_pre, NULL }, /* Ss */ 129 {mdoc_pp_pre, NULL}, /* Pp */ 130 {mdoc_d1_pre, NULL}, /* D1 */ 131 {mdoc_d1_pre, NULL}, /* Dl */ 132 {mdoc_bd_pre, NULL}, /* Bd */ 133 {NULL, NULL}, /* Ed */ 134 {mdoc_bl_pre, NULL}, /* Bl */ 135 {NULL, NULL}, /* El */ 136 {mdoc_it_pre, NULL}, /* It */ 137 {mdoc_ad_pre, NULL}, /* Ad */ 138 {mdoc_an_pre, NULL}, /* An */ 139 {mdoc_ar_pre, NULL}, /* Ar */ 140 {mdoc_cd_pre, NULL}, /* Cd */ 141 {mdoc_fl_pre, NULL}, /* Cm */ 142 {mdoc_dv_pre, NULL}, /* Dv */ 143 {mdoc_er_pre, NULL}, /* Er */ 144 {mdoc_ev_pre, NULL}, /* Ev */ 145 {mdoc_ex_pre, NULL}, /* Ex */ 146 {mdoc_fa_pre, NULL}, /* Fa */ 147 {mdoc_fd_pre, NULL}, /* Fd */ 148 {mdoc_fl_pre, NULL}, /* Fl */ 149 {mdoc_fn_pre, NULL}, /* Fn */ 150 {mdoc_ft_pre, NULL}, /* Ft */ 151 {mdoc_ic_pre, NULL}, /* Ic */ 152 {mdoc_in_pre, NULL}, /* In */ 153 {mdoc_li_pre, NULL}, /* Li */ 154 {mdoc_nd_pre, NULL}, /* Nd */ 155 {mdoc_nm_pre, NULL}, /* Nm */ 156 {mdoc_quote_pre, mdoc_quote_post}, /* Op */ 157 {mdoc_ft_pre, NULL}, /* Ot */ 158 {mdoc_pa_pre, NULL}, /* Pa */ 159 {mdoc_rv_pre, NULL}, /* Rv */ 160 {NULL, NULL}, /* St */ 161 {mdoc_va_pre, NULL}, /* Va */ 162 {mdoc_vt_pre, NULL}, /* Vt */ 163 {mdoc_xr_pre, NULL}, /* Xr */ 164 {mdoc__x_pre, mdoc__x_post}, /* %A */ 165 {mdoc__x_pre, mdoc__x_post}, /* %B */ 166 {mdoc__x_pre, mdoc__x_post}, /* %D */ 167 {mdoc__x_pre, mdoc__x_post}, /* %I */ 168 {mdoc__x_pre, mdoc__x_post}, /* %J */ 169 {mdoc__x_pre, mdoc__x_post}, /* %N */ 170 {mdoc__x_pre, mdoc__x_post}, /* %O */ 171 {mdoc__x_pre, mdoc__x_post}, /* %P */ 172 {mdoc__x_pre, mdoc__x_post}, /* %R */ 173 {mdoc__x_pre, mdoc__x_post}, /* %T */ 174 {mdoc__x_pre, mdoc__x_post}, /* %V */ 175 {NULL, NULL}, /* Ac */ 176 {mdoc_quote_pre, mdoc_quote_post}, /* Ao */ 177 {mdoc_quote_pre, mdoc_quote_post}, /* Aq */ 178 {NULL, NULL}, /* At */ 179 {NULL, NULL}, /* Bc */ 180 {mdoc_bf_pre, NULL}, /* Bf */ 181 {mdoc_quote_pre, mdoc_quote_post}, /* Bo */ 182 {mdoc_quote_pre, mdoc_quote_post}, /* Bq */ 183 {mdoc_xx_pre, NULL}, /* Bsx */ 184 {mdoc_bx_pre, NULL}, /* Bx */ 185 {mdoc_skip_pre, NULL}, /* Db */ 186 {NULL, NULL}, /* Dc */ 187 {mdoc_quote_pre, mdoc_quote_post}, /* Do */ 188 {mdoc_quote_pre, mdoc_quote_post}, /* Dq */ 189 {NULL, NULL}, /* Ec */ /* FIXME: no space */ 190 {NULL, NULL}, /* Ef */ 191 {mdoc_em_pre, NULL}, /* Em */ 192 {mdoc_eo_pre, mdoc_eo_post}, /* Eo */ 193 {mdoc_xx_pre, NULL}, /* Fx */ 194 {mdoc_ms_pre, NULL}, /* Ms */ 195 {mdoc_no_pre, NULL}, /* No */ 196 {mdoc_ns_pre, NULL}, /* Ns */ 197 {mdoc_xx_pre, NULL}, /* Nx */ 198 {mdoc_xx_pre, NULL}, /* Ox */ 199 {NULL, NULL}, /* Pc */ 200 {mdoc_igndelim_pre, mdoc_pf_post}, /* Pf */ 201 {mdoc_quote_pre, mdoc_quote_post}, /* Po */ 202 {mdoc_quote_pre, mdoc_quote_post}, /* Pq */ 203 {NULL, NULL}, /* Qc */ 204 {mdoc_quote_pre, mdoc_quote_post}, /* Ql */ 205 {mdoc_quote_pre, mdoc_quote_post}, /* Qo */ 206 {mdoc_quote_pre, mdoc_quote_post}, /* Qq */ 207 {NULL, NULL}, /* Re */ 208 {mdoc_rs_pre, NULL}, /* Rs */ 209 {NULL, NULL}, /* Sc */ 210 {mdoc_quote_pre, mdoc_quote_post}, /* So */ 211 {mdoc_quote_pre, mdoc_quote_post}, /* Sq */ 212 {mdoc_sm_pre, NULL}, /* Sm */ 213 {mdoc_sx_pre, NULL}, /* Sx */ 214 {mdoc_sy_pre, NULL}, /* Sy */ 215 {NULL, NULL}, /* Tn */ 216 {mdoc_xx_pre, NULL}, /* Ux */ 217 {NULL, NULL}, /* Xc */ 218 {NULL, NULL}, /* Xo */ 219 {mdoc_fo_pre, mdoc_fo_post}, /* Fo */ 220 {NULL, NULL}, /* Fc */ 221 {mdoc_quote_pre, mdoc_quote_post}, /* Oo */ 222 {NULL, NULL}, /* Oc */ 223 {mdoc_bk_pre, mdoc_bk_post}, /* Bk */ 224 {NULL, NULL}, /* Ek */ 225 {mdoc_bt_pre, NULL}, /* Bt */ 226 {NULL, NULL}, /* Hf */ 227 {mdoc_em_pre, NULL}, /* Fr */ 228 {mdoc_ud_pre, NULL}, /* Ud */ 229 {mdoc_lb_pre, NULL}, /* Lb */ 230 {mdoc_pp_pre, NULL}, /* Lp */ 231 {mdoc_lk_pre, NULL}, /* Lk */ 232 {mdoc_mt_pre, NULL}, /* Mt */ 233 {mdoc_quote_pre, mdoc_quote_post}, /* Brq */ 234 {mdoc_quote_pre, mdoc_quote_post}, /* Bro */ 235 {NULL, NULL}, /* Brc */ 236 {mdoc__x_pre, mdoc__x_post}, /* %C */ 237 {mdoc_skip_pre, NULL}, /* Es */ 238 {mdoc_quote_pre, mdoc_quote_post}, /* En */ 239 {mdoc_xx_pre, NULL}, /* Dx */ 240 {mdoc__x_pre, mdoc__x_post}, /* %Q */ 241 {mdoc_sp_pre, NULL}, /* br */ 242 {mdoc_sp_pre, NULL}, /* sp */ 243 {mdoc__x_pre, mdoc__x_post}, /* %U */ 244 {NULL, NULL}, /* Ta */ 245 {mdoc_skip_pre, NULL}, /* ll */ 246 }; 247 248 static const char * const lists[LIST_MAX] = { 249 NULL, 250 "list-bul", 251 "list-col", 252 "list-dash", 253 "list-diag", 254 "list-enum", 255 "list-hang", 256 "list-hyph", 257 "list-inset", 258 "list-item", 259 "list-ohang", 260 "list-tag" 261 }; 262 263 264 /* 265 * Calculate the scaling unit passed in a `-width' argument. This uses 266 * either a native scaling unit (e.g., 1i, 2m) or the string length of 267 * the value. 268 */ 269 static void 270 a2width(const char *p, struct roffsu *su) 271 { 272 273 if (a2roffsu(p, su, SCALE_MAX) < 2) { 274 su->unit = SCALE_EN; 275 su->scale = html_strlen(p); 276 } else if (su->scale < 0.0) 277 su->scale = 0.0; 278 } 279 280 /* 281 * See the same function in mdoc_term.c for documentation. 282 */ 283 static void 284 synopsis_pre(struct html *h, const struct roff_node *n) 285 { 286 287 if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags)) 288 return; 289 290 if (n->prev->tok == n->tok && 291 MDOC_Fo != n->tok && 292 MDOC_Ft != n->tok && 293 MDOC_Fn != n->tok) { 294 print_otag(h, TAG_BR, 0, NULL); 295 return; 296 } 297 298 switch (n->prev->tok) { 299 case MDOC_Fd: 300 case MDOC_Fn: 301 case MDOC_Fo: 302 case MDOC_In: 303 case MDOC_Vt: 304 print_paragraph(h); 305 break; 306 case MDOC_Ft: 307 if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) { 308 print_paragraph(h); 309 break; 310 } 311 /* FALLTHROUGH */ 312 default: 313 print_otag(h, TAG_BR, 0, NULL); 314 break; 315 } 316 } 317 318 void 319 html_mdoc(void *arg, const struct roff_man *mdoc) 320 { 321 struct htmlpair tag; 322 struct html *h; 323 struct tag *t, *tt; 324 325 PAIR_CLASS_INIT(&tag, "mandoc"); 326 h = (struct html *)arg; 327 328 if ( ! (HTML_FRAGMENT & h->oflags)) { 329 print_gen_decls(h); 330 t = print_otag(h, TAG_HTML, 0, NULL); 331 tt = print_otag(h, TAG_HEAD, 0, NULL); 332 print_mdoc_head(&mdoc->meta, mdoc->first->child, h); 333 print_tagq(h, tt); 334 print_otag(h, TAG_BODY, 0, NULL); 335 print_otag(h, TAG_DIV, 1, &tag); 336 } else 337 t = print_otag(h, TAG_DIV, 1, &tag); 338 339 mdoc_root_pre(&mdoc->meta, mdoc->first->child, h); 340 print_mdoc_nodelist(&mdoc->meta, mdoc->first->child, h); 341 mdoc_root_post(&mdoc->meta, mdoc->first->child, h); 342 print_tagq(h, t); 343 putchar('\n'); 344 } 345 346 static void 347 print_mdoc_head(MDOC_ARGS) 348 { 349 350 print_gen_head(h); 351 bufinit(h); 352 bufcat(h, meta->title); 353 if (meta->msec) 354 bufcat_fmt(h, "(%s)", meta->msec); 355 if (meta->arch) 356 bufcat_fmt(h, " (%s)", meta->arch); 357 358 print_otag(h, TAG_TITLE, 0, NULL); 359 print_text(h, h->buf); 360 } 361 362 static void 363 print_mdoc_nodelist(MDOC_ARGS) 364 { 365 366 while (n != NULL) { 367 print_mdoc_node(meta, n, h); 368 n = n->next; 369 } 370 } 371 372 static void 373 print_mdoc_node(MDOC_ARGS) 374 { 375 int child; 376 struct tag *t; 377 378 child = 1; 379 t = h->tags.head; 380 n->flags &= ~MDOC_ENDED; 381 382 switch (n->type) { 383 case ROFFT_TEXT: 384 /* No tables in this mode... */ 385 assert(NULL == h->tblt); 386 387 /* 388 * Make sure that if we're in a literal mode already 389 * (i.e., within a <PRE>) don't print the newline. 390 */ 391 if (' ' == *n->string && MDOC_LINE & n->flags) 392 if ( ! (HTML_LITERAL & h->flags)) 393 print_otag(h, TAG_BR, 0, NULL); 394 if (MDOC_DELIMC & n->flags) 395 h->flags |= HTML_NOSPACE; 396 print_text(h, n->string); 397 if (MDOC_DELIMO & n->flags) 398 h->flags |= HTML_NOSPACE; 399 return; 400 case ROFFT_EQN: 401 if (n->flags & MDOC_LINE) 402 putchar('\n'); 403 print_eqn(h, n->eqn); 404 break; 405 case ROFFT_TBL: 406 /* 407 * This will take care of initialising all of the table 408 * state data for the first table, then tearing it down 409 * for the last one. 410 */ 411 print_tbl(h, n->span); 412 return; 413 default: 414 /* 415 * Close out the current table, if it's open, and unset 416 * the "meta" table state. This will be reopened on the 417 * next table element. 418 */ 419 if (h->tblt != NULL) { 420 print_tblclose(h); 421 t = h->tags.head; 422 } 423 assert(h->tblt == NULL); 424 if (mdocs[n->tok].pre && (n->end == ENDBODY_NOT || n->child)) 425 child = (*mdocs[n->tok].pre)(meta, n, h); 426 break; 427 } 428 429 if (h->flags & HTML_KEEP && n->flags & MDOC_LINE) { 430 h->flags &= ~HTML_KEEP; 431 h->flags |= HTML_PREKEEP; 432 } 433 434 if (child && n->child) 435 print_mdoc_nodelist(meta, n->child, h); 436 437 print_stagq(h, t); 438 439 switch (n->type) { 440 case ROFFT_EQN: 441 break; 442 default: 443 if ( ! mdocs[n->tok].post || n->flags & MDOC_ENDED) 444 break; 445 (*mdocs[n->tok].post)(meta, n, h); 446 if (n->end != ENDBODY_NOT) 447 n->body->flags |= MDOC_ENDED; 448 if (n->end == ENDBODY_NOSPACE) 449 h->flags |= HTML_NOSPACE; 450 break; 451 } 452 } 453 454 static void 455 mdoc_root_post(MDOC_ARGS) 456 { 457 struct htmlpair tag; 458 struct tag *t, *tt; 459 460 PAIR_CLASS_INIT(&tag, "foot"); 461 t = print_otag(h, TAG_TABLE, 1, &tag); 462 463 print_otag(h, TAG_TBODY, 0, NULL); 464 465 tt = print_otag(h, TAG_TR, 0, NULL); 466 467 PAIR_CLASS_INIT(&tag, "foot-date"); 468 print_otag(h, TAG_TD, 1, &tag); 469 print_text(h, meta->date); 470 print_stagq(h, tt); 471 472 PAIR_CLASS_INIT(&tag, "foot-os"); 473 print_otag(h, TAG_TD, 1, &tag); 474 print_text(h, meta->os); 475 print_tagq(h, t); 476 } 477 478 static int 479 mdoc_root_pre(MDOC_ARGS) 480 { 481 struct htmlpair tag; 482 struct tag *t, *tt; 483 char *volume, *title; 484 485 if (NULL == meta->arch) 486 volume = mandoc_strdup(meta->vol); 487 else 488 mandoc_asprintf(&volume, "%s (%s)", 489 meta->vol, meta->arch); 490 491 if (NULL == meta->msec) 492 title = mandoc_strdup(meta->title); 493 else 494 mandoc_asprintf(&title, "%s(%s)", 495 meta->title, meta->msec); 496 497 PAIR_CLASS_INIT(&tag, "head"); 498 t = print_otag(h, TAG_TABLE, 1, &tag); 499 500 print_otag(h, TAG_TBODY, 0, NULL); 501 502 tt = print_otag(h, TAG_TR, 0, NULL); 503 504 PAIR_CLASS_INIT(&tag, "head-ltitle"); 505 print_otag(h, TAG_TD, 1, &tag); 506 print_text(h, title); 507 print_stagq(h, tt); 508 509 PAIR_CLASS_INIT(&tag, "head-vol"); 510 print_otag(h, TAG_TD, 1, &tag); 511 print_text(h, volume); 512 print_stagq(h, tt); 513 514 PAIR_CLASS_INIT(&tag, "head-rtitle"); 515 print_otag(h, TAG_TD, 1, &tag); 516 print_text(h, title); 517 print_tagq(h, t); 518 519 free(title); 520 free(volume); 521 return 1; 522 } 523 524 static int 525 mdoc_sh_pre(MDOC_ARGS) 526 { 527 struct htmlpair tag; 528 529 switch (n->type) { 530 case ROFFT_BLOCK: 531 PAIR_CLASS_INIT(&tag, "section"); 532 print_otag(h, TAG_DIV, 1, &tag); 533 return 1; 534 case ROFFT_BODY: 535 if (n->sec == SEC_AUTHORS) 536 h->flags &= ~(HTML_SPLIT|HTML_NOSPLIT); 537 return 1; 538 default: 539 break; 540 } 541 542 bufinit(h); 543 544 for (n = n->child; n != NULL && n->type == ROFFT_TEXT; ) { 545 bufcat_id(h, n->string); 546 if (NULL != (n = n->next)) 547 bufcat_id(h, " "); 548 } 549 550 if (NULL == n) { 551 PAIR_ID_INIT(&tag, h->buf); 552 print_otag(h, TAG_H1, 1, &tag); 553 } else 554 print_otag(h, TAG_H1, 0, NULL); 555 556 return 1; 557 } 558 559 static int 560 mdoc_ss_pre(MDOC_ARGS) 561 { 562 struct htmlpair tag; 563 564 if (n->type == ROFFT_BLOCK) { 565 PAIR_CLASS_INIT(&tag, "subsection"); 566 print_otag(h, TAG_DIV, 1, &tag); 567 return 1; 568 } else if (n->type == ROFFT_BODY) 569 return 1; 570 571 bufinit(h); 572 573 for (n = n->child; n != NULL && n->type == ROFFT_TEXT; ) { 574 bufcat_id(h, n->string); 575 if (NULL != (n = n->next)) 576 bufcat_id(h, " "); 577 } 578 579 if (NULL == n) { 580 PAIR_ID_INIT(&tag, h->buf); 581 print_otag(h, TAG_H2, 1, &tag); 582 } else 583 print_otag(h, TAG_H2, 0, NULL); 584 585 return 1; 586 } 587 588 static int 589 mdoc_fl_pre(MDOC_ARGS) 590 { 591 struct htmlpair tag; 592 593 PAIR_CLASS_INIT(&tag, "flag"); 594 print_otag(h, TAG_B, 1, &tag); 595 596 /* `Cm' has no leading hyphen. */ 597 598 if (MDOC_Cm == n->tok) 599 return 1; 600 601 print_text(h, "\\-"); 602 603 if (!(n->child == NULL && 604 (n->next == NULL || 605 n->next->type == ROFFT_TEXT || 606 n->next->flags & MDOC_LINE))) 607 h->flags |= HTML_NOSPACE; 608 609 return 1; 610 } 611 612 static int 613 mdoc_nd_pre(MDOC_ARGS) 614 { 615 struct htmlpair tag; 616 617 if (n->type != ROFFT_BODY) 618 return 1; 619 620 /* XXX: this tag in theory can contain block elements. */ 621 622 print_text(h, "\\(em"); 623 PAIR_CLASS_INIT(&tag, "desc"); 624 print_otag(h, TAG_SPAN, 1, &tag); 625 return 1; 626 } 627 628 static int 629 mdoc_nm_pre(MDOC_ARGS) 630 { 631 struct htmlpair tag; 632 struct roffsu su; 633 int len; 634 635 switch (n->type) { 636 case ROFFT_HEAD: 637 print_otag(h, TAG_TD, 0, NULL); 638 /* FALLTHROUGH */ 639 case ROFFT_ELEM: 640 PAIR_CLASS_INIT(&tag, "name"); 641 print_otag(h, TAG_B, 1, &tag); 642 if (n->child == NULL && meta->name != NULL) 643 print_text(h, meta->name); 644 return 1; 645 case ROFFT_BODY: 646 print_otag(h, TAG_TD, 0, NULL); 647 return 1; 648 default: 649 break; 650 } 651 652 synopsis_pre(h, n); 653 PAIR_CLASS_INIT(&tag, "synopsis"); 654 print_otag(h, TAG_TABLE, 1, &tag); 655 656 for (len = 0, n = n->head->child; n; n = n->next) 657 if (n->type == ROFFT_TEXT) 658 len += html_strlen(n->string); 659 660 if (len == 0 && meta->name != NULL) 661 len = html_strlen(meta->name); 662 663 SCALE_HS_INIT(&su, len); 664 bufinit(h); 665 bufcat_su(h, "width", &su); 666 PAIR_STYLE_INIT(&tag, h); 667 print_otag(h, TAG_COL, 1, &tag); 668 print_otag(h, TAG_COL, 0, NULL); 669 print_otag(h, TAG_TBODY, 0, NULL); 670 print_otag(h, TAG_TR, 0, NULL); 671 return 1; 672 } 673 674 static int 675 mdoc_xr_pre(MDOC_ARGS) 676 { 677 struct htmlpair tag[2]; 678 679 if (NULL == n->child) 680 return 0; 681 682 PAIR_CLASS_INIT(&tag[0], "link-man"); 683 684 if (h->base_man) { 685 buffmt_man(h, n->child->string, 686 n->child->next ? 687 n->child->next->string : NULL); 688 PAIR_HREF_INIT(&tag[1], h->buf); 689 print_otag(h, TAG_A, 2, tag); 690 } else 691 print_otag(h, TAG_A, 1, tag); 692 693 n = n->child; 694 print_text(h, n->string); 695 696 if (NULL == (n = n->next)) 697 return 0; 698 699 h->flags |= HTML_NOSPACE; 700 print_text(h, "("); 701 h->flags |= HTML_NOSPACE; 702 print_text(h, n->string); 703 h->flags |= HTML_NOSPACE; 704 print_text(h, ")"); 705 return 0; 706 } 707 708 static int 709 mdoc_ns_pre(MDOC_ARGS) 710 { 711 712 if ( ! (MDOC_LINE & n->flags)) 713 h->flags |= HTML_NOSPACE; 714 return 1; 715 } 716 717 static int 718 mdoc_ar_pre(MDOC_ARGS) 719 { 720 struct htmlpair tag; 721 722 PAIR_CLASS_INIT(&tag, "arg"); 723 print_otag(h, TAG_I, 1, &tag); 724 return 1; 725 } 726 727 static int 728 mdoc_xx_pre(MDOC_ARGS) 729 { 730 const char *pp; 731 struct htmlpair tag; 732 int flags; 733 734 switch (n->tok) { 735 case MDOC_Bsx: 736 pp = "BSD/OS"; 737 break; 738 case MDOC_Dx: 739 pp = "DragonFly"; 740 break; 741 case MDOC_Fx: 742 pp = "FreeBSD"; 743 break; 744 case MDOC_Nx: 745 pp = "NetBSD"; 746 break; 747 case MDOC_Ox: 748 pp = "OpenBSD"; 749 break; 750 case MDOC_Ux: 751 pp = "UNIX"; 752 break; 753 default: 754 return 1; 755 } 756 757 PAIR_CLASS_INIT(&tag, "unix"); 758 print_otag(h, TAG_SPAN, 1, &tag); 759 760 print_text(h, pp); 761 if (n->child) { 762 flags = h->flags; 763 h->flags |= HTML_KEEP; 764 print_text(h, n->child->string); 765 h->flags = flags; 766 } 767 return 0; 768 } 769 770 static int 771 mdoc_bx_pre(MDOC_ARGS) 772 { 773 struct htmlpair tag; 774 775 PAIR_CLASS_INIT(&tag, "unix"); 776 print_otag(h, TAG_SPAN, 1, &tag); 777 778 if (NULL != (n = n->child)) { 779 print_text(h, n->string); 780 h->flags |= HTML_NOSPACE; 781 print_text(h, "BSD"); 782 } else { 783 print_text(h, "BSD"); 784 return 0; 785 } 786 787 if (NULL != (n = n->next)) { 788 h->flags |= HTML_NOSPACE; 789 print_text(h, "-"); 790 h->flags |= HTML_NOSPACE; 791 print_text(h, n->string); 792 } 793 794 return 0; 795 } 796 797 static int 798 mdoc_it_pre(MDOC_ARGS) 799 { 800 struct roffsu su; 801 enum mdoc_list type; 802 struct htmlpair tag[2]; 803 const struct roff_node *bl; 804 805 bl = n->parent; 806 while (bl && MDOC_Bl != bl->tok) 807 bl = bl->parent; 808 809 assert(bl); 810 811 type = bl->norm->Bl.type; 812 813 assert(lists[type]); 814 PAIR_CLASS_INIT(&tag[0], lists[type]); 815 816 bufinit(h); 817 818 if (n->type == ROFFT_HEAD) { 819 switch (type) { 820 case LIST_bullet: 821 case LIST_dash: 822 case LIST_item: 823 case LIST_hyphen: 824 case LIST_enum: 825 return 0; 826 case LIST_diag: 827 case LIST_hang: 828 case LIST_inset: 829 case LIST_ohang: 830 case LIST_tag: 831 SCALE_VS_INIT(&su, ! bl->norm->Bl.comp); 832 bufcat_su(h, "margin-top", &su); 833 PAIR_STYLE_INIT(&tag[1], h); 834 print_otag(h, TAG_DT, 2, tag); 835 if (LIST_diag != type) 836 break; 837 PAIR_CLASS_INIT(&tag[0], "diag"); 838 print_otag(h, TAG_B, 1, tag); 839 break; 840 case LIST_column: 841 break; 842 default: 843 break; 844 } 845 } else if (n->type == ROFFT_BODY) { 846 switch (type) { 847 case LIST_bullet: 848 case LIST_hyphen: 849 case LIST_dash: 850 case LIST_enum: 851 case LIST_item: 852 SCALE_VS_INIT(&su, ! bl->norm->Bl.comp); 853 bufcat_su(h, "margin-top", &su); 854 PAIR_STYLE_INIT(&tag[1], h); 855 print_otag(h, TAG_LI, 2, tag); 856 break; 857 case LIST_diag: 858 case LIST_hang: 859 case LIST_inset: 860 case LIST_ohang: 861 case LIST_tag: 862 if (NULL == bl->norm->Bl.width) { 863 print_otag(h, TAG_DD, 1, tag); 864 break; 865 } 866 a2width(bl->norm->Bl.width, &su); 867 bufcat_su(h, "margin-left", &su); 868 PAIR_STYLE_INIT(&tag[1], h); 869 print_otag(h, TAG_DD, 2, tag); 870 break; 871 case LIST_column: 872 SCALE_VS_INIT(&su, ! bl->norm->Bl.comp); 873 bufcat_su(h, "margin-top", &su); 874 PAIR_STYLE_INIT(&tag[1], h); 875 print_otag(h, TAG_TD, 2, tag); 876 break; 877 default: 878 break; 879 } 880 } else { 881 switch (type) { 882 case LIST_column: 883 print_otag(h, TAG_TR, 1, tag); 884 break; 885 default: 886 break; 887 } 888 } 889 890 return 1; 891 } 892 893 static int 894 mdoc_bl_pre(MDOC_ARGS) 895 { 896 int i; 897 struct htmlpair tag[3]; 898 struct roffsu su; 899 char buf[BUFSIZ]; 900 901 if (n->type == ROFFT_BODY) { 902 if (LIST_column == n->norm->Bl.type) 903 print_otag(h, TAG_TBODY, 0, NULL); 904 return 1; 905 } 906 907 if (n->type == ROFFT_HEAD) { 908 if (LIST_column != n->norm->Bl.type) 909 return 0; 910 911 /* 912 * For each column, print out the <COL> tag with our 913 * suggested width. The last column gets min-width, as 914 * in terminal mode it auto-sizes to the width of the 915 * screen and we want to preserve that behaviour. 916 */ 917 918 for (i = 0; i < (int)n->norm->Bl.ncols; i++) { 919 bufinit(h); 920 a2width(n->norm->Bl.cols[i], &su); 921 if (i < (int)n->norm->Bl.ncols - 1) 922 bufcat_su(h, "width", &su); 923 else 924 bufcat_su(h, "min-width", &su); 925 PAIR_STYLE_INIT(&tag[0], h); 926 print_otag(h, TAG_COL, 1, tag); 927 } 928 929 return 0; 930 } 931 932 SCALE_VS_INIT(&su, 0); 933 bufinit(h); 934 bufcat_su(h, "margin-top", &su); 935 bufcat_su(h, "margin-bottom", &su); 936 PAIR_STYLE_INIT(&tag[0], h); 937 938 assert(lists[n->norm->Bl.type]); 939 (void)strlcpy(buf, "list ", BUFSIZ); 940 (void)strlcat(buf, lists[n->norm->Bl.type], BUFSIZ); 941 PAIR_INIT(&tag[1], ATTR_CLASS, buf); 942 943 /* Set the block's left-hand margin. */ 944 945 if (n->norm->Bl.offs) { 946 a2width(n->norm->Bl.offs, &su); 947 bufcat_su(h, "margin-left", &su); 948 } 949 950 switch (n->norm->Bl.type) { 951 case LIST_bullet: 952 case LIST_dash: 953 case LIST_hyphen: 954 case LIST_item: 955 print_otag(h, TAG_UL, 2, tag); 956 break; 957 case LIST_enum: 958 print_otag(h, TAG_OL, 2, tag); 959 break; 960 case LIST_diag: 961 case LIST_hang: 962 case LIST_inset: 963 case LIST_ohang: 964 case LIST_tag: 965 print_otag(h, TAG_DL, 2, tag); 966 break; 967 case LIST_column: 968 print_otag(h, TAG_TABLE, 2, tag); 969 break; 970 default: 971 abort(); 972 } 973 974 return 1; 975 } 976 977 static int 978 mdoc_ex_pre(MDOC_ARGS) 979 { 980 struct htmlpair tag; 981 struct tag *t; 982 struct roff_node *nch; 983 984 if (n->prev) 985 print_otag(h, TAG_BR, 0, NULL); 986 987 PAIR_CLASS_INIT(&tag, "utility"); 988 989 print_text(h, "The"); 990 991 for (nch = n->child; nch != NULL; nch = nch->next) { 992 assert(nch->type == ROFFT_TEXT); 993 994 t = print_otag(h, TAG_B, 1, &tag); 995 print_text(h, nch->string); 996 print_tagq(h, t); 997 998 if (nch->next == NULL) 999 continue; 1000 1001 if (nch->prev != NULL || nch->next->next != NULL) { 1002 h->flags |= HTML_NOSPACE; 1003 print_text(h, ","); 1004 } 1005 1006 if (nch->next->next == NULL) 1007 print_text(h, "and"); 1008 } 1009 1010 if (n->child != NULL && n->child->next != NULL) 1011 print_text(h, "utilities exit\\~0"); 1012 else 1013 print_text(h, "utility exits\\~0"); 1014 1015 print_text(h, "on success, and\\~>0 if an error occurs."); 1016 return 0; 1017 } 1018 1019 static int 1020 mdoc_em_pre(MDOC_ARGS) 1021 { 1022 struct htmlpair tag; 1023 1024 PAIR_CLASS_INIT(&tag, "emph"); 1025 print_otag(h, TAG_SPAN, 1, &tag); 1026 return 1; 1027 } 1028 1029 static int 1030 mdoc_d1_pre(MDOC_ARGS) 1031 { 1032 struct htmlpair tag[2]; 1033 struct roffsu su; 1034 1035 if (n->type != ROFFT_BLOCK) 1036 return 1; 1037 1038 SCALE_VS_INIT(&su, 0); 1039 bufinit(h); 1040 bufcat_su(h, "margin-top", &su); 1041 bufcat_su(h, "margin-bottom", &su); 1042 PAIR_STYLE_INIT(&tag[0], h); 1043 print_otag(h, TAG_BLOCKQUOTE, 1, tag); 1044 1045 /* BLOCKQUOTE needs a block body. */ 1046 1047 PAIR_CLASS_INIT(&tag[0], "display"); 1048 print_otag(h, TAG_DIV, 1, tag); 1049 1050 if (MDOC_Dl == n->tok) { 1051 PAIR_CLASS_INIT(&tag[0], "lit"); 1052 print_otag(h, TAG_CODE, 1, tag); 1053 } 1054 1055 return 1; 1056 } 1057 1058 static int 1059 mdoc_sx_pre(MDOC_ARGS) 1060 { 1061 struct htmlpair tag[2]; 1062 1063 bufinit(h); 1064 bufcat(h, "#"); 1065 1066 for (n = n->child; n; ) { 1067 bufcat_id(h, n->string); 1068 if (NULL != (n = n->next)) 1069 bufcat_id(h, " "); 1070 } 1071 1072 PAIR_CLASS_INIT(&tag[0], "link-sec"); 1073 PAIR_HREF_INIT(&tag[1], h->buf); 1074 1075 print_otag(h, TAG_I, 1, tag); 1076 print_otag(h, TAG_A, 2, tag); 1077 return 1; 1078 } 1079 1080 static int 1081 mdoc_bd_pre(MDOC_ARGS) 1082 { 1083 struct htmlpair tag[2]; 1084 int comp, sv; 1085 struct roff_node *nn; 1086 struct roffsu su; 1087 1088 if (n->type == ROFFT_HEAD) 1089 return 0; 1090 1091 if (n->type == ROFFT_BLOCK) { 1092 comp = n->norm->Bd.comp; 1093 for (nn = n; nn && ! comp; nn = nn->parent) { 1094 if (nn->type != ROFFT_BLOCK) 1095 continue; 1096 if (MDOC_Ss == nn->tok || MDOC_Sh == nn->tok) 1097 comp = 1; 1098 if (nn->prev) 1099 break; 1100 } 1101 if ( ! comp) 1102 print_paragraph(h); 1103 return 1; 1104 } 1105 1106 /* Handle the -offset argument. */ 1107 1108 if (n->norm->Bd.offs == NULL || 1109 ! strcmp(n->norm->Bd.offs, "left")) 1110 SCALE_HS_INIT(&su, 0); 1111 else if ( ! strcmp(n->norm->Bd.offs, "indent")) 1112 SCALE_HS_INIT(&su, INDENT); 1113 else if ( ! strcmp(n->norm->Bd.offs, "indent-two")) 1114 SCALE_HS_INIT(&su, INDENT * 2); 1115 else 1116 a2width(n->norm->Bd.offs, &su); 1117 1118 bufinit(h); 1119 bufcat_su(h, "margin-left", &su); 1120 PAIR_STYLE_INIT(&tag[0], h); 1121 1122 if (DISP_unfilled != n->norm->Bd.type && 1123 DISP_literal != n->norm->Bd.type) { 1124 PAIR_CLASS_INIT(&tag[1], "display"); 1125 print_otag(h, TAG_DIV, 2, tag); 1126 return 1; 1127 } 1128 1129 PAIR_CLASS_INIT(&tag[1], "lit display"); 1130 print_otag(h, TAG_PRE, 2, tag); 1131 1132 /* This can be recursive: save & set our literal state. */ 1133 1134 sv = h->flags & HTML_LITERAL; 1135 h->flags |= HTML_LITERAL; 1136 1137 for (nn = n->child; nn; nn = nn->next) { 1138 print_mdoc_node(meta, nn, h); 1139 /* 1140 * If the printed node flushes its own line, then we 1141 * needn't do it here as well. This is hacky, but the 1142 * notion of selective eoln whitespace is pretty dumb 1143 * anyway, so don't sweat it. 1144 */ 1145 switch (nn->tok) { 1146 case MDOC_Sm: 1147 case MDOC_br: 1148 case MDOC_sp: 1149 case MDOC_Bl: 1150 case MDOC_D1: 1151 case MDOC_Dl: 1152 case MDOC_Lp: 1153 case MDOC_Pp: 1154 continue; 1155 default: 1156 break; 1157 } 1158 if (h->flags & HTML_NONEWLINE || 1159 (nn->next && ! (nn->next->flags & MDOC_LINE))) 1160 continue; 1161 else if (nn->next) 1162 print_text(h, "\n"); 1163 1164 h->flags |= HTML_NOSPACE; 1165 } 1166 1167 if (0 == sv) 1168 h->flags &= ~HTML_LITERAL; 1169 1170 return 0; 1171 } 1172 1173 static int 1174 mdoc_pa_pre(MDOC_ARGS) 1175 { 1176 struct htmlpair tag; 1177 1178 PAIR_CLASS_INIT(&tag, "file"); 1179 print_otag(h, TAG_I, 1, &tag); 1180 return 1; 1181 } 1182 1183 static int 1184 mdoc_ad_pre(MDOC_ARGS) 1185 { 1186 struct htmlpair tag; 1187 1188 PAIR_CLASS_INIT(&tag, "addr"); 1189 print_otag(h, TAG_I, 1, &tag); 1190 return 1; 1191 } 1192 1193 static int 1194 mdoc_an_pre(MDOC_ARGS) 1195 { 1196 struct htmlpair tag; 1197 1198 if (n->norm->An.auth == AUTH_split) { 1199 h->flags &= ~HTML_NOSPLIT; 1200 h->flags |= HTML_SPLIT; 1201 return 0; 1202 } 1203 if (n->norm->An.auth == AUTH_nosplit) { 1204 h->flags &= ~HTML_SPLIT; 1205 h->flags |= HTML_NOSPLIT; 1206 return 0; 1207 } 1208 1209 if (h->flags & HTML_SPLIT) 1210 print_otag(h, TAG_BR, 0, NULL); 1211 1212 if (n->sec == SEC_AUTHORS && ! (h->flags & HTML_NOSPLIT)) 1213 h->flags |= HTML_SPLIT; 1214 1215 PAIR_CLASS_INIT(&tag, "author"); 1216 print_otag(h, TAG_SPAN, 1, &tag); 1217 return 1; 1218 } 1219 1220 static int 1221 mdoc_cd_pre(MDOC_ARGS) 1222 { 1223 struct htmlpair tag; 1224 1225 synopsis_pre(h, n); 1226 PAIR_CLASS_INIT(&tag, "config"); 1227 print_otag(h, TAG_B, 1, &tag); 1228 return 1; 1229 } 1230 1231 static int 1232 mdoc_dv_pre(MDOC_ARGS) 1233 { 1234 struct htmlpair tag; 1235 1236 PAIR_CLASS_INIT(&tag, "define"); 1237 print_otag(h, TAG_SPAN, 1, &tag); 1238 return 1; 1239 } 1240 1241 static int 1242 mdoc_ev_pre(MDOC_ARGS) 1243 { 1244 struct htmlpair tag; 1245 1246 PAIR_CLASS_INIT(&tag, "env"); 1247 print_otag(h, TAG_SPAN, 1, &tag); 1248 return 1; 1249 } 1250 1251 static int 1252 mdoc_er_pre(MDOC_ARGS) 1253 { 1254 struct htmlpair tag; 1255 1256 PAIR_CLASS_INIT(&tag, "errno"); 1257 print_otag(h, TAG_SPAN, 1, &tag); 1258 return 1; 1259 } 1260 1261 static int 1262 mdoc_fa_pre(MDOC_ARGS) 1263 { 1264 const struct roff_node *nn; 1265 struct htmlpair tag; 1266 struct tag *t; 1267 1268 PAIR_CLASS_INIT(&tag, "farg"); 1269 if (n->parent->tok != MDOC_Fo) { 1270 print_otag(h, TAG_I, 1, &tag); 1271 return 1; 1272 } 1273 1274 for (nn = n->child; nn; nn = nn->next) { 1275 t = print_otag(h, TAG_I, 1, &tag); 1276 print_text(h, nn->string); 1277 print_tagq(h, t); 1278 if (nn->next) { 1279 h->flags |= HTML_NOSPACE; 1280 print_text(h, ","); 1281 } 1282 } 1283 1284 if (n->child && n->next && n->next->tok == MDOC_Fa) { 1285 h->flags |= HTML_NOSPACE; 1286 print_text(h, ","); 1287 } 1288 1289 return 0; 1290 } 1291 1292 static int 1293 mdoc_fd_pre(MDOC_ARGS) 1294 { 1295 struct htmlpair tag[2]; 1296 char buf[BUFSIZ]; 1297 size_t sz; 1298 int i; 1299 struct tag *t; 1300 1301 synopsis_pre(h, n); 1302 1303 if (NULL == (n = n->child)) 1304 return 0; 1305 1306 assert(n->type == ROFFT_TEXT); 1307 1308 if (strcmp(n->string, "#include")) { 1309 PAIR_CLASS_INIT(&tag[0], "macro"); 1310 print_otag(h, TAG_B, 1, tag); 1311 return 1; 1312 } 1313 1314 PAIR_CLASS_INIT(&tag[0], "includes"); 1315 print_otag(h, TAG_B, 1, tag); 1316 print_text(h, n->string); 1317 1318 if (NULL != (n = n->next)) { 1319 assert(n->type == ROFFT_TEXT); 1320 1321 /* 1322 * XXX This is broken and not easy to fix. 1323 * When using -Oincludes, truncation may occur. 1324 * Dynamic allocation wouldn't help because 1325 * passing long strings to buffmt_includes() 1326 * does not work either. 1327 */ 1328 1329 strlcpy(buf, '<' == *n->string || '"' == *n->string ? 1330 n->string + 1 : n->string, BUFSIZ); 1331 1332 sz = strlen(buf); 1333 if (sz && ('>' == buf[sz - 1] || '"' == buf[sz - 1])) 1334 buf[sz - 1] = '\0'; 1335 1336 PAIR_CLASS_INIT(&tag[0], "link-includes"); 1337 1338 i = 1; 1339 if (h->base_includes) { 1340 buffmt_includes(h, buf); 1341 PAIR_HREF_INIT(&tag[i], h->buf); 1342 i++; 1343 } 1344 1345 t = print_otag(h, TAG_A, i, tag); 1346 print_text(h, n->string); 1347 print_tagq(h, t); 1348 1349 n = n->next; 1350 } 1351 1352 for ( ; n; n = n->next) { 1353 assert(n->type == ROFFT_TEXT); 1354 print_text(h, n->string); 1355 } 1356 1357 return 0; 1358 } 1359 1360 static int 1361 mdoc_vt_pre(MDOC_ARGS) 1362 { 1363 struct htmlpair tag; 1364 1365 if (n->type == ROFFT_BLOCK) { 1366 synopsis_pre(h, n); 1367 return 1; 1368 } else if (n->type == ROFFT_ELEM) { 1369 synopsis_pre(h, n); 1370 } else if (n->type == ROFFT_HEAD) 1371 return 0; 1372 1373 PAIR_CLASS_INIT(&tag, "type"); 1374 print_otag(h, TAG_SPAN, 1, &tag); 1375 return 1; 1376 } 1377 1378 static int 1379 mdoc_ft_pre(MDOC_ARGS) 1380 { 1381 struct htmlpair tag; 1382 1383 synopsis_pre(h, n); 1384 PAIR_CLASS_INIT(&tag, "ftype"); 1385 print_otag(h, TAG_I, 1, &tag); 1386 return 1; 1387 } 1388 1389 static int 1390 mdoc_fn_pre(MDOC_ARGS) 1391 { 1392 struct tag *t; 1393 struct htmlpair tag[2]; 1394 char nbuf[BUFSIZ]; 1395 const char *sp, *ep; 1396 int sz, i, pretty; 1397 1398 pretty = MDOC_SYNPRETTY & n->flags; 1399 synopsis_pre(h, n); 1400 1401 /* Split apart into type and name. */ 1402 assert(n->child->string); 1403 sp = n->child->string; 1404 1405 ep = strchr(sp, ' '); 1406 if (NULL != ep) { 1407 PAIR_CLASS_INIT(&tag[0], "ftype"); 1408 t = print_otag(h, TAG_I, 1, tag); 1409 1410 while (ep) { 1411 sz = MIN((int)(ep - sp), BUFSIZ - 1); 1412 (void)memcpy(nbuf, sp, (size_t)sz); 1413 nbuf[sz] = '\0'; 1414 print_text(h, nbuf); 1415 sp = ++ep; 1416 ep = strchr(sp, ' '); 1417 } 1418 print_tagq(h, t); 1419 } 1420 1421 PAIR_CLASS_INIT(&tag[0], "fname"); 1422 1423 /* 1424 * FIXME: only refer to IDs that we know exist. 1425 */ 1426 1427 #if 0 1428 if (MDOC_SYNPRETTY & n->flags) { 1429 nbuf[0] = '\0'; 1430 html_idcat(nbuf, sp, BUFSIZ); 1431 PAIR_ID_INIT(&tag[1], nbuf); 1432 } else { 1433 strlcpy(nbuf, "#", BUFSIZ); 1434 html_idcat(nbuf, sp, BUFSIZ); 1435 PAIR_HREF_INIT(&tag[1], nbuf); 1436 } 1437 #endif 1438 1439 t = print_otag(h, TAG_B, 1, tag); 1440 1441 if (sp) 1442 print_text(h, sp); 1443 1444 print_tagq(h, t); 1445 1446 h->flags |= HTML_NOSPACE; 1447 print_text(h, "("); 1448 h->flags |= HTML_NOSPACE; 1449 1450 PAIR_CLASS_INIT(&tag[0], "farg"); 1451 bufinit(h); 1452 bufcat_style(h, "white-space", "nowrap"); 1453 PAIR_STYLE_INIT(&tag[1], h); 1454 1455 for (n = n->child->next; n; n = n->next) { 1456 i = 1; 1457 if (MDOC_SYNPRETTY & n->flags) 1458 i = 2; 1459 t = print_otag(h, TAG_I, i, tag); 1460 print_text(h, n->string); 1461 print_tagq(h, t); 1462 if (n->next) { 1463 h->flags |= HTML_NOSPACE; 1464 print_text(h, ","); 1465 } 1466 } 1467 1468 h->flags |= HTML_NOSPACE; 1469 print_text(h, ")"); 1470 1471 if (pretty) { 1472 h->flags |= HTML_NOSPACE; 1473 print_text(h, ";"); 1474 } 1475 1476 return 0; 1477 } 1478 1479 static int 1480 mdoc_sm_pre(MDOC_ARGS) 1481 { 1482 1483 if (NULL == n->child) 1484 h->flags ^= HTML_NONOSPACE; 1485 else if (0 == strcmp("on", n->child->string)) 1486 h->flags &= ~HTML_NONOSPACE; 1487 else 1488 h->flags |= HTML_NONOSPACE; 1489 1490 if ( ! (HTML_NONOSPACE & h->flags)) 1491 h->flags &= ~HTML_NOSPACE; 1492 1493 return 0; 1494 } 1495 1496 static int 1497 mdoc_skip_pre(MDOC_ARGS) 1498 { 1499 1500 return 0; 1501 } 1502 1503 static int 1504 mdoc_pp_pre(MDOC_ARGS) 1505 { 1506 1507 print_paragraph(h); 1508 return 0; 1509 } 1510 1511 static int 1512 mdoc_sp_pre(MDOC_ARGS) 1513 { 1514 struct roffsu su; 1515 struct htmlpair tag; 1516 1517 SCALE_VS_INIT(&su, 1); 1518 1519 if (MDOC_sp == n->tok) { 1520 if (NULL != (n = n->child)) { 1521 if ( ! a2roffsu(n->string, &su, SCALE_VS)) 1522 su.scale = 1.0; 1523 else if (su.scale < 0.0) 1524 su.scale = 0.0; 1525 } 1526 } else 1527 su.scale = 0.0; 1528 1529 bufinit(h); 1530 bufcat_su(h, "height", &su); 1531 PAIR_STYLE_INIT(&tag, h); 1532 print_otag(h, TAG_DIV, 1, &tag); 1533 1534 /* So the div isn't empty: */ 1535 print_text(h, "\\~"); 1536 1537 return 0; 1538 1539 } 1540 1541 static int 1542 mdoc_lk_pre(MDOC_ARGS) 1543 { 1544 struct htmlpair tag[2]; 1545 1546 if (NULL == (n = n->child)) 1547 return 0; 1548 1549 assert(n->type == ROFFT_TEXT); 1550 1551 PAIR_CLASS_INIT(&tag[0], "link-ext"); 1552 PAIR_HREF_INIT(&tag[1], n->string); 1553 1554 print_otag(h, TAG_A, 2, tag); 1555 1556 if (NULL == n->next) 1557 print_text(h, n->string); 1558 1559 for (n = n->next; n; n = n->next) 1560 print_text(h, n->string); 1561 1562 return 0; 1563 } 1564 1565 static int 1566 mdoc_mt_pre(MDOC_ARGS) 1567 { 1568 struct htmlpair tag[2]; 1569 struct tag *t; 1570 1571 PAIR_CLASS_INIT(&tag[0], "link-mail"); 1572 1573 for (n = n->child; n; n = n->next) { 1574 assert(n->type == ROFFT_TEXT); 1575 1576 bufinit(h); 1577 bufcat(h, "mailto:"); 1578 bufcat(h, n->string); 1579 1580 PAIR_HREF_INIT(&tag[1], h->buf); 1581 t = print_otag(h, TAG_A, 2, tag); 1582 print_text(h, n->string); 1583 print_tagq(h, t); 1584 } 1585 1586 return 0; 1587 } 1588 1589 static int 1590 mdoc_fo_pre(MDOC_ARGS) 1591 { 1592 struct htmlpair tag; 1593 struct tag *t; 1594 1595 if (n->type == ROFFT_BODY) { 1596 h->flags |= HTML_NOSPACE; 1597 print_text(h, "("); 1598 h->flags |= HTML_NOSPACE; 1599 return 1; 1600 } else if (n->type == ROFFT_BLOCK) { 1601 synopsis_pre(h, n); 1602 return 1; 1603 } 1604 1605 if (n->child == NULL) 1606 return 0; 1607 1608 assert(n->child->string); 1609 PAIR_CLASS_INIT(&tag, "fname"); 1610 t = print_otag(h, TAG_B, 1, &tag); 1611 print_text(h, n->child->string); 1612 print_tagq(h, t); 1613 return 0; 1614 } 1615 1616 static void 1617 mdoc_fo_post(MDOC_ARGS) 1618 { 1619 1620 if (n->type != ROFFT_BODY) 1621 return; 1622 h->flags |= HTML_NOSPACE; 1623 print_text(h, ")"); 1624 h->flags |= HTML_NOSPACE; 1625 print_text(h, ";"); 1626 } 1627 1628 static int 1629 mdoc_in_pre(MDOC_ARGS) 1630 { 1631 struct tag *t; 1632 struct htmlpair tag[2]; 1633 int i; 1634 1635 synopsis_pre(h, n); 1636 1637 PAIR_CLASS_INIT(&tag[0], "includes"); 1638 print_otag(h, TAG_B, 1, tag); 1639 1640 /* 1641 * The first argument of the `In' gets special treatment as 1642 * being a linked value. Subsequent values are printed 1643 * afterward. groff does similarly. This also handles the case 1644 * of no children. 1645 */ 1646 1647 if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags) 1648 print_text(h, "#include"); 1649 1650 print_text(h, "<"); 1651 h->flags |= HTML_NOSPACE; 1652 1653 if (NULL != (n = n->child)) { 1654 assert(n->type == ROFFT_TEXT); 1655 1656 PAIR_CLASS_INIT(&tag[0], "link-includes"); 1657 1658 i = 1; 1659 if (h->base_includes) { 1660 buffmt_includes(h, n->string); 1661 PAIR_HREF_INIT(&tag[i], h->buf); 1662 i++; 1663 } 1664 1665 t = print_otag(h, TAG_A, i, tag); 1666 print_text(h, n->string); 1667 print_tagq(h, t); 1668 1669 n = n->next; 1670 } 1671 1672 h->flags |= HTML_NOSPACE; 1673 print_text(h, ">"); 1674 1675 for ( ; n; n = n->next) { 1676 assert(n->type == ROFFT_TEXT); 1677 print_text(h, n->string); 1678 } 1679 1680 return 0; 1681 } 1682 1683 static int 1684 mdoc_ic_pre(MDOC_ARGS) 1685 { 1686 struct htmlpair tag; 1687 1688 PAIR_CLASS_INIT(&tag, "cmd"); 1689 print_otag(h, TAG_B, 1, &tag); 1690 return 1; 1691 } 1692 1693 static int 1694 mdoc_rv_pre(MDOC_ARGS) 1695 { 1696 struct htmlpair tag; 1697 struct tag *t; 1698 struct roff_node *nch; 1699 1700 if (n->prev) 1701 print_otag(h, TAG_BR, 0, NULL); 1702 1703 PAIR_CLASS_INIT(&tag, "fname"); 1704 1705 if (n->child != NULL) { 1706 print_text(h, "The"); 1707 1708 for (nch = n->child; nch != NULL; nch = nch->next) { 1709 t = print_otag(h, TAG_B, 1, &tag); 1710 print_text(h, nch->string); 1711 print_tagq(h, t); 1712 1713 h->flags |= HTML_NOSPACE; 1714 print_text(h, "()"); 1715 1716 if (nch->next == NULL) 1717 continue; 1718 1719 if (nch->prev != NULL || nch->next->next != NULL) { 1720 h->flags |= HTML_NOSPACE; 1721 print_text(h, ","); 1722 } 1723 if (nch->next->next == NULL) 1724 print_text(h, "and"); 1725 } 1726 1727 if (n->child != NULL && n->child->next != NULL) 1728 print_text(h, "functions return"); 1729 else 1730 print_text(h, "function returns"); 1731 1732 print_text(h, "the value\\~0 if successful;"); 1733 } else 1734 print_text(h, "Upon successful completion," 1735 " the value\\~0 is returned;"); 1736 1737 print_text(h, "otherwise the value\\~\\-1 is returned" 1738 " and the global variable"); 1739 1740 PAIR_CLASS_INIT(&tag, "var"); 1741 t = print_otag(h, TAG_B, 1, &tag); 1742 print_text(h, "errno"); 1743 print_tagq(h, t); 1744 print_text(h, "is set to indicate the error."); 1745 return 0; 1746 } 1747 1748 static int 1749 mdoc_va_pre(MDOC_ARGS) 1750 { 1751 struct htmlpair tag; 1752 1753 PAIR_CLASS_INIT(&tag, "var"); 1754 print_otag(h, TAG_B, 1, &tag); 1755 return 1; 1756 } 1757 1758 static int 1759 mdoc_ap_pre(MDOC_ARGS) 1760 { 1761 1762 h->flags |= HTML_NOSPACE; 1763 print_text(h, "\\(aq"); 1764 h->flags |= HTML_NOSPACE; 1765 return 1; 1766 } 1767 1768 static int 1769 mdoc_bf_pre(MDOC_ARGS) 1770 { 1771 struct htmlpair tag[2]; 1772 struct roffsu su; 1773 1774 if (n->type == ROFFT_HEAD) 1775 return 0; 1776 else if (n->type != ROFFT_BODY) 1777 return 1; 1778 1779 if (FONT_Em == n->norm->Bf.font) 1780 PAIR_CLASS_INIT(&tag[0], "emph"); 1781 else if (FONT_Sy == n->norm->Bf.font) 1782 PAIR_CLASS_INIT(&tag[0], "symb"); 1783 else if (FONT_Li == n->norm->Bf.font) 1784 PAIR_CLASS_INIT(&tag[0], "lit"); 1785 else 1786 PAIR_CLASS_INIT(&tag[0], "none"); 1787 1788 /* 1789 * We want this to be inline-formatted, but needs to be div to 1790 * accept block children. 1791 */ 1792 bufinit(h); 1793 bufcat_style(h, "display", "inline"); 1794 SCALE_HS_INIT(&su, 1); 1795 /* Needs a left-margin for spacing. */ 1796 bufcat_su(h, "margin-left", &su); 1797 PAIR_STYLE_INIT(&tag[1], h); 1798 print_otag(h, TAG_DIV, 2, tag); 1799 return 1; 1800 } 1801 1802 static int 1803 mdoc_ms_pre(MDOC_ARGS) 1804 { 1805 struct htmlpair tag; 1806 1807 PAIR_CLASS_INIT(&tag, "symb"); 1808 print_otag(h, TAG_SPAN, 1, &tag); 1809 return 1; 1810 } 1811 1812 static int 1813 mdoc_igndelim_pre(MDOC_ARGS) 1814 { 1815 1816 h->flags |= HTML_IGNDELIM; 1817 return 1; 1818 } 1819 1820 static void 1821 mdoc_pf_post(MDOC_ARGS) 1822 { 1823 1824 if ( ! (n->next == NULL || n->next->flags & MDOC_LINE)) 1825 h->flags |= HTML_NOSPACE; 1826 } 1827 1828 static int 1829 mdoc_rs_pre(MDOC_ARGS) 1830 { 1831 struct htmlpair tag; 1832 1833 if (n->type != ROFFT_BLOCK) 1834 return 1; 1835 1836 if (n->prev && SEC_SEE_ALSO == n->sec) 1837 print_paragraph(h); 1838 1839 PAIR_CLASS_INIT(&tag, "ref"); 1840 print_otag(h, TAG_SPAN, 1, &tag); 1841 return 1; 1842 } 1843 1844 static int 1845 mdoc_no_pre(MDOC_ARGS) 1846 { 1847 struct htmlpair tag; 1848 1849 PAIR_CLASS_INIT(&tag, "none"); 1850 print_otag(h, TAG_CODE, 1, &tag); 1851 return 1; 1852 } 1853 1854 static int 1855 mdoc_li_pre(MDOC_ARGS) 1856 { 1857 struct htmlpair tag; 1858 1859 PAIR_CLASS_INIT(&tag, "lit"); 1860 print_otag(h, TAG_CODE, 1, &tag); 1861 return 1; 1862 } 1863 1864 static int 1865 mdoc_sy_pre(MDOC_ARGS) 1866 { 1867 struct htmlpair tag; 1868 1869 PAIR_CLASS_INIT(&tag, "symb"); 1870 print_otag(h, TAG_SPAN, 1, &tag); 1871 return 1; 1872 } 1873 1874 static int 1875 mdoc_bt_pre(MDOC_ARGS) 1876 { 1877 1878 print_text(h, "is currently in beta test."); 1879 return 0; 1880 } 1881 1882 static int 1883 mdoc_ud_pre(MDOC_ARGS) 1884 { 1885 1886 print_text(h, "currently under development."); 1887 return 0; 1888 } 1889 1890 static int 1891 mdoc_lb_pre(MDOC_ARGS) 1892 { 1893 struct htmlpair tag; 1894 1895 if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags && n->prev) 1896 print_otag(h, TAG_BR, 0, NULL); 1897 1898 PAIR_CLASS_INIT(&tag, "lib"); 1899 print_otag(h, TAG_SPAN, 1, &tag); 1900 return 1; 1901 } 1902 1903 static int 1904 mdoc__x_pre(MDOC_ARGS) 1905 { 1906 struct htmlpair tag[2]; 1907 enum htmltag t; 1908 1909 t = TAG_SPAN; 1910 1911 switch (n->tok) { 1912 case MDOC__A: 1913 PAIR_CLASS_INIT(&tag[0], "ref-auth"); 1914 if (n->prev && MDOC__A == n->prev->tok) 1915 if (NULL == n->next || MDOC__A != n->next->tok) 1916 print_text(h, "and"); 1917 break; 1918 case MDOC__B: 1919 PAIR_CLASS_INIT(&tag[0], "ref-book"); 1920 t = TAG_I; 1921 break; 1922 case MDOC__C: 1923 PAIR_CLASS_INIT(&tag[0], "ref-city"); 1924 break; 1925 case MDOC__D: 1926 PAIR_CLASS_INIT(&tag[0], "ref-date"); 1927 break; 1928 case MDOC__I: 1929 PAIR_CLASS_INIT(&tag[0], "ref-issue"); 1930 t = TAG_I; 1931 break; 1932 case MDOC__J: 1933 PAIR_CLASS_INIT(&tag[0], "ref-jrnl"); 1934 t = TAG_I; 1935 break; 1936 case MDOC__N: 1937 PAIR_CLASS_INIT(&tag[0], "ref-num"); 1938 break; 1939 case MDOC__O: 1940 PAIR_CLASS_INIT(&tag[0], "ref-opt"); 1941 break; 1942 case MDOC__P: 1943 PAIR_CLASS_INIT(&tag[0], "ref-page"); 1944 break; 1945 case MDOC__Q: 1946 PAIR_CLASS_INIT(&tag[0], "ref-corp"); 1947 break; 1948 case MDOC__R: 1949 PAIR_CLASS_INIT(&tag[0], "ref-rep"); 1950 break; 1951 case MDOC__T: 1952 PAIR_CLASS_INIT(&tag[0], "ref-title"); 1953 break; 1954 case MDOC__U: 1955 PAIR_CLASS_INIT(&tag[0], "link-ref"); 1956 break; 1957 case MDOC__V: 1958 PAIR_CLASS_INIT(&tag[0], "ref-vol"); 1959 break; 1960 default: 1961 abort(); 1962 } 1963 1964 if (MDOC__U != n->tok) { 1965 print_otag(h, t, 1, tag); 1966 return 1; 1967 } 1968 1969 PAIR_HREF_INIT(&tag[1], n->child->string); 1970 print_otag(h, TAG_A, 2, tag); 1971 1972 return 1; 1973 } 1974 1975 static void 1976 mdoc__x_post(MDOC_ARGS) 1977 { 1978 1979 if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok) 1980 if (NULL == n->next->next || MDOC__A != n->next->next->tok) 1981 if (NULL == n->prev || MDOC__A != n->prev->tok) 1982 return; 1983 1984 /* TODO: %U */ 1985 1986 if (NULL == n->parent || MDOC_Rs != n->parent->tok) 1987 return; 1988 1989 h->flags |= HTML_NOSPACE; 1990 print_text(h, n->next ? "," : "."); 1991 } 1992 1993 static int 1994 mdoc_bk_pre(MDOC_ARGS) 1995 { 1996 1997 switch (n->type) { 1998 case ROFFT_BLOCK: 1999 break; 2000 case ROFFT_HEAD: 2001 return 0; 2002 case ROFFT_BODY: 2003 if (n->parent->args != NULL || n->prev->child == NULL) 2004 h->flags |= HTML_PREKEEP; 2005 break; 2006 default: 2007 abort(); 2008 } 2009 2010 return 1; 2011 } 2012 2013 static void 2014 mdoc_bk_post(MDOC_ARGS) 2015 { 2016 2017 if (n->type == ROFFT_BODY) 2018 h->flags &= ~(HTML_KEEP | HTML_PREKEEP); 2019 } 2020 2021 static int 2022 mdoc_quote_pre(MDOC_ARGS) 2023 { 2024 struct htmlpair tag; 2025 2026 if (n->type != ROFFT_BODY) 2027 return 1; 2028 2029 switch (n->tok) { 2030 case MDOC_Ao: 2031 case MDOC_Aq: 2032 print_text(h, n->child != NULL && n->child->next == NULL && 2033 n->child->tok == MDOC_Mt ? "<" : "\\(la"); 2034 break; 2035 case MDOC_Bro: 2036 case MDOC_Brq: 2037 print_text(h, "\\(lC"); 2038 break; 2039 case MDOC_Bo: 2040 case MDOC_Bq: 2041 print_text(h, "\\(lB"); 2042 break; 2043 case MDOC_Oo: 2044 case MDOC_Op: 2045 print_text(h, "\\(lB"); 2046 h->flags |= HTML_NOSPACE; 2047 PAIR_CLASS_INIT(&tag, "opt"); 2048 print_otag(h, TAG_SPAN, 1, &tag); 2049 break; 2050 case MDOC_En: 2051 if (NULL == n->norm->Es || 2052 NULL == n->norm->Es->child) 2053 return 1; 2054 print_text(h, n->norm->Es->child->string); 2055 break; 2056 case MDOC_Do: 2057 case MDOC_Dq: 2058 case MDOC_Qo: 2059 case MDOC_Qq: 2060 print_text(h, "\\(lq"); 2061 break; 2062 case MDOC_Po: 2063 case MDOC_Pq: 2064 print_text(h, "("); 2065 break; 2066 case MDOC_Ql: 2067 print_text(h, "\\(oq"); 2068 h->flags |= HTML_NOSPACE; 2069 PAIR_CLASS_INIT(&tag, "lit"); 2070 print_otag(h, TAG_CODE, 1, &tag); 2071 break; 2072 case MDOC_So: 2073 case MDOC_Sq: 2074 print_text(h, "\\(oq"); 2075 break; 2076 default: 2077 abort(); 2078 } 2079 2080 h->flags |= HTML_NOSPACE; 2081 return 1; 2082 } 2083 2084 static void 2085 mdoc_quote_post(MDOC_ARGS) 2086 { 2087 2088 if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM) 2089 return; 2090 2091 h->flags |= HTML_NOSPACE; 2092 2093 switch (n->tok) { 2094 case MDOC_Ao: 2095 case MDOC_Aq: 2096 print_text(h, n->child != NULL && n->child->next == NULL && 2097 n->child->tok == MDOC_Mt ? ">" : "\\(ra"); 2098 break; 2099 case MDOC_Bro: 2100 case MDOC_Brq: 2101 print_text(h, "\\(rC"); 2102 break; 2103 case MDOC_Oo: 2104 case MDOC_Op: 2105 case MDOC_Bo: 2106 case MDOC_Bq: 2107 print_text(h, "\\(rB"); 2108 break; 2109 case MDOC_En: 2110 if (n->norm->Es == NULL || 2111 n->norm->Es->child == NULL || 2112 n->norm->Es->child->next == NULL) 2113 h->flags &= ~HTML_NOSPACE; 2114 else 2115 print_text(h, n->norm->Es->child->next->string); 2116 break; 2117 case MDOC_Qo: 2118 case MDOC_Qq: 2119 case MDOC_Do: 2120 case MDOC_Dq: 2121 print_text(h, "\\(rq"); 2122 break; 2123 case MDOC_Po: 2124 case MDOC_Pq: 2125 print_text(h, ")"); 2126 break; 2127 case MDOC_Ql: 2128 case MDOC_So: 2129 case MDOC_Sq: 2130 print_text(h, "\\(cq"); 2131 break; 2132 default: 2133 abort(); 2134 } 2135 } 2136 2137 static int 2138 mdoc_eo_pre(MDOC_ARGS) 2139 { 2140 2141 if (n->type != ROFFT_BODY) 2142 return 1; 2143 2144 if (n->end == ENDBODY_NOT && 2145 n->parent->head->child == NULL && 2146 n->child != NULL && 2147 n->child->end != ENDBODY_NOT) 2148 print_text(h, "\\&"); 2149 else if (n->end != ENDBODY_NOT ? n->child != NULL : 2150 n->parent->head->child != NULL && (n->child != NULL || 2151 (n->parent->tail != NULL && n->parent->tail->child != NULL))) 2152 h->flags |= HTML_NOSPACE; 2153 return 1; 2154 } 2155 2156 static void 2157 mdoc_eo_post(MDOC_ARGS) 2158 { 2159 int body, tail; 2160 2161 if (n->type != ROFFT_BODY) 2162 return; 2163 2164 if (n->end != ENDBODY_NOT) { 2165 h->flags &= ~HTML_NOSPACE; 2166 return; 2167 } 2168 2169 body = n->child != NULL || n->parent->head->child != NULL; 2170 tail = n->parent->tail != NULL && n->parent->tail->child != NULL; 2171 2172 if (body && tail) 2173 h->flags |= HTML_NOSPACE; 2174 else if ( ! tail) 2175 h->flags &= ~HTML_NOSPACE; 2176 } 2177