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