1 /* $Id: mdoc_markdown.c,v 1.40 2025/06/26 17:06:34 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2017, 2018, 2020, 2025 Ingo Schwarze <schwarze@openbsd.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 * 17 * Markdown formatter for mdoc(7) used by mandoc(1). 18 */ 19 #include "config.h" 20 21 #include <sys/types.h> 22 23 #include <assert.h> 24 #include <ctype.h> 25 #include <stdio.h> 26 #include <stdlib.h> 27 #include <string.h> 28 29 #include "mandoc_aux.h" 30 #include "mandoc.h" 31 #include "roff.h" 32 #include "mdoc.h" 33 #include "main.h" 34 35 struct md_act { 36 int (*cond)(struct roff_node *); 37 int (*pre)(struct roff_node *); 38 void (*post)(struct roff_node *); 39 const char *prefix; /* pre-node string constant */ 40 const char *suffix; /* post-node string constant */ 41 }; 42 43 static void md_nodelist(struct roff_node *); 44 static void md_node(struct roff_node *); 45 static const char *md_stack(char); 46 static void md_preword(void); 47 static void md_rawword(const char *); 48 static void md_word(const char *); 49 static void md_named(const char *); 50 static void md_char(unsigned char); 51 static void md_uri(const char *); 52 53 static int md_cond_head(struct roff_node *); 54 static int md_cond_body(struct roff_node *); 55 56 static int md_pre_abort(struct roff_node *); 57 static int md_pre_raw(struct roff_node *); 58 static int md_pre_word(struct roff_node *); 59 static int md_pre_skip(struct roff_node *); 60 static void md_pre_syn(struct roff_node *); 61 static int md_pre_An(struct roff_node *); 62 static int md_pre_Ap(struct roff_node *); 63 static int md_pre_Bd(struct roff_node *); 64 static int md_pre_Bk(struct roff_node *); 65 static int md_pre_Bl(struct roff_node *); 66 static int md_pre_D1(struct roff_node *); 67 static int md_pre_Dl(struct roff_node *); 68 static int md_pre_En(struct roff_node *); 69 static int md_pre_Eo(struct roff_node *); 70 static int md_pre_Fa(struct roff_node *); 71 static int md_pre_Fd(struct roff_node *); 72 static int md_pre_Fn(struct roff_node *); 73 static int md_pre_Fo(struct roff_node *); 74 static int md_pre_In(struct roff_node *); 75 static int md_pre_It(struct roff_node *); 76 static int md_pre_Lk(struct roff_node *); 77 static int md_pre_Mt(struct roff_node *); 78 static int md_pre_Nd(struct roff_node *); 79 static int md_pre_Nm(struct roff_node *); 80 static int md_pre_No(struct roff_node *); 81 static int md_pre_Ns(struct roff_node *); 82 static int md_pre_Pp(struct roff_node *); 83 static int md_pre_Rs(struct roff_node *); 84 static int md_pre_Sh(struct roff_node *); 85 static int md_pre_Sm(struct roff_node *); 86 static int md_pre_Vt(struct roff_node *); 87 static int md_pre_Xr(struct roff_node *); 88 static int md_pre__R(struct roff_node *); 89 static int md_pre__T(struct roff_node *); 90 static int md_pre_br(struct roff_node *); 91 92 static void md_post_raw(struct roff_node *); 93 static void md_post_word(struct roff_node *); 94 static void md_post_pc(struct roff_node *); 95 static void md_post_Bk(struct roff_node *); 96 static void md_post_Bl(struct roff_node *); 97 static void md_post_D1(struct roff_node *); 98 static void md_post_En(struct roff_node *); 99 static void md_post_Eo(struct roff_node *); 100 static void md_post_Fa(struct roff_node *); 101 static void md_post_Fd(struct roff_node *); 102 static void md_post_Fl(struct roff_node *); 103 static void md_post_Fn(struct roff_node *); 104 static void md_post_Fo(struct roff_node *); 105 static void md_post_In(struct roff_node *); 106 static void md_post_It(struct roff_node *); 107 static void md_post_Lb(struct roff_node *); 108 static void md_post_Nm(struct roff_node *); 109 static void md_post_Pf(struct roff_node *); 110 static void md_post_Vt(struct roff_node *); 111 static void md_post__T(struct roff_node *); 112 113 static const struct md_act md_acts[MDOC_MAX - MDOC_Dd] = { 114 { NULL, NULL, NULL, NULL, NULL }, /* Dd */ 115 { NULL, NULL, NULL, NULL, NULL }, /* Dt */ 116 { NULL, NULL, NULL, NULL, NULL }, /* Os */ 117 { NULL, md_pre_Sh, NULL, NULL, NULL }, /* Sh */ 118 { NULL, md_pre_Sh, NULL, NULL, NULL }, /* Ss */ 119 { NULL, md_pre_Pp, NULL, NULL, NULL }, /* Pp */ 120 { md_cond_body, md_pre_D1, md_post_D1, NULL, NULL }, /* D1 */ 121 { md_cond_body, md_pre_Dl, md_post_D1, NULL, NULL }, /* Dl */ 122 { md_cond_body, md_pre_Bd, md_post_D1, NULL, NULL }, /* Bd */ 123 { NULL, NULL, NULL, NULL, NULL }, /* Ed */ 124 { md_cond_body, md_pre_Bl, md_post_Bl, NULL, NULL }, /* Bl */ 125 { NULL, NULL, NULL, NULL, NULL }, /* El */ 126 { NULL, md_pre_It, md_post_It, NULL, NULL }, /* It */ 127 { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Ad */ 128 { NULL, md_pre_An, NULL, NULL, NULL }, /* An */ 129 { NULL, md_pre_Ap, NULL, NULL, NULL }, /* Ap */ 130 { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Ar */ 131 { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Cd */ 132 { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Cm */ 133 { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Dv */ 134 { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Er */ 135 { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Ev */ 136 { NULL, NULL, NULL, NULL, NULL }, /* Ex */ 137 { NULL, md_pre_Fa, md_post_Fa, NULL, NULL }, /* Fa */ 138 { NULL, md_pre_Fd, md_post_Fd, "**", "**" }, /* Fd */ 139 { NULL, md_pre_raw, md_post_Fl, "**-", "**" }, /* Fl */ 140 { NULL, md_pre_Fn, md_post_Fn, NULL, NULL }, /* Fn */ 141 { NULL, md_pre_Fd, md_post_raw, "*", "*" }, /* Ft */ 142 { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Ic */ 143 { NULL, md_pre_In, md_post_In, NULL, NULL }, /* In */ 144 { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Li */ 145 { md_cond_head, md_pre_Nd, NULL, NULL, NULL }, /* Nd */ 146 { NULL, md_pre_Nm, md_post_Nm, "**", "**" }, /* Nm */ 147 { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Op */ 148 { NULL, md_pre_abort, NULL, NULL, NULL }, /* Ot */ 149 { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Pa */ 150 { NULL, NULL, NULL, NULL, NULL }, /* Rv */ 151 { NULL, NULL, NULL, NULL, NULL }, /* St */ 152 { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Va */ 153 { NULL, md_pre_Vt, md_post_Vt, "*", "*" }, /* Vt */ 154 { NULL, md_pre_Xr, NULL, NULL, NULL }, /* Xr */ 155 { NULL, NULL, md_post_pc, NULL, NULL }, /* %A */ 156 { NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %B */ 157 { NULL, NULL, md_post_pc, NULL, NULL }, /* %D */ 158 { NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %I */ 159 { NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %J */ 160 { NULL, NULL, md_post_pc, NULL, NULL }, /* %N */ 161 { NULL, NULL, md_post_pc, NULL, NULL }, /* %O */ 162 { NULL, NULL, md_post_pc, NULL, NULL }, /* %P */ 163 { NULL, md_pre__R, md_post_pc, NULL, NULL }, /* %R */ 164 { NULL, md_pre__T, md_post__T, NULL, NULL }, /* %T */ 165 { NULL, NULL, md_post_pc, NULL, NULL }, /* %V */ 166 { NULL, NULL, NULL, NULL, NULL }, /* Ac */ 167 { md_cond_body, md_pre_word, md_post_word, "<", ">" }, /* Ao */ 168 { md_cond_body, md_pre_word, md_post_word, "<", ">" }, /* Aq */ 169 { NULL, NULL, NULL, NULL, NULL }, /* At */ 170 { NULL, NULL, NULL, NULL, NULL }, /* Bc */ 171 { NULL, NULL, NULL, NULL, NULL }, /* Bf XXX not implemented */ 172 { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Bo */ 173 { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Bq */ 174 { NULL, NULL, NULL, NULL, NULL }, /* Bsx */ 175 { NULL, NULL, NULL, NULL, NULL }, /* Bx */ 176 { NULL, NULL, NULL, NULL, NULL }, /* Db */ 177 { NULL, NULL, NULL, NULL, NULL }, /* Dc */ 178 { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Do */ 179 { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Dq */ 180 { NULL, NULL, NULL, NULL, NULL }, /* Ec */ 181 { NULL, NULL, NULL, NULL, NULL }, /* Ef */ 182 { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Em */ 183 { md_cond_body, md_pre_Eo, md_post_Eo, NULL, NULL }, /* Eo */ 184 { NULL, NULL, NULL, NULL, NULL }, /* Fx */ 185 { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Ms */ 186 { NULL, md_pre_No, NULL, NULL, NULL }, /* No */ 187 { NULL, md_pre_Ns, NULL, NULL, NULL }, /* Ns */ 188 { NULL, NULL, NULL, NULL, NULL }, /* Nx */ 189 { NULL, NULL, NULL, NULL, NULL }, /* Ox */ 190 { NULL, NULL, NULL, NULL, NULL }, /* Pc */ 191 { NULL, NULL, md_post_Pf, NULL, NULL }, /* Pf */ 192 { md_cond_body, md_pre_word, md_post_word, "(", ")" }, /* Po */ 193 { md_cond_body, md_pre_word, md_post_word, "(", ")" }, /* Pq */ 194 { NULL, NULL, NULL, NULL, NULL }, /* Qc */ 195 { md_cond_body, md_pre_raw, md_post_raw, "'`", "`'" }, /* Ql */ 196 { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Qo */ 197 { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Qq */ 198 { NULL, NULL, NULL, NULL, NULL }, /* Re */ 199 { md_cond_body, md_pre_Rs, NULL, NULL, NULL }, /* Rs */ 200 { NULL, NULL, NULL, NULL, NULL }, /* Sc */ 201 { md_cond_body, md_pre_word, md_post_word, "'", "'" }, /* So */ 202 { md_cond_body, md_pre_word, md_post_word, "'", "'" }, /* Sq */ 203 { NULL, md_pre_Sm, NULL, NULL, NULL }, /* Sm */ 204 { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Sx */ 205 { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Sy */ 206 { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Tn */ 207 { NULL, NULL, NULL, NULL, NULL }, /* Ux */ 208 { NULL, NULL, NULL, NULL, NULL }, /* Xc */ 209 { NULL, NULL, NULL, NULL, NULL }, /* Xo */ 210 { NULL, md_pre_Fo, md_post_Fo, "**", "**" }, /* Fo */ 211 { NULL, NULL, NULL, NULL, NULL }, /* Fc */ 212 { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Oo */ 213 { NULL, NULL, NULL, NULL, NULL }, /* Oc */ 214 { NULL, md_pre_Bk, md_post_Bk, NULL, NULL }, /* Bk */ 215 { NULL, NULL, NULL, NULL, NULL }, /* Ek */ 216 { NULL, NULL, NULL, NULL, NULL }, /* Bt */ 217 { NULL, NULL, NULL, NULL, NULL }, /* Hf */ 218 { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Fr */ 219 { NULL, NULL, NULL, NULL, NULL }, /* Ud */ 220 { NULL, NULL, md_post_Lb, NULL, NULL }, /* Lb */ 221 { NULL, md_pre_abort, NULL, NULL, NULL }, /* Lp */ 222 { NULL, md_pre_Lk, NULL, NULL, NULL }, /* Lk */ 223 { NULL, md_pre_Mt, NULL, NULL, NULL }, /* Mt */ 224 { md_cond_body, md_pre_word, md_post_word, "{", "}" }, /* Brq */ 225 { md_cond_body, md_pre_word, md_post_word, "{", "}" }, /* Bro */ 226 { NULL, NULL, NULL, NULL, NULL }, /* Brc */ 227 { NULL, NULL, md_post_pc, NULL, NULL }, /* %C */ 228 { NULL, md_pre_skip, NULL, NULL, NULL }, /* Es */ 229 { md_cond_body, md_pre_En, md_post_En, NULL, NULL }, /* En */ 230 { NULL, NULL, NULL, NULL, NULL }, /* Dx */ 231 { NULL, NULL, md_post_pc, NULL, NULL }, /* %Q */ 232 { NULL, md_pre_Lk, md_post_pc, NULL, NULL }, /* %U */ 233 { NULL, NULL, NULL, NULL, NULL }, /* Ta */ 234 { NULL, md_pre_skip, NULL, NULL, NULL }, /* Tg */ 235 }; 236 static const struct md_act *md_act(enum roff_tok); 237 238 static int outflags; 239 #define MD_spc (1 << 0) /* Blank character before next word. */ 240 #define MD_spc_force (1 << 1) /* Even before trailing punctuation. */ 241 #define MD_nonl (1 << 2) /* Prevent linebreak in markdown code. */ 242 #define MD_nl (1 << 3) /* Break markdown code line. */ 243 #define MD_br (1 << 4) /* Insert an output line break. */ 244 #define MD_sp (1 << 5) /* Insert a paragraph break. */ 245 #define MD_Sm (1 << 6) /* Horizontal spacing mode. */ 246 #define MD_Bk (1 << 7) /* Word keep mode. */ 247 #define MD_An_split (1 << 8) /* Author mode is "split". */ 248 #define MD_An_nosplit (1 << 9) /* Author mode is "nosplit". */ 249 250 static int escflags; /* Escape in generated markdown code: */ 251 #define ESC_BOL (1 << 0) /* "#*+-" near the beginning of a line. */ 252 #define ESC_NUM (1 << 1) /* "." after a leading number. */ 253 #define ESC_HYP (1 << 2) /* "(" immediately after "]". */ 254 #define ESC_SQU (1 << 4) /* "]" when "[" is open. */ 255 #define ESC_FON (1 << 5) /* "*" immediately after unrelated "*". */ 256 #define ESC_EOL (1 << 6) /* " " at the and of a line. */ 257 258 static int code_blocks, quote_blocks, list_blocks; 259 static int outcount; 260 261 262 static const struct md_act * 263 md_act(enum roff_tok tok) 264 { 265 assert(tok >= MDOC_Dd && tok <= MDOC_MAX); 266 return md_acts + (tok - MDOC_Dd); 267 } 268 269 void 270 markdown_mdoc(void *arg, const struct roff_meta *mdoc) 271 { 272 outflags = MD_Sm; 273 md_word(mdoc->title); 274 if (mdoc->msec != NULL) { 275 outflags &= ~MD_spc; 276 md_word("("); 277 md_word(mdoc->msec); 278 md_word(")"); 279 } 280 md_word("-"); 281 md_word(mdoc->vol); 282 if (mdoc->arch != NULL) { 283 md_word("("); 284 md_word(mdoc->arch); 285 md_word(")"); 286 } 287 outflags |= MD_sp; 288 289 md_nodelist(mdoc->first->child); 290 291 outflags |= MD_sp; 292 md_word(mdoc->os); 293 md_word("-"); 294 md_word(mdoc->date); 295 md_word("-"); 296 md_word(mdoc->title); 297 if (mdoc->msec != NULL) { 298 outflags &= ~MD_spc; 299 md_word("("); 300 md_word(mdoc->msec); 301 md_word(")"); 302 } 303 putchar('\n'); 304 } 305 306 static void 307 md_nodelist(struct roff_node *n) 308 { 309 while (n != NULL) { 310 md_node(n); 311 n = n->next; 312 } 313 } 314 315 static void 316 md_node(struct roff_node *n) 317 { 318 const struct md_act *act; 319 int cond, process_children; 320 321 if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT) 322 return; 323 324 if (outflags & MD_nonl) 325 outflags &= ~(MD_nl | MD_sp); 326 else if (outflags & MD_spc && 327 n->flags & NODE_LINE && 328 !roff_node_transparent(n)) 329 outflags |= MD_nl; 330 331 act = NULL; 332 cond = 0; 333 process_children = 1; 334 n->flags &= ~NODE_ENDED; 335 336 if (n->type == ROFFT_TEXT) { 337 if (n->flags & NODE_DELIMC) 338 outflags &= ~(MD_spc | MD_spc_force); 339 else if (outflags & MD_Sm) 340 outflags |= MD_spc_force; 341 md_word(n->string); 342 if (n->flags & NODE_DELIMO) 343 outflags &= ~(MD_spc | MD_spc_force); 344 else if (outflags & MD_Sm) 345 outflags |= MD_spc; 346 } else if (n->tok < ROFF_MAX) { 347 switch (n->tok) { 348 case ROFF_br: 349 process_children = md_pre_br(n); 350 break; 351 case ROFF_sp: 352 process_children = md_pre_Pp(n); 353 break; 354 default: 355 process_children = 0; 356 break; 357 } 358 } else { 359 act = md_act(n->tok); 360 cond = act->cond == NULL || (*act->cond)(n); 361 if (cond && act->pre != NULL && 362 (n->end == ENDBODY_NOT || n->child != NULL)) 363 process_children = (*act->pre)(n); 364 } 365 366 if (process_children && n->child != NULL) 367 md_nodelist(n->child); 368 369 if (n->flags & NODE_ENDED) 370 return; 371 372 if (cond && act->post != NULL) 373 (*act->post)(n); 374 375 if (n->end != ENDBODY_NOT) 376 n->body->flags |= NODE_ENDED; 377 } 378 379 static const char * 380 md_stack(char c) 381 { 382 static char *stack; 383 static size_t sz; 384 static size_t cur; 385 386 switch (c) { 387 case '\0': 388 break; 389 case (char)-1: 390 assert(cur); 391 stack[--cur] = '\0'; 392 break; 393 default: 394 if (cur + 1 >= sz) { 395 sz += 8; 396 stack = mandoc_realloc(stack, sz); 397 } 398 stack[cur] = c; 399 stack[++cur] = '\0'; 400 break; 401 } 402 return stack == NULL ? "" : stack; 403 } 404 405 /* 406 * Handle vertical and horizontal spacing. 407 */ 408 static void 409 md_preword(void) 410 { 411 const char *cp; 412 413 /* 414 * If a list block is nested inside a code block or a blockquote, 415 * blank lines for paragraph breaks no longer work; instead, 416 * they terminate the list. Work around this markdown issue 417 * by using mere line breaks instead. 418 */ 419 420 if (list_blocks && outflags & MD_sp) { 421 outflags &= ~MD_sp; 422 outflags |= MD_br; 423 } 424 425 /* 426 * End the old line if requested. 427 * Escape whitespace at the end of the markdown line 428 * such that it won't look like an output line break. 429 */ 430 431 if (outflags & MD_sp) 432 putchar('\n'); 433 else if (outflags & MD_br) { 434 putchar(' '); 435 putchar(' '); 436 } else if (outflags & MD_nl && escflags & ESC_EOL) 437 md_named("zwnj"); 438 439 /* Start a new line if necessary. */ 440 441 if (outflags & (MD_nl | MD_br | MD_sp)) { 442 putchar('\n'); 443 for (cp = md_stack('\0'); *cp != '\0'; cp++) { 444 putchar(*cp); 445 if (*cp == '>') 446 putchar(' '); 447 } 448 outflags &= ~(MD_nl | MD_br | MD_sp); 449 escflags = ESC_BOL; 450 outcount = 0; 451 452 /* Handle horizontal spacing. */ 453 454 } else if (outflags & MD_spc) { 455 if (outflags & MD_Bk) 456 fputs(" ", stdout); 457 else 458 putchar(' '); 459 escflags &= ~ESC_FON; 460 outcount++; 461 } 462 463 outflags &= ~(MD_spc_force | MD_nonl); 464 if (outflags & MD_Sm) 465 outflags |= MD_spc; 466 else 467 outflags &= ~MD_spc; 468 } 469 470 /* 471 * Print markdown syntax elements. 472 * Can also be used for constant strings when neither escaping 473 * nor delimiter handling is required. 474 */ 475 static void 476 md_rawword(const char *s) 477 { 478 md_preword(); 479 480 if (*s == '\0') 481 return; 482 483 if (escflags & ESC_FON) { 484 escflags &= ~ESC_FON; 485 if (*s == '*' && !code_blocks) 486 fputs("‌", stdout); 487 } 488 489 while (*s != '\0') { 490 switch(*s) { 491 case '*': 492 if (s[1] == '\0') 493 escflags |= ESC_FON; 494 break; 495 case '[': 496 escflags |= ESC_SQU; 497 break; 498 case ']': 499 escflags |= ESC_HYP; 500 escflags &= ~ESC_SQU; 501 break; 502 default: 503 break; 504 } 505 md_char(*s++); 506 } 507 if (s[-1] == ' ') 508 escflags |= ESC_EOL; 509 else 510 escflags &= ~ESC_EOL; 511 } 512 513 /* 514 * Print text and mdoc(7) syntax elements. 515 */ 516 static void 517 md_word(const char *s) 518 { 519 const char *seq, *prevfont, *currfont, *nextfont; 520 char c; 521 int bs, sz, uc, breakline; 522 523 /* No spacing before closing delimiters. */ 524 if (s[0] != '\0' && s[1] == '\0' && 525 strchr("!),.:;?]", s[0]) != NULL && 526 (outflags & MD_spc_force) == 0) 527 outflags &= ~MD_spc; 528 529 md_preword(); 530 531 if (*s == '\0') 532 return; 533 534 /* No spacing after opening delimiters. */ 535 if ((s[0] == '(' || s[0] == '[') && s[1] == '\0') 536 outflags &= ~MD_spc; 537 538 breakline = 0; 539 prevfont = currfont = ""; 540 while ((c = *s++) != '\0') { 541 bs = 0; 542 switch(c) { 543 case ASCII_NBRSP: 544 if (code_blocks) 545 c = ' '; 546 else { 547 md_named("nbsp"); 548 c = '\0'; 549 } 550 break; 551 case ASCII_HYPH: 552 bs = escflags & ESC_BOL && !code_blocks; 553 c = '-'; 554 break; 555 case ASCII_BREAK: 556 continue; 557 case '#': 558 case '+': 559 case '-': 560 bs = escflags & ESC_BOL && !code_blocks; 561 break; 562 case '(': 563 bs = escflags & ESC_HYP && !code_blocks; 564 break; 565 case ')': 566 bs = escflags & ESC_NUM && !code_blocks; 567 break; 568 case '*': 569 case '[': 570 case '_': 571 case '`': 572 bs = !code_blocks; 573 break; 574 case '.': 575 bs = escflags & ESC_NUM && !code_blocks; 576 break; 577 case '<': 578 if (code_blocks == 0) { 579 md_named("lt"); 580 c = '\0'; 581 } 582 break; 583 case '=': 584 if (escflags & ESC_BOL && !code_blocks) { 585 md_named("equals"); 586 c = '\0'; 587 } 588 break; 589 case '>': 590 if (code_blocks == 0) { 591 md_named("gt"); 592 c = '\0'; 593 } 594 break; 595 case '\\': 596 uc = 0; 597 nextfont = NULL; 598 switch (mandoc_escape(&s, &seq, &sz)) { 599 case ESCAPE_UNICODE: 600 uc = mchars_num2uc(seq + 1, sz - 1); 601 break; 602 case ESCAPE_NUMBERED: 603 uc = mchars_num2char(seq, sz); 604 break; 605 case ESCAPE_SPECIAL: 606 uc = mchars_spec2cp(seq, sz); 607 break; 608 case ESCAPE_UNDEF: 609 uc = *seq; 610 break; 611 case ESCAPE_DEVICE: 612 md_rawword("markdown"); 613 continue; 614 case ESCAPE_FONTBOLD: 615 case ESCAPE_FONTCB: 616 nextfont = "**"; 617 break; 618 case ESCAPE_FONTITALIC: 619 case ESCAPE_FONTCI: 620 nextfont = "*"; 621 break; 622 case ESCAPE_FONTBI: 623 nextfont = "***"; 624 break; 625 case ESCAPE_FONT: 626 case ESCAPE_FONTCR: 627 case ESCAPE_FONTROMAN: 628 nextfont = ""; 629 break; 630 case ESCAPE_FONTPREV: 631 nextfont = prevfont; 632 break; 633 case ESCAPE_BREAK: 634 breakline = 1; 635 break; 636 case ESCAPE_NOSPACE: 637 case ESCAPE_SKIPCHAR: 638 case ESCAPE_OVERSTRIKE: 639 /* XXX not implemented */ 640 /* FALLTHROUGH */ 641 case ESCAPE_ERROR: 642 default: 643 break; 644 } 645 if (nextfont != NULL && !code_blocks) { 646 if (*currfont != '\0') { 647 outflags &= ~MD_spc; 648 md_rawword(currfont); 649 } 650 prevfont = currfont; 651 currfont = nextfont; 652 if (*currfont != '\0') { 653 outflags &= ~MD_spc; 654 md_rawword(currfont); 655 } 656 } 657 if (uc) { 658 if ((uc < 0x20 && uc != 0x09) || 659 (uc > 0x7E && uc < 0xA0)) 660 uc = 0xFFFD; 661 if (code_blocks) { 662 seq = mchars_uc2str(uc); 663 fputs(seq, stdout); 664 outcount += strlen(seq); 665 } else { 666 printf("&#%d;", uc); 667 outcount++; 668 } 669 escflags &= ~ESC_FON; 670 } 671 c = '\0'; 672 break; 673 case ']': 674 bs = escflags & ESC_SQU && !code_blocks; 675 escflags |= ESC_HYP; 676 break; 677 default: 678 break; 679 } 680 if (bs) 681 putchar('\\'); 682 md_char(c); 683 if (breakline && 684 (*s == '\0' || *s == ' ' || *s == ASCII_NBRSP)) { 685 printf(" \n"); 686 breakline = 0; 687 while (*s == ' ' || *s == ASCII_NBRSP) 688 s++; 689 } 690 } 691 if (*currfont != '\0') { 692 outflags &= ~MD_spc; 693 md_rawword(currfont); 694 } else if (s[-2] == ' ') 695 escflags |= ESC_EOL; 696 else 697 escflags &= ~ESC_EOL; 698 } 699 700 /* 701 * Print a single HTML named character reference. 702 */ 703 static void 704 md_named(const char *s) 705 { 706 printf("&%s;", s); 707 escflags &= ~(ESC_FON | ESC_EOL); 708 outcount++; 709 } 710 711 /* 712 * Print a single raw character and maintain certain escape flags. 713 */ 714 static void 715 md_char(unsigned char c) 716 { 717 if (c != '\0') { 718 putchar(c); 719 if (c == '*') 720 escflags |= ESC_FON; 721 else 722 escflags &= ~ESC_FON; 723 outcount++; 724 } 725 if (c != ']') 726 escflags &= ~ESC_HYP; 727 if (c == ' ' || c == '\t' || c == '>') 728 return; 729 if (isdigit(c) == 0) 730 escflags &= ~ESC_NUM; 731 else if (escflags & ESC_BOL) 732 escflags |= ESC_NUM; 733 escflags &= ~ESC_BOL; 734 } 735 736 static int 737 md_cond_head(struct roff_node *n) 738 { 739 return n->type == ROFFT_HEAD; 740 } 741 742 static int 743 md_cond_body(struct roff_node *n) 744 { 745 return n->type == ROFFT_BODY; 746 } 747 748 static int 749 md_pre_abort(struct roff_node *n) 750 { 751 abort(); 752 } 753 754 static int 755 md_pre_raw(struct roff_node *n) 756 { 757 const char *prefix; 758 759 if ((prefix = md_act(n->tok)->prefix) != NULL) { 760 md_rawword(prefix); 761 outflags &= ~MD_spc; 762 if (strchr(prefix, '`') != NULL) 763 code_blocks++; 764 } 765 return 1; 766 } 767 768 static void 769 md_post_raw(struct roff_node *n) 770 { 771 const char *suffix; 772 773 if ((suffix = md_act(n->tok)->suffix) != NULL) { 774 outflags &= ~(MD_spc | MD_nl); 775 md_rawword(suffix); 776 if (strchr(suffix, '`') != NULL) 777 code_blocks--; 778 } 779 } 780 781 static int 782 md_pre_word(struct roff_node *n) 783 { 784 const char *prefix; 785 786 if ((prefix = md_act(n->tok)->prefix) != NULL) { 787 md_word(prefix); 788 outflags &= ~MD_spc; 789 } 790 return 1; 791 } 792 793 static void 794 md_post_word(struct roff_node *n) 795 { 796 const char *suffix; 797 798 if ((suffix = md_act(n->tok)->suffix) != NULL) { 799 outflags &= ~(MD_spc | MD_nl); 800 md_word(suffix); 801 } 802 } 803 804 static void 805 md_post_pc(struct roff_node *n) 806 { 807 struct roff_node *nn; 808 809 md_post_raw(n); 810 if (n->parent->tok != MDOC_Rs) 811 return; 812 813 if ((nn = roff_node_next(n)) != NULL) { 814 md_word(","); 815 if (nn->tok == n->tok && 816 (nn = roff_node_prev(n)) != NULL && 817 nn->tok == n->tok) 818 md_word("and"); 819 } else { 820 md_word("."); 821 outflags |= MD_nl; 822 } 823 } 824 825 static int 826 md_pre_skip(struct roff_node *n) 827 { 828 return 0; 829 } 830 831 static void 832 md_pre_syn(struct roff_node *n) 833 { 834 struct roff_node *np; 835 836 if ((n->flags & NODE_SYNPRETTY) == 0 || 837 (np = roff_node_prev(n)) == NULL) 838 return; 839 840 if (np->tok == n->tok && 841 n->tok != MDOC_Ft && 842 n->tok != MDOC_Fo && 843 n->tok != MDOC_Fn) { 844 outflags |= MD_br; 845 return; 846 } 847 848 switch (np->tok) { 849 case MDOC_Fd: 850 case MDOC_Fn: 851 case MDOC_Fo: 852 case MDOC_In: 853 case MDOC_Vt: 854 outflags |= MD_sp; 855 break; 856 case MDOC_Ft: 857 if (n->tok != MDOC_Fn && n->tok != MDOC_Fo) { 858 outflags |= MD_sp; 859 break; 860 } 861 /* FALLTHROUGH */ 862 default: 863 outflags |= MD_br; 864 break; 865 } 866 } 867 868 static int 869 md_pre_An(struct roff_node *n) 870 { 871 switch (n->norm->An.auth) { 872 case AUTH_split: 873 outflags &= ~MD_An_nosplit; 874 outflags |= MD_An_split; 875 return 0; 876 case AUTH_nosplit: 877 outflags &= ~MD_An_split; 878 outflags |= MD_An_nosplit; 879 return 0; 880 default: 881 if (outflags & MD_An_split) 882 outflags |= MD_br; 883 else if (n->sec == SEC_AUTHORS && 884 ! (outflags & MD_An_nosplit)) 885 outflags |= MD_An_split; 886 return 1; 887 } 888 } 889 890 static int 891 md_pre_Ap(struct roff_node *n) 892 { 893 outflags &= ~MD_spc; 894 md_word("'"); 895 outflags &= ~MD_spc; 896 return 0; 897 } 898 899 static int 900 md_pre_Bd(struct roff_node *n) 901 { 902 switch (n->norm->Bd.type) { 903 case DISP_unfilled: 904 case DISP_literal: 905 return md_pre_Dl(n); 906 default: 907 return md_pre_D1(n); 908 } 909 } 910 911 static int 912 md_pre_Bk(struct roff_node *n) 913 { 914 switch (n->type) { 915 case ROFFT_BLOCK: 916 return 1; 917 case ROFFT_BODY: 918 outflags |= MD_Bk; 919 return 1; 920 default: 921 return 0; 922 } 923 } 924 925 static void 926 md_post_Bk(struct roff_node *n) 927 { 928 if (n->type == ROFFT_BODY) 929 outflags &= ~MD_Bk; 930 } 931 932 static int 933 md_pre_Bl(struct roff_node *n) 934 { 935 n->norm->Bl.count = 0; 936 if (n->norm->Bl.type == LIST_column) 937 md_pre_Dl(n); 938 outflags |= MD_sp; 939 return 1; 940 } 941 942 static void 943 md_post_Bl(struct roff_node *n) 944 { 945 n->norm->Bl.count = 0; 946 if (n->norm->Bl.type == LIST_column) 947 md_post_D1(n); 948 outflags |= MD_sp; 949 } 950 951 static int 952 md_pre_D1(struct roff_node *n) 953 { 954 /* 955 * Markdown blockquote syntax does not work inside code blocks. 956 * The best we can do is fall back to another nested code block. 957 */ 958 if (code_blocks) { 959 md_stack('\t'); 960 code_blocks++; 961 } else { 962 md_stack('>'); 963 quote_blocks++; 964 } 965 outflags |= MD_sp; 966 return 1; 967 } 968 969 static void 970 md_post_D1(struct roff_node *n) 971 { 972 md_stack((char)-1); 973 if (code_blocks) 974 code_blocks--; 975 else 976 quote_blocks--; 977 outflags |= MD_sp; 978 } 979 980 static int 981 md_pre_Dl(struct roff_node *n) 982 { 983 /* 984 * Markdown code block syntax does not work inside blockquotes. 985 * The best we can do is fall back to another nested blockquote. 986 */ 987 if (quote_blocks) { 988 md_stack('>'); 989 quote_blocks++; 990 } else { 991 md_stack('\t'); 992 code_blocks++; 993 } 994 outflags |= MD_sp; 995 return 1; 996 } 997 998 static int 999 md_pre_En(struct roff_node *n) 1000 { 1001 if (n->norm->Es == NULL || 1002 n->norm->Es->child == NULL) 1003 return 1; 1004 1005 md_word(n->norm->Es->child->string); 1006 outflags &= ~MD_spc; 1007 return 1; 1008 } 1009 1010 static void 1011 md_post_En(struct roff_node *n) 1012 { 1013 if (n->norm->Es == NULL || 1014 n->norm->Es->child == NULL || 1015 n->norm->Es->child->next == NULL) 1016 return; 1017 1018 outflags &= ~MD_spc; 1019 md_word(n->norm->Es->child->next->string); 1020 } 1021 1022 static int 1023 md_pre_Eo(struct roff_node *n) 1024 { 1025 if (n->end == ENDBODY_NOT && 1026 n->parent->head->child == NULL && 1027 n->child != NULL && 1028 n->child->end != ENDBODY_NOT) 1029 md_preword(); 1030 else if (n->end != ENDBODY_NOT ? n->child != NULL : 1031 n->parent->head->child != NULL && (n->child != NULL || 1032 (n->parent->tail != NULL && n->parent->tail->child != NULL))) 1033 outflags &= ~(MD_spc | MD_nl); 1034 return 1; 1035 } 1036 1037 static void 1038 md_post_Eo(struct roff_node *n) 1039 { 1040 if (n->end != ENDBODY_NOT) { 1041 outflags |= MD_spc; 1042 return; 1043 } 1044 1045 if (n->child == NULL && n->parent->head->child == NULL) 1046 return; 1047 1048 if (n->parent->tail != NULL && n->parent->tail->child != NULL) 1049 outflags &= ~MD_spc; 1050 else 1051 outflags |= MD_spc; 1052 } 1053 1054 static int 1055 md_pre_Fa(struct roff_node *n) 1056 { 1057 int am_Fa; 1058 1059 am_Fa = n->tok == MDOC_Fa; 1060 1061 if (am_Fa) 1062 n = n->child; 1063 1064 while (n != NULL) { 1065 md_rawword("*"); 1066 outflags &= ~MD_spc; 1067 md_node(n); 1068 outflags &= ~MD_spc; 1069 md_rawword("*"); 1070 if ((n = n->next) != NULL) 1071 md_word(","); 1072 } 1073 return 0; 1074 } 1075 1076 static void 1077 md_post_Fa(struct roff_node *n) 1078 { 1079 struct roff_node *nn; 1080 1081 if ((nn = roff_node_next(n)) != NULL && nn->tok == MDOC_Fa) 1082 md_word(","); 1083 } 1084 1085 static int 1086 md_pre_Fd(struct roff_node *n) 1087 { 1088 md_pre_syn(n); 1089 md_pre_raw(n); 1090 return 1; 1091 } 1092 1093 static void 1094 md_post_Fd(struct roff_node *n) 1095 { 1096 md_post_raw(n); 1097 outflags |= MD_br; 1098 } 1099 1100 static void 1101 md_post_Fl(struct roff_node *n) 1102 { 1103 struct roff_node *nn; 1104 1105 md_post_raw(n); 1106 if (n->child == NULL && (nn = roff_node_next(n)) != NULL && 1107 nn->type != ROFFT_TEXT && (nn->flags & NODE_LINE) == 0) 1108 outflags &= ~MD_spc; 1109 } 1110 1111 static int 1112 md_pre_Fn(struct roff_node *n) 1113 { 1114 md_pre_syn(n); 1115 1116 if ((n = n->child) == NULL) 1117 return 0; 1118 1119 md_rawword("**"); 1120 outflags &= ~MD_spc; 1121 md_node(n); 1122 outflags &= ~MD_spc; 1123 md_rawword("**"); 1124 outflags &= ~MD_spc; 1125 md_word("("); 1126 1127 if ((n = n->next) != NULL) 1128 md_pre_Fa(n); 1129 return 0; 1130 } 1131 1132 static void 1133 md_post_Fn(struct roff_node *n) 1134 { 1135 md_word(")"); 1136 if (n->flags & NODE_SYNPRETTY) { 1137 md_word(";"); 1138 outflags |= MD_sp; 1139 } 1140 } 1141 1142 static int 1143 md_pre_Fo(struct roff_node *n) 1144 { 1145 switch (n->type) { 1146 case ROFFT_BLOCK: 1147 md_pre_syn(n); 1148 break; 1149 case ROFFT_HEAD: 1150 if (n->child == NULL) 1151 return 0; 1152 md_pre_raw(n); 1153 break; 1154 case ROFFT_BODY: 1155 outflags &= ~(MD_spc | MD_nl); 1156 md_word("("); 1157 break; 1158 default: 1159 break; 1160 } 1161 return 1; 1162 } 1163 1164 static void 1165 md_post_Fo(struct roff_node *n) 1166 { 1167 switch (n->type) { 1168 case ROFFT_HEAD: 1169 if (n->child != NULL) 1170 md_post_raw(n); 1171 break; 1172 case ROFFT_BODY: 1173 md_post_Fn(n); 1174 break; 1175 default: 1176 break; 1177 } 1178 } 1179 1180 static int 1181 md_pre_In(struct roff_node *n) 1182 { 1183 if (n->flags & NODE_SYNPRETTY) { 1184 md_pre_syn(n); 1185 md_rawword("**"); 1186 outflags &= ~MD_spc; 1187 md_word("#include <"); 1188 } else { 1189 md_word("<"); 1190 outflags &= ~MD_spc; 1191 md_rawword("*"); 1192 } 1193 outflags &= ~MD_spc; 1194 return 1; 1195 } 1196 1197 static void 1198 md_post_In(struct roff_node *n) 1199 { 1200 if (n->flags & NODE_SYNPRETTY) { 1201 outflags &= ~MD_spc; 1202 md_rawword(">**"); 1203 outflags |= MD_nl; 1204 } else { 1205 outflags &= ~MD_spc; 1206 md_rawword("*>"); 1207 } 1208 } 1209 1210 static int 1211 md_pre_It(struct roff_node *n) 1212 { 1213 struct roff_node *bln; 1214 1215 switch (n->type) { 1216 case ROFFT_BLOCK: 1217 return 1; 1218 1219 case ROFFT_HEAD: 1220 bln = n->parent->parent; 1221 if (bln->norm->Bl.comp == 0 && 1222 bln->norm->Bl.type != LIST_column) 1223 outflags |= MD_sp; 1224 outflags |= MD_nl; 1225 1226 switch (bln->norm->Bl.type) { 1227 case LIST_item: 1228 outflags |= MD_br; 1229 return 0; 1230 case LIST_inset: 1231 case LIST_diag: 1232 case LIST_ohang: 1233 outflags |= MD_br; 1234 return 1; 1235 case LIST_tag: 1236 case LIST_hang: 1237 outflags |= MD_sp; 1238 return 1; 1239 case LIST_bullet: 1240 md_rawword("*\t"); 1241 break; 1242 case LIST_dash: 1243 case LIST_hyphen: 1244 md_rawword("-\t"); 1245 break; 1246 case LIST_enum: 1247 md_preword(); 1248 if (bln->norm->Bl.count < 99) 1249 bln->norm->Bl.count++; 1250 printf("%d.\t", bln->norm->Bl.count); 1251 escflags &= ~ESC_FON; 1252 break; 1253 case LIST_column: 1254 outflags |= MD_br; 1255 return 0; 1256 default: 1257 return 0; 1258 } 1259 outflags &= ~MD_spc; 1260 outflags |= MD_nonl; 1261 outcount = 0; 1262 md_stack('\t'); 1263 if (code_blocks || quote_blocks) 1264 list_blocks++; 1265 return 0; 1266 1267 case ROFFT_BODY: 1268 bln = n->parent->parent; 1269 switch (bln->norm->Bl.type) { 1270 case LIST_ohang: 1271 outflags |= MD_br; 1272 break; 1273 case LIST_tag: 1274 case LIST_hang: 1275 md_pre_D1(n); 1276 break; 1277 default: 1278 break; 1279 } 1280 return 1; 1281 1282 default: 1283 return 0; 1284 } 1285 } 1286 1287 static void 1288 md_post_It(struct roff_node *n) 1289 { 1290 struct roff_node *bln; 1291 int i, nc; 1292 1293 if (n->type != ROFFT_BODY) 1294 return; 1295 1296 bln = n->parent->parent; 1297 switch (bln->norm->Bl.type) { 1298 case LIST_bullet: 1299 case LIST_dash: 1300 case LIST_hyphen: 1301 case LIST_enum: 1302 md_stack((char)-1); 1303 if (code_blocks || quote_blocks) 1304 list_blocks--; 1305 break; 1306 case LIST_tag: 1307 case LIST_hang: 1308 md_post_D1(n); 1309 break; 1310 1311 case LIST_column: 1312 if (n->next == NULL) 1313 break; 1314 1315 /* Calculate the array index of the current column. */ 1316 1317 i = 0; 1318 while ((n = n->prev) != NULL && n->type != ROFFT_HEAD) 1319 i++; 1320 1321 /* 1322 * If a width was specified for this column, 1323 * subtract what printed, and 1324 * add the same spacing as in mdoc_term.c. 1325 */ 1326 1327 nc = bln->norm->Bl.ncols; 1328 i = i < nc ? strlen(bln->norm->Bl.cols[i]) - outcount + 1329 (nc < 5 ? 4 : nc == 5 ? 3 : 1) : 1; 1330 if (i < 1) 1331 i = 1; 1332 while (i-- > 0) 1333 putchar(' '); 1334 1335 outflags &= ~MD_spc; 1336 escflags &= ~ESC_FON; 1337 outcount = 0; 1338 break; 1339 1340 default: 1341 break; 1342 } 1343 } 1344 1345 static void 1346 md_post_Lb(struct roff_node *n) 1347 { 1348 if (n->sec == SEC_LIBRARY) 1349 outflags |= MD_br; 1350 } 1351 1352 static void 1353 md_uri(const char *s) 1354 { 1355 while (*s != '\0') { 1356 if (strchr("%()<>", *s) != NULL) { 1357 printf("%%%2.2hhX", *s); 1358 outcount += 3; 1359 } else { 1360 putchar(*s); 1361 outcount++; 1362 } 1363 s++; 1364 } 1365 } 1366 1367 static int 1368 md_pre_Lk(struct roff_node *n) 1369 { 1370 const struct roff_node *link, *descr, *punct; 1371 1372 if ((link = n->child) == NULL) 1373 return 0; 1374 1375 /* Find beginning of trailing punctuation. */ 1376 punct = n->last; 1377 while (punct != link && punct->flags & NODE_DELIMC) 1378 punct = punct->prev; 1379 punct = punct->next; 1380 1381 /* Link text. */ 1382 descr = link->next; 1383 if (descr == punct) 1384 descr = link; /* no text */ 1385 md_rawword("["); 1386 outflags &= ~MD_spc; 1387 do { 1388 md_word(descr->string); 1389 descr = descr->next; 1390 } while (descr != punct); 1391 outflags &= ~MD_spc; 1392 1393 /* Link target. */ 1394 md_rawword("]("); 1395 md_uri(link->string); 1396 outflags &= ~MD_spc; 1397 md_rawword(")"); 1398 1399 /* Trailing punctuation. */ 1400 while (punct != NULL) { 1401 md_word(punct->string); 1402 punct = punct->next; 1403 } 1404 return 0; 1405 } 1406 1407 static int 1408 md_pre_Mt(struct roff_node *n) 1409 { 1410 const struct roff_node *nch; 1411 1412 md_rawword("["); 1413 outflags &= ~MD_spc; 1414 for (nch = n->child; nch != NULL; nch = nch->next) 1415 md_word(nch->string); 1416 outflags &= ~MD_spc; 1417 md_rawword("](mailto:"); 1418 for (nch = n->child; nch != NULL; nch = nch->next) { 1419 md_uri(nch->string); 1420 if (nch->next != NULL) { 1421 putchar(' '); 1422 outcount++; 1423 } 1424 } 1425 outflags &= ~MD_spc; 1426 md_rawword(")"); 1427 return 0; 1428 } 1429 1430 static int 1431 md_pre_Nd(struct roff_node *n) 1432 { 1433 outflags &= ~MD_nl; 1434 outflags |= MD_spc; 1435 md_word("-"); 1436 return 1; 1437 } 1438 1439 static int 1440 md_pre_Nm(struct roff_node *n) 1441 { 1442 switch (n->type) { 1443 case ROFFT_BLOCK: 1444 outflags |= MD_Bk; 1445 md_pre_syn(n); 1446 break; 1447 case ROFFT_HEAD: 1448 case ROFFT_ELEM: 1449 md_pre_raw(n); 1450 break; 1451 default: 1452 break; 1453 } 1454 return 1; 1455 } 1456 1457 static void 1458 md_post_Nm(struct roff_node *n) 1459 { 1460 switch (n->type) { 1461 case ROFFT_BLOCK: 1462 outflags &= ~MD_Bk; 1463 break; 1464 case ROFFT_HEAD: 1465 case ROFFT_ELEM: 1466 md_post_raw(n); 1467 break; 1468 default: 1469 break; 1470 } 1471 } 1472 1473 static int 1474 md_pre_No(struct roff_node *n) 1475 { 1476 outflags |= MD_spc_force; 1477 return 1; 1478 } 1479 1480 static int 1481 md_pre_Ns(struct roff_node *n) 1482 { 1483 outflags &= ~MD_spc; 1484 return 0; 1485 } 1486 1487 static void 1488 md_post_Pf(struct roff_node *n) 1489 { 1490 if (n->next != NULL && (n->next->flags & NODE_LINE) == 0) 1491 outflags &= ~MD_spc; 1492 } 1493 1494 static int 1495 md_pre_Pp(struct roff_node *n) 1496 { 1497 outflags |= MD_sp; 1498 return 0; 1499 } 1500 1501 static int 1502 md_pre_Rs(struct roff_node *n) 1503 { 1504 if (n->sec == SEC_SEE_ALSO) 1505 outflags |= MD_sp; 1506 return 1; 1507 } 1508 1509 static int 1510 md_pre_Sh(struct roff_node *n) 1511 { 1512 switch (n->type) { 1513 case ROFFT_BLOCK: 1514 if (n->sec == SEC_AUTHORS) 1515 outflags &= ~(MD_An_split | MD_An_nosplit); 1516 break; 1517 case ROFFT_HEAD: 1518 outflags |= MD_sp; 1519 md_rawword(n->tok == MDOC_Sh ? "#" : "##"); 1520 break; 1521 case ROFFT_BODY: 1522 outflags |= MD_sp; 1523 break; 1524 default: 1525 break; 1526 } 1527 return 1; 1528 } 1529 1530 static int 1531 md_pre_Sm(struct roff_node *n) 1532 { 1533 if (n->child == NULL) 1534 outflags ^= MD_Sm; 1535 else if (strcmp("on", n->child->string) == 0) 1536 outflags |= MD_Sm; 1537 else 1538 outflags &= ~MD_Sm; 1539 1540 if (outflags & MD_Sm) 1541 outflags |= MD_spc; 1542 1543 return 0; 1544 } 1545 1546 static int 1547 md_pre_Vt(struct roff_node *n) 1548 { 1549 switch (n->type) { 1550 case ROFFT_BLOCK: 1551 md_pre_syn(n); 1552 return 1; 1553 case ROFFT_BODY: 1554 case ROFFT_ELEM: 1555 md_pre_raw(n); 1556 return 1; 1557 default: 1558 return 0; 1559 } 1560 } 1561 1562 static void 1563 md_post_Vt(struct roff_node *n) 1564 { 1565 switch (n->type) { 1566 case ROFFT_BODY: 1567 case ROFFT_ELEM: 1568 md_post_raw(n); 1569 break; 1570 default: 1571 break; 1572 } 1573 } 1574 1575 static int 1576 md_pre_Xr(struct roff_node *n) 1577 { 1578 n = n->child; 1579 if (n == NULL) 1580 return 0; 1581 md_node(n); 1582 n = n->next; 1583 if (n == NULL) 1584 return 0; 1585 outflags &= ~MD_spc; 1586 md_word("("); 1587 md_node(n); 1588 md_word(")"); 1589 return 0; 1590 } 1591 1592 static int 1593 md_pre__R(struct roff_node *n) 1594 { 1595 const unsigned char *cp; 1596 const char *arg; 1597 1598 arg = n->child->string; 1599 1600 if (strncmp(arg, "RFC ", 4) != 0) 1601 return 1; 1602 cp = arg += 4; 1603 while (isdigit(*cp)) 1604 cp++; 1605 if (*cp != '\0') 1606 return 1; 1607 1608 md_rawword("[RFC "); 1609 outflags &= ~MD_spc; 1610 md_rawword(arg); 1611 outflags &= ~MD_spc; 1612 md_rawword("](http://www.rfc-editor.org/rfc/rfc"); 1613 outflags &= ~MD_spc; 1614 md_rawword(arg); 1615 outflags &= ~MD_spc; 1616 md_rawword(".html)"); 1617 return 0; 1618 } 1619 1620 static int 1621 md_pre__T(struct roff_node *n) 1622 { 1623 if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T) 1624 md_word("\""); 1625 else 1626 md_rawword("*"); 1627 outflags &= ~MD_spc; 1628 return 1; 1629 } 1630 1631 static void 1632 md_post__T(struct roff_node *n) 1633 { 1634 outflags &= ~MD_spc; 1635 if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T) 1636 md_word("\""); 1637 else 1638 md_rawword("*"); 1639 md_post_pc(n); 1640 } 1641 1642 static int 1643 md_pre_br(struct roff_node *n) 1644 { 1645 outflags |= MD_br; 1646 return 0; 1647 } 1648