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