/* Id: mdoc_markdown.c,v 1.37 2021/08/10 12:55:03 schwarze Exp  */
/*
* Copyright (c) 2017, 2018, 2020 Ingo Schwarze <[email protected]>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Markdown formatter for mdoc(7) used by mandoc(1).
*/
#include "config.h"

#include <sys/types.h>

#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "mandoc_aux.h"
#include "mandoc.h"
#include "roff.h"
#include "mdoc.h"
#include "main.h"

struct  md_act {
       int             (*cond)(struct roff_node *);
       int             (*pre)(struct roff_node *);
       void            (*post)(struct roff_node *);
       const char       *prefix; /* pre-node string constant */
       const char       *suffix; /* post-node string constant */
};

static  void     md_nodelist(struct roff_node *);
static  void     md_node(struct roff_node *);
static  const char *md_stack(char);
static  void     md_preword(void);
static  void     md_rawword(const char *);
static  void     md_word(const char *);
static  void     md_named(const char *);
static  void     md_char(unsigned char);
static  void     md_uri(const char *);

static  int      md_cond_head(struct roff_node *);
static  int      md_cond_body(struct roff_node *);

static  int      md_pre_abort(struct roff_node *);
static  int      md_pre_raw(struct roff_node *);
static  int      md_pre_word(struct roff_node *);
static  int      md_pre_skip(struct roff_node *);
static  void     md_pre_syn(struct roff_node *);
static  int      md_pre_An(struct roff_node *);
static  int      md_pre_Ap(struct roff_node *);
static  int      md_pre_Bd(struct roff_node *);
static  int      md_pre_Bk(struct roff_node *);
static  int      md_pre_Bl(struct roff_node *);
static  int      md_pre_D1(struct roff_node *);
static  int      md_pre_Dl(struct roff_node *);
static  int      md_pre_En(struct roff_node *);
static  int      md_pre_Eo(struct roff_node *);
static  int      md_pre_Fa(struct roff_node *);
static  int      md_pre_Fd(struct roff_node *);
static  int      md_pre_Fn(struct roff_node *);
static  int      md_pre_Fo(struct roff_node *);
static  int      md_pre_In(struct roff_node *);
static  int      md_pre_It(struct roff_node *);
static  int      md_pre_Lk(struct roff_node *);
static  int      md_pre_Mt(struct roff_node *);
static  int      md_pre_Nd(struct roff_node *);
static  int      md_pre_Nm(struct roff_node *);
static  int      md_pre_No(struct roff_node *);
static  int      md_pre_Ns(struct roff_node *);
static  int      md_pre_Pp(struct roff_node *);
static  int      md_pre_Rs(struct roff_node *);
static  int      md_pre_Sh(struct roff_node *);
static  int      md_pre_Sm(struct roff_node *);
static  int      md_pre_Vt(struct roff_node *);
static  int      md_pre_Xr(struct roff_node *);
static  int      md_pre__T(struct roff_node *);
static  int      md_pre_br(struct roff_node *);

static  void     md_post_raw(struct roff_node *);
static  void     md_post_word(struct roff_node *);
static  void     md_post_pc(struct roff_node *);
static  void     md_post_Bk(struct roff_node *);
static  void     md_post_Bl(struct roff_node *);
static  void     md_post_D1(struct roff_node *);
static  void     md_post_En(struct roff_node *);
static  void     md_post_Eo(struct roff_node *);
static  void     md_post_Fa(struct roff_node *);
static  void     md_post_Fd(struct roff_node *);
static  void     md_post_Fl(struct roff_node *);
static  void     md_post_Fn(struct roff_node *);
static  void     md_post_Fo(struct roff_node *);
static  void     md_post_In(struct roff_node *);
static  void     md_post_It(struct roff_node *);
static  void     md_post_Lb(struct roff_node *);
static  void     md_post_Nm(struct roff_node *);
static  void     md_post_Pf(struct roff_node *);
static  void     md_post_Vt(struct roff_node *);
static  void     md_post__T(struct roff_node *);

static  const struct md_act md_acts[MDOC_MAX - MDOC_Dd] = {
       { NULL, NULL, NULL, NULL, NULL }, /* Dd */
       { NULL, NULL, NULL, NULL, NULL }, /* Dt */
       { NULL, NULL, NULL, NULL, NULL }, /* Os */
       { NULL, md_pre_Sh, NULL, NULL, NULL }, /* Sh */
       { NULL, md_pre_Sh, NULL, NULL, NULL }, /* Ss */
       { NULL, md_pre_Pp, NULL, NULL, NULL }, /* Pp */
       { md_cond_body, md_pre_D1, md_post_D1, NULL, NULL }, /* D1 */
       { md_cond_body, md_pre_Dl, md_post_D1, NULL, NULL }, /* Dl */
       { md_cond_body, md_pre_Bd, md_post_D1, NULL, NULL }, /* Bd */
       { NULL, NULL, NULL, NULL, NULL }, /* Ed */
       { md_cond_body, md_pre_Bl, md_post_Bl, NULL, NULL }, /* Bl */
       { NULL, NULL, NULL, NULL, NULL }, /* El */
       { NULL, md_pre_It, md_post_It, NULL, NULL }, /* It */
       { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Ad */
       { NULL, md_pre_An, NULL, NULL, NULL }, /* An */
       { NULL, md_pre_Ap, NULL, NULL, NULL }, /* Ap */
       { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Ar */
       { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Cd */
       { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Cm */
       { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Dv */
       { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Er */
       { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Ev */
       { NULL, NULL, NULL, NULL, NULL }, /* Ex */
       { NULL, md_pre_Fa, md_post_Fa, NULL, NULL }, /* Fa */
       { NULL, md_pre_Fd, md_post_Fd, "**", "**" }, /* Fd */
       { NULL, md_pre_raw, md_post_Fl, "**-", "**" }, /* Fl */
       { NULL, md_pre_Fn, md_post_Fn, NULL, NULL }, /* Fn */
       { NULL, md_pre_Fd, md_post_raw, "*", "*" }, /* Ft */
       { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Ic */
       { NULL, md_pre_In, md_post_In, NULL, NULL }, /* In */
       { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Li */
       { md_cond_head, md_pre_Nd, NULL, NULL, NULL }, /* Nd */
       { NULL, md_pre_Nm, md_post_Nm, "**", "**" }, /* Nm */
       { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Op */
       { NULL, md_pre_abort, NULL, NULL, NULL }, /* Ot */
       { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Pa */
       { NULL, NULL, NULL, NULL, NULL }, /* Rv */
       { NULL, NULL, NULL, NULL, NULL }, /* St */
       { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Va */
       { NULL, md_pre_Vt, md_post_Vt, "*", "*" }, /* Vt */
       { NULL, md_pre_Xr, NULL, NULL, NULL }, /* Xr */
       { NULL, NULL, md_post_pc, NULL, NULL }, /* %A */
       { NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %B */
       { NULL, NULL, md_post_pc, NULL, NULL }, /* %D */
       { NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %I */
       { NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %J */
       { NULL, NULL, md_post_pc, NULL, NULL }, /* %N */
       { NULL, NULL, md_post_pc, NULL, NULL }, /* %O */
       { NULL, NULL, md_post_pc, NULL, NULL }, /* %P */
       { NULL, NULL, md_post_pc, NULL, NULL }, /* %R */
       { NULL, md_pre__T, md_post__T, NULL, NULL }, /* %T */
       { NULL, NULL, md_post_pc, NULL, NULL }, /* %V */
       { NULL, NULL, NULL, NULL, NULL }, /* Ac */
       { md_cond_body, md_pre_word, md_post_word, "<", ">" }, /* Ao */
       { md_cond_body, md_pre_word, md_post_word, "<", ">" }, /* Aq */
       { NULL, NULL, NULL, NULL, NULL }, /* At */
       { NULL, NULL, NULL, NULL, NULL }, /* Bc */
       { NULL, NULL, NULL, NULL, NULL }, /* Bf XXX not implemented */
       { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Bo */
       { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Bq */
       { NULL, NULL, NULL, NULL, NULL }, /* Bsx */
       { NULL, NULL, NULL, NULL, NULL }, /* Bx */
       { NULL, NULL, NULL, NULL, NULL }, /* Db */
       { NULL, NULL, NULL, NULL, NULL }, /* Dc */
       { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Do */
       { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Dq */
       { NULL, NULL, NULL, NULL, NULL }, /* Ec */
       { NULL, NULL, NULL, NULL, NULL }, /* Ef */
       { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Em */
       { md_cond_body, md_pre_Eo, md_post_Eo, NULL, NULL }, /* Eo */
       { NULL, NULL, NULL, NULL, NULL }, /* Fx */
       { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Ms */
       { NULL, md_pre_No, NULL, NULL, NULL }, /* No */
       { NULL, md_pre_Ns, NULL, NULL, NULL }, /* Ns */
       { NULL, NULL, NULL, NULL, NULL }, /* Nx */
       { NULL, NULL, NULL, NULL, NULL }, /* Ox */
       { NULL, NULL, NULL, NULL, NULL }, /* Pc */
       { NULL, NULL, md_post_Pf, NULL, NULL }, /* Pf */
       { md_cond_body, md_pre_word, md_post_word, "(", ")" }, /* Po */
       { md_cond_body, md_pre_word, md_post_word, "(", ")" }, /* Pq */
       { NULL, NULL, NULL, NULL, NULL }, /* Qc */
       { md_cond_body, md_pre_raw, md_post_raw, "'`", "`'" }, /* Ql */
       { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Qo */
       { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Qq */
       { NULL, NULL, NULL, NULL, NULL }, /* Re */
       { md_cond_body, md_pre_Rs, NULL, NULL, NULL }, /* Rs */
       { NULL, NULL, NULL, NULL, NULL }, /* Sc */
       { md_cond_body, md_pre_word, md_post_word, "'", "'" }, /* So */
       { md_cond_body, md_pre_word, md_post_word, "'", "'" }, /* Sq */
       { NULL, md_pre_Sm, NULL, NULL, NULL }, /* Sm */
       { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Sx */
       { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Sy */
       { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Tn */
       { NULL, NULL, NULL, NULL, NULL }, /* Ux */
       { NULL, NULL, NULL, NULL, NULL }, /* Xc */
       { NULL, NULL, NULL, NULL, NULL }, /* Xo */
       { NULL, md_pre_Fo, md_post_Fo, "**", "**" }, /* Fo */
       { NULL, NULL, NULL, NULL, NULL }, /* Fc */
       { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Oo */
       { NULL, NULL, NULL, NULL, NULL }, /* Oc */
       { NULL, md_pre_Bk, md_post_Bk, NULL, NULL }, /* Bk */
       { NULL, NULL, NULL, NULL, NULL }, /* Ek */
       { NULL, NULL, NULL, NULL, NULL }, /* Bt */
       { NULL, NULL, NULL, NULL, NULL }, /* Hf */
       { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Fr */
       { NULL, NULL, NULL, NULL, NULL }, /* Ud */
       { NULL, NULL, md_post_Lb, NULL, NULL }, /* Lb */
       { NULL, md_pre_abort, NULL, NULL, NULL }, /* Lp */
       { NULL, md_pre_Lk, NULL, NULL, NULL }, /* Lk */
       { NULL, md_pre_Mt, NULL, NULL, NULL }, /* Mt */
       { md_cond_body, md_pre_word, md_post_word, "{", "}" }, /* Brq */
       { md_cond_body, md_pre_word, md_post_word, "{", "}" }, /* Bro */
       { NULL, NULL, NULL, NULL, NULL }, /* Brc */
       { NULL, NULL, md_post_pc, NULL, NULL }, /* %C */
       { NULL, md_pre_skip, NULL, NULL, NULL }, /* Es */
       { md_cond_body, md_pre_En, md_post_En, NULL, NULL }, /* En */
       { NULL, NULL, NULL, NULL, NULL }, /* Dx */
       { NULL, NULL, md_post_pc, NULL, NULL }, /* %Q */
       { NULL, md_pre_Lk, md_post_pc, NULL, NULL }, /* %U */
       { NULL, NULL, NULL, NULL, NULL }, /* Ta */
       { NULL, md_pre_skip, NULL, NULL, NULL }, /* Tg */
};
static const struct md_act *md_act(enum roff_tok);

static  int      outflags;
#define MD_spc           (1 << 0)  /* Blank character before next word. */
#define MD_spc_force     (1 << 1)  /* Even before trailing punctuation. */
#define MD_nonl          (1 << 2)  /* Prevent linebreak in markdown code. */
#define MD_nl            (1 << 3)  /* Break markdown code line. */
#define MD_br            (1 << 4)  /* Insert an output line break. */
#define MD_sp            (1 << 5)  /* Insert a paragraph break. */
#define MD_Sm            (1 << 6)  /* Horizontal spacing mode. */
#define MD_Bk            (1 << 7)  /* Word keep mode. */
#define MD_An_split      (1 << 8)  /* Author mode is "split". */
#define MD_An_nosplit    (1 << 9)  /* Author mode is "nosplit". */

static  int      escflags; /* Escape in generated markdown code: */
#define ESC_BOL  (1 << 0)  /* "#*+-" near the beginning of a line. */
#define ESC_NUM  (1 << 1)  /* "." after a leading number. */
#define ESC_HYP  (1 << 2)  /* "(" immediately after "]". */
#define ESC_SQU  (1 << 4)  /* "]" when "[" is open. */
#define ESC_FON  (1 << 5)  /* "*" immediately after unrelated "*". */
#define ESC_EOL  (1 << 6)  /* " " at the and of a line. */

static  int      code_blocks, quote_blocks, list_blocks;
static  int      outcount;


static const struct md_act *
md_act(enum roff_tok tok)
{
       assert(tok >= MDOC_Dd && tok <= MDOC_MAX);
       return md_acts + (tok - MDOC_Dd);
}

void
markdown_mdoc(void *arg, const struct roff_meta *mdoc)
{
       outflags = MD_Sm;
       md_word(mdoc->title);
       if (mdoc->msec != NULL) {
               outflags &= ~MD_spc;
               md_word("(");
               md_word(mdoc->msec);
               md_word(")");
       }
       md_word("-");
       md_word(mdoc->vol);
       if (mdoc->arch != NULL) {
               md_word("(");
               md_word(mdoc->arch);
               md_word(")");
       }
       outflags |= MD_sp;

       md_nodelist(mdoc->first->child);

       outflags |= MD_sp;
       md_word(mdoc->os);
       md_word("-");
       md_word(mdoc->date);
       putchar('\n');
}

static void
md_nodelist(struct roff_node *n)
{
       while (n != NULL) {
               md_node(n);
               n = n->next;
       }
}

static void
md_node(struct roff_node *n)
{
       const struct md_act     *act;
       int                      cond, process_children;

       if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
               return;

       if (outflags & MD_nonl)
               outflags &= ~(MD_nl | MD_sp);
       else if (outflags & MD_spc &&
            n->flags & NODE_LINE &&
            !roff_node_transparent(n))
               outflags |= MD_nl;

       act = NULL;
       cond = 0;
       process_children = 1;
       n->flags &= ~NODE_ENDED;

       if (n->type == ROFFT_TEXT) {
               if (n->flags & NODE_DELIMC)
                       outflags &= ~(MD_spc | MD_spc_force);
               else if (outflags & MD_Sm)
                       outflags |= MD_spc_force;
               md_word(n->string);
               if (n->flags & NODE_DELIMO)
                       outflags &= ~(MD_spc | MD_spc_force);
               else if (outflags & MD_Sm)
                       outflags |= MD_spc;
       } else if (n->tok < ROFF_MAX) {
               switch (n->tok) {
               case ROFF_br:
                       process_children = md_pre_br(n);
                       break;
               case ROFF_sp:
                       process_children = md_pre_Pp(n);
                       break;
               default:
                       process_children = 0;
                       break;
               }
       } else {
               act = md_act(n->tok);
               cond = act->cond == NULL || (*act->cond)(n);
               if (cond && act->pre != NULL &&
                   (n->end == ENDBODY_NOT || n->child != NULL))
                       process_children = (*act->pre)(n);
       }

       if (process_children && n->child != NULL)
               md_nodelist(n->child);

       if (n->flags & NODE_ENDED)
               return;

       if (cond && act->post != NULL)
               (*act->post)(n);

       if (n->end != ENDBODY_NOT)
               n->body->flags |= NODE_ENDED;
}

static const char *
md_stack(char c)
{
       static char     *stack;
       static size_t    sz;
       static size_t    cur;

       switch (c) {
       case '\0':
               break;
       case (char)-1:
               assert(cur);
               stack[--cur] = '\0';
               break;
       default:
               if (cur + 1 >= sz) {
                       sz += 8;
                       stack = mandoc_realloc(stack, sz);
               }
               stack[cur] = c;
               stack[++cur] = '\0';
               break;
       }
       return stack == NULL ? "" : stack;
}

/*
* Handle vertical and horizontal spacing.
*/
static void
md_preword(void)
{
       const char      *cp;

       /*
        * If a list block is nested inside a code block or a blockquote,
        * blank lines for paragraph breaks no longer work; instead,
        * they terminate the list.  Work around this markdown issue
        * by using mere line breaks instead.
        */

       if (list_blocks && outflags & MD_sp) {
               outflags &= ~MD_sp;
               outflags |= MD_br;
       }

       /*
        * End the old line if requested.
        * Escape whitespace at the end of the markdown line
        * such that it won't look like an output line break.
        */

       if (outflags & MD_sp)
               putchar('\n');
       else if (outflags & MD_br) {
               putchar(' ');
               putchar(' ');
       } else if (outflags & MD_nl && escflags & ESC_EOL)
               md_named("zwnj");

       /* Start a new line if necessary. */

       if (outflags & (MD_nl | MD_br | MD_sp)) {
               putchar('\n');
               for (cp = md_stack('\0'); *cp != '\0'; cp++) {
                       putchar(*cp);
                       if (*cp == '>')
                               putchar(' ');
               }
               outflags &= ~(MD_nl | MD_br | MD_sp);
               escflags = ESC_BOL;
               outcount = 0;

       /* Handle horizontal spacing. */

       } else if (outflags & MD_spc) {
               if (outflags & MD_Bk)
                       fputs("&nbsp;", stdout);
               else
                       putchar(' ');
               escflags &= ~ESC_FON;
               outcount++;
       }

       outflags &= ~(MD_spc_force | MD_nonl);
       if (outflags & MD_Sm)
               outflags |= MD_spc;
       else
               outflags &= ~MD_spc;
}

/*
* Print markdown syntax elements.
* Can also be used for constant strings when neither escaping
* nor delimiter handling is required.
*/
static void
md_rawword(const char *s)
{
       md_preword();

       if (*s == '\0')
               return;

       if (escflags & ESC_FON) {
               escflags &= ~ESC_FON;
               if (*s == '*' && !code_blocks)
                       fputs("&zwnj;", stdout);
       }

       while (*s != '\0') {
               switch(*s) {
               case '*':
                       if (s[1] == '\0')
                               escflags |= ESC_FON;
                       break;
               case '[':
                       escflags |= ESC_SQU;
                       break;
               case ']':
                       escflags |= ESC_HYP;
                       escflags &= ~ESC_SQU;
                       break;
               default:
                       break;
               }
               md_char(*s++);
       }
       if (s[-1] == ' ')
               escflags |= ESC_EOL;
       else
               escflags &= ~ESC_EOL;
}

/*
* Print text and mdoc(7) syntax elements.
*/
static void
md_word(const char *s)
{
       const char      *seq, *prevfont, *currfont, *nextfont;
       char             c;
       int              bs, sz, uc, breakline;

       /* No spacing before closing delimiters. */
       if (s[0] != '\0' && s[1] == '\0' &&
           strchr("!),.:;?]", s[0]) != NULL &&
           (outflags & MD_spc_force) == 0)
               outflags &= ~MD_spc;

       md_preword();

       if (*s == '\0')
               return;

       /* No spacing after opening delimiters. */
       if ((s[0] == '(' || s[0] == '[') && s[1] == '\0')
               outflags &= ~MD_spc;

       breakline = 0;
       prevfont = currfont = "";
       while ((c = *s++) != '\0') {
               bs = 0;
               switch(c) {
               case ASCII_NBRSP:
                       if (code_blocks)
                               c = ' ';
                       else {
                               md_named("nbsp");
                               c = '\0';
                       }
                       break;
               case ASCII_HYPH:
                       bs = escflags & ESC_BOL && !code_blocks;
                       c = '-';
                       break;
               case ASCII_BREAK:
                       continue;
               case '#':
               case '+':
               case '-':
                       bs = escflags & ESC_BOL && !code_blocks;
                       break;
               case '(':
                       bs = escflags & ESC_HYP && !code_blocks;
                       break;
               case ')':
                       bs = escflags & ESC_NUM && !code_blocks;
                       break;
               case '*':
               case '[':
               case '_':
               case '`':
                       bs = !code_blocks;
                       break;
               case '.':
                       bs = escflags & ESC_NUM && !code_blocks;
                       break;
               case '<':
                       if (code_blocks == 0) {
                               md_named("lt");
                               c = '\0';
                       }
                       break;
               case '=':
                       if (escflags & ESC_BOL && !code_blocks) {
                               md_named("equals");
                               c = '\0';
                       }
                       break;
               case '>':
                       if (code_blocks == 0) {
                               md_named("gt");
                               c = '\0';
                       }
                       break;
               case '\\':
                       uc = 0;
                       nextfont = NULL;
                       switch (mandoc_escape(&s, &seq, &sz)) {
                       case ESCAPE_UNICODE:
                               uc = mchars_num2uc(seq + 1, sz - 1);
                               break;
                       case ESCAPE_NUMBERED:
                               uc = mchars_num2char(seq, sz);
                               break;
                       case ESCAPE_SPECIAL:
                               uc = mchars_spec2cp(seq, sz);
                               break;
                       case ESCAPE_UNDEF:
                               uc = *seq;
                               break;
                       case ESCAPE_DEVICE:
                               md_rawword("markdown");
                               continue;
                       case ESCAPE_FONTBOLD:
                       case ESCAPE_FONTCB:
                               nextfont = "**";
                               break;
                       case ESCAPE_FONTITALIC:
                       case ESCAPE_FONTCI:
                               nextfont = "*";
                               break;
                       case ESCAPE_FONTBI:
                               nextfont = "***";
                               break;
                       case ESCAPE_FONT:
                       case ESCAPE_FONTCR:
                       case ESCAPE_FONTROMAN:
                               nextfont = "";
                               break;
                       case ESCAPE_FONTPREV:
                               nextfont = prevfont;
                               break;
                       case ESCAPE_BREAK:
                               breakline = 1;
                               break;
                       case ESCAPE_NOSPACE:
                       case ESCAPE_SKIPCHAR:
                       case ESCAPE_OVERSTRIKE:
                               /* XXX not implemented */
                               /* FALLTHROUGH */
                       case ESCAPE_ERROR:
                       default:
                               break;
                       }
                       if (nextfont != NULL && !code_blocks) {
                               if (*currfont != '\0') {
                                       outflags &= ~MD_spc;
                                       md_rawword(currfont);
                               }
                               prevfont = currfont;
                               currfont = nextfont;
                               if (*currfont != '\0') {
                                       outflags &= ~MD_spc;
                                       md_rawword(currfont);
                               }
                       }
                       if (uc) {
                               if ((uc < 0x20 && uc != 0x09) ||
                                   (uc > 0x7E && uc < 0xA0))
                                       uc = 0xFFFD;
                               if (code_blocks) {
                                       seq = mchars_uc2str(uc);
                                       fputs(seq, stdout);
                                       outcount += strlen(seq);
                               } else {
                                       printf("&#%d;", uc);
                                       outcount++;
                               }
                               escflags &= ~ESC_FON;
                       }
                       c = '\0';
                       break;
               case ']':
                       bs = escflags & ESC_SQU && !code_blocks;
                       escflags |= ESC_HYP;
                       break;
               default:
                       break;
               }
               if (bs)
                       putchar('\\');
               md_char(c);
               if (breakline &&
                   (*s == '\0' || *s == ' ' || *s == ASCII_NBRSP)) {
                       printf("  \n");
                       breakline = 0;
                       while (*s == ' ' || *s == ASCII_NBRSP)
                               s++;
               }
       }
       if (*currfont != '\0') {
               outflags &= ~MD_spc;
               md_rawword(currfont);
       } else if (s[-2] == ' ')
               escflags |= ESC_EOL;
       else
               escflags &= ~ESC_EOL;
}

/*
* Print a single HTML named character reference.
*/
static void
md_named(const char *s)
{
       printf("&%s;", s);
       escflags &= ~(ESC_FON | ESC_EOL);
       outcount++;
}

/*
* Print a single raw character and maintain certain escape flags.
*/
static void
md_char(unsigned char c)
{
       if (c != '\0') {
               putchar(c);
               if (c == '*')
                       escflags |= ESC_FON;
               else
                       escflags &= ~ESC_FON;
               outcount++;
       }
       if (c != ']')
               escflags &= ~ESC_HYP;
       if (c == ' ' || c == '\t' || c == '>')
               return;
       if (isdigit(c) == 0)
               escflags &= ~ESC_NUM;
       else if (escflags & ESC_BOL)
               escflags |= ESC_NUM;
       escflags &= ~ESC_BOL;
}

static int
md_cond_head(struct roff_node *n)
{
       return n->type == ROFFT_HEAD;
}

static int
md_cond_body(struct roff_node *n)
{
       return n->type == ROFFT_BODY;
}

static int
md_pre_abort(struct roff_node *n)
{
       abort();
}

static int
md_pre_raw(struct roff_node *n)
{
       const char      *prefix;

       if ((prefix = md_act(n->tok)->prefix) != NULL) {
               md_rawword(prefix);
               outflags &= ~MD_spc;
               if (*prefix == '`')
                       code_blocks++;
       }
       return 1;
}

static void
md_post_raw(struct roff_node *n)
{
       const char      *suffix;

       if ((suffix = md_act(n->tok)->suffix) != NULL) {
               outflags &= ~(MD_spc | MD_nl);
               md_rawword(suffix);
               if (*suffix == '`')
                       code_blocks--;
       }
}

static int
md_pre_word(struct roff_node *n)
{
       const char      *prefix;

       if ((prefix = md_act(n->tok)->prefix) != NULL) {
               md_word(prefix);
               outflags &= ~MD_spc;
       }
       return 1;
}

static void
md_post_word(struct roff_node *n)
{
       const char      *suffix;

       if ((suffix = md_act(n->tok)->suffix) != NULL) {
               outflags &= ~(MD_spc | MD_nl);
               md_word(suffix);
       }
}

static void
md_post_pc(struct roff_node *n)
{
       struct roff_node *nn;

       md_post_raw(n);
       if (n->parent->tok != MDOC_Rs)
               return;

       if ((nn = roff_node_next(n)) != NULL) {
               md_word(",");
               if (nn->tok == n->tok &&
                   (nn = roff_node_prev(n)) != NULL &&
                   nn->tok == n->tok)
                       md_word("and");
       } else {
               md_word(".");
               outflags |= MD_nl;
       }
}

static int
md_pre_skip(struct roff_node *n)
{
       return 0;
}

static void
md_pre_syn(struct roff_node *n)
{
       struct roff_node *np;

       if ((n->flags & NODE_SYNPRETTY) == 0 ||
           (np = roff_node_prev(n)) == NULL)
               return;

       if (np->tok == n->tok &&
           n->tok != MDOC_Ft &&
           n->tok != MDOC_Fo &&
           n->tok != MDOC_Fn) {
               outflags |= MD_br;
               return;
       }

       switch (np->tok) {
       case MDOC_Fd:
       case MDOC_Fn:
       case MDOC_Fo:
       case MDOC_In:
       case MDOC_Vt:
               outflags |= MD_sp;
               break;
       case MDOC_Ft:
               if (n->tok != MDOC_Fn && n->tok != MDOC_Fo) {
                       outflags |= MD_sp;
                       break;
               }
               /* FALLTHROUGH */
       default:
               outflags |= MD_br;
               break;
       }
}

static int
md_pre_An(struct roff_node *n)
{
       switch (n->norm->An.auth) {
       case AUTH_split:
               outflags &= ~MD_An_nosplit;
               outflags |= MD_An_split;
               return 0;
       case AUTH_nosplit:
               outflags &= ~MD_An_split;
               outflags |= MD_An_nosplit;
               return 0;
       default:
               if (outflags & MD_An_split)
                       outflags |= MD_br;
               else if (n->sec == SEC_AUTHORS &&
                   ! (outflags & MD_An_nosplit))
                       outflags |= MD_An_split;
               return 1;
       }
}

static int
md_pre_Ap(struct roff_node *n)
{
       outflags &= ~MD_spc;
       md_word("'");
       outflags &= ~MD_spc;
       return 0;
}

static int
md_pre_Bd(struct roff_node *n)
{
       switch (n->norm->Bd.type) {
       case DISP_unfilled:
       case DISP_literal:
               return md_pre_Dl(n);
       default:
               return md_pre_D1(n);
       }
}

static int
md_pre_Bk(struct roff_node *n)
{
       switch (n->type) {
       case ROFFT_BLOCK:
               return 1;
       case ROFFT_BODY:
               outflags |= MD_Bk;
               return 1;
       default:
               return 0;
       }
}

static void
md_post_Bk(struct roff_node *n)
{
       if (n->type == ROFFT_BODY)
               outflags &= ~MD_Bk;
}

static int
md_pre_Bl(struct roff_node *n)
{
       n->norm->Bl.count = 0;
       if (n->norm->Bl.type == LIST_column)
               md_pre_Dl(n);
       outflags |= MD_sp;
       return 1;
}

static void
md_post_Bl(struct roff_node *n)
{
       n->norm->Bl.count = 0;
       if (n->norm->Bl.type == LIST_column)
               md_post_D1(n);
       outflags |= MD_sp;
}

static int
md_pre_D1(struct roff_node *n)
{
       /*
        * Markdown blockquote syntax does not work inside code blocks.
        * The best we can do is fall back to another nested code block.
        */
       if (code_blocks) {
               md_stack('\t');
               code_blocks++;
       } else {
               md_stack('>');
               quote_blocks++;
       }
       outflags |= MD_sp;
       return 1;
}

static void
md_post_D1(struct roff_node *n)
{
       md_stack((char)-1);
       if (code_blocks)
               code_blocks--;
       else
               quote_blocks--;
       outflags |= MD_sp;
}

static int
md_pre_Dl(struct roff_node *n)
{
       /*
        * Markdown code block syntax does not work inside blockquotes.
        * The best we can do is fall back to another nested blockquote.
        */
       if (quote_blocks) {
               md_stack('>');
               quote_blocks++;
       } else {
               md_stack('\t');
               code_blocks++;
       }
       outflags |= MD_sp;
       return 1;
}

static int
md_pre_En(struct roff_node *n)
{
       if (n->norm->Es == NULL ||
           n->norm->Es->child == NULL)
               return 1;

       md_word(n->norm->Es->child->string);
       outflags &= ~MD_spc;
       return 1;
}

static void
md_post_En(struct roff_node *n)
{
       if (n->norm->Es == NULL ||
           n->norm->Es->child == NULL ||
           n->norm->Es->child->next == NULL)
               return;

       outflags &= ~MD_spc;
       md_word(n->norm->Es->child->next->string);
}

static int
md_pre_Eo(struct roff_node *n)
{
       if (n->end == ENDBODY_NOT &&
           n->parent->head->child == NULL &&
           n->child != NULL &&
           n->child->end != ENDBODY_NOT)
               md_preword();
       else if (n->end != ENDBODY_NOT ? n->child != NULL :
           n->parent->head->child != NULL && (n->child != NULL ||
           (n->parent->tail != NULL && n->parent->tail->child != NULL)))
               outflags &= ~(MD_spc | MD_nl);
       return 1;
}

static void
md_post_Eo(struct roff_node *n)
{
       if (n->end != ENDBODY_NOT) {
               outflags |= MD_spc;
               return;
       }

       if (n->child == NULL && n->parent->head->child == NULL)
               return;

       if (n->parent->tail != NULL && n->parent->tail->child != NULL)
               outflags &= ~MD_spc;
       else
               outflags |= MD_spc;
}

static int
md_pre_Fa(struct roff_node *n)
{
       int      am_Fa;

       am_Fa = n->tok == MDOC_Fa;

       if (am_Fa)
               n = n->child;

       while (n != NULL) {
               md_rawword("*");
               outflags &= ~MD_spc;
               md_node(n);
               outflags &= ~MD_spc;
               md_rawword("*");
               if ((n = n->next) != NULL)
                       md_word(",");
       }
       return 0;
}

static void
md_post_Fa(struct roff_node *n)
{
       struct roff_node *nn;

       if ((nn = roff_node_next(n)) != NULL && nn->tok == MDOC_Fa)
               md_word(",");
}

static int
md_pre_Fd(struct roff_node *n)
{
       md_pre_syn(n);
       md_pre_raw(n);
       return 1;
}

static void
md_post_Fd(struct roff_node *n)
{
       md_post_raw(n);
       outflags |= MD_br;
}

static void
md_post_Fl(struct roff_node *n)
{
       struct roff_node *nn;

       md_post_raw(n);
       if (n->child == NULL && (nn = roff_node_next(n)) != NULL &&
           nn->type != ROFFT_TEXT && (nn->flags & NODE_LINE) == 0)
               outflags &= ~MD_spc;
}

static int
md_pre_Fn(struct roff_node *n)
{
       md_pre_syn(n);

       if ((n = n->child) == NULL)
               return 0;

       md_rawword("**");
       outflags &= ~MD_spc;
       md_node(n);
       outflags &= ~MD_spc;
       md_rawword("**");
       outflags &= ~MD_spc;
       md_word("(");

       if ((n = n->next) != NULL)
               md_pre_Fa(n);
       return 0;
}

static void
md_post_Fn(struct roff_node *n)
{
       md_word(")");
       if (n->flags & NODE_SYNPRETTY) {
               md_word(";");
               outflags |= MD_sp;
       }
}

static int
md_pre_Fo(struct roff_node *n)
{
       switch (n->type) {
       case ROFFT_BLOCK:
               md_pre_syn(n);
               break;
       case ROFFT_HEAD:
               if (n->child == NULL)
                       return 0;
               md_pre_raw(n);
               break;
       case ROFFT_BODY:
               outflags &= ~(MD_spc | MD_nl);
               md_word("(");
               break;
       default:
               break;
       }
       return 1;
}

static void
md_post_Fo(struct roff_node *n)
{
       switch (n->type) {
       case ROFFT_HEAD:
               if (n->child != NULL)
                       md_post_raw(n);
               break;
       case ROFFT_BODY:
               md_post_Fn(n);
               break;
       default:
               break;
       }
}

static int
md_pre_In(struct roff_node *n)
{
       if (n->flags & NODE_SYNPRETTY) {
               md_pre_syn(n);
               md_rawword("**");
               outflags &= ~MD_spc;
               md_word("#include <");
       } else {
               md_word("<");
               outflags &= ~MD_spc;
               md_rawword("*");
       }
       outflags &= ~MD_spc;
       return 1;
}

static void
md_post_In(struct roff_node *n)
{
       if (n->flags & NODE_SYNPRETTY) {
               outflags &= ~MD_spc;
               md_rawword(">**");
               outflags |= MD_nl;
       } else {
               outflags &= ~MD_spc;
               md_rawword("*>");
       }
}

static int
md_pre_It(struct roff_node *n)
{
       struct roff_node        *bln;

       switch (n->type) {
       case ROFFT_BLOCK:
               return 1;

       case ROFFT_HEAD:
               bln = n->parent->parent;
               if (bln->norm->Bl.comp == 0 &&
                   bln->norm->Bl.type != LIST_column)
                       outflags |= MD_sp;
               outflags |= MD_nl;

               switch (bln->norm->Bl.type) {
               case LIST_item:
                       outflags |= MD_br;
                       return 0;
               case LIST_inset:
               case LIST_diag:
               case LIST_ohang:
                       outflags |= MD_br;
                       return 1;
               case LIST_tag:
               case LIST_hang:
                       outflags |= MD_sp;
                       return 1;
               case LIST_bullet:
                       md_rawword("*\t");
                       break;
               case LIST_dash:
               case LIST_hyphen:
                       md_rawword("-\t");
                       break;
               case LIST_enum:
                       md_preword();
                       if (bln->norm->Bl.count < 99)
                               bln->norm->Bl.count++;
                       printf("%d.\t", bln->norm->Bl.count);
                       escflags &= ~ESC_FON;
                       break;
               case LIST_column:
                       outflags |= MD_br;
                       return 0;
               default:
                       return 0;
               }
               outflags &= ~MD_spc;
               outflags |= MD_nonl;
               outcount = 0;
               md_stack('\t');
               if (code_blocks || quote_blocks)
                       list_blocks++;
               return 0;

       case ROFFT_BODY:
               bln = n->parent->parent;
               switch (bln->norm->Bl.type) {
               case LIST_ohang:
                       outflags |= MD_br;
                       break;
               case LIST_tag:
               case LIST_hang:
                       md_pre_D1(n);
                       break;
               default:
                       break;
               }
               return 1;

       default:
               return 0;
       }
}

static void
md_post_It(struct roff_node *n)
{
       struct roff_node        *bln;
       int                      i, nc;

       if (n->type != ROFFT_BODY)
               return;

       bln = n->parent->parent;
       switch (bln->norm->Bl.type) {
       case LIST_bullet:
       case LIST_dash:
       case LIST_hyphen:
       case LIST_enum:
               md_stack((char)-1);
               if (code_blocks || quote_blocks)
                       list_blocks--;
               break;
       case LIST_tag:
       case LIST_hang:
               md_post_D1(n);
               break;

       case LIST_column:
               if (n->next == NULL)
                       break;

               /* Calculate the array index of the current column. */

               i = 0;
               while ((n = n->prev) != NULL && n->type != ROFFT_HEAD)
                       i++;

               /*
                * If a width was specified for this column,
                * subtract what printed, and
                * add the same spacing as in mdoc_term.c.
                */

               nc = bln->norm->Bl.ncols;
               i = i < nc ? strlen(bln->norm->Bl.cols[i]) - outcount +
                   (nc < 5 ? 4 : nc == 5 ? 3 : 1) : 1;
               if (i < 1)
                       i = 1;
               while (i-- > 0)
                       putchar(' ');

               outflags &= ~MD_spc;
               escflags &= ~ESC_FON;
               outcount = 0;
               break;

       default:
               break;
       }
}

static void
md_post_Lb(struct roff_node *n)
{
       if (n->sec == SEC_LIBRARY)
               outflags |= MD_br;
}

static void
md_uri(const char *s)
{
       while (*s != '\0') {
               if (strchr("%()<>", *s) != NULL) {
                       printf("%%%2.2hhX", *s);
                       outcount += 3;
               } else {
                       putchar(*s);
                       outcount++;
               }
               s++;
       }
}

static int
md_pre_Lk(struct roff_node *n)
{
       const struct roff_node *link, *descr, *punct;

       if ((link = n->child) == NULL)
               return 0;

       /* Find beginning of trailing punctuation. */
       punct = n->last;
       while (punct != link && punct->flags & NODE_DELIMC)
               punct = punct->prev;
       punct = punct->next;

       /* Link text. */
       descr = link->next;
       if (descr == punct)
               descr = link;  /* no text */
       md_rawword("[");
       outflags &= ~MD_spc;
       do {
               md_word(descr->string);
               descr = descr->next;
       } while (descr != punct);
       outflags &= ~MD_spc;

       /* Link target. */
       md_rawword("](");
       md_uri(link->string);
       outflags &= ~MD_spc;
       md_rawword(")");

       /* Trailing punctuation. */
       while (punct != NULL) {
               md_word(punct->string);
               punct = punct->next;
       }
       return 0;
}

static int
md_pre_Mt(struct roff_node *n)
{
       const struct roff_node *nch;

       md_rawword("[");
       outflags &= ~MD_spc;
       for (nch = n->child; nch != NULL; nch = nch->next)
               md_word(nch->string);
       outflags &= ~MD_spc;
       md_rawword("](mailto:");
       for (nch = n->child; nch != NULL; nch = nch->next) {
               md_uri(nch->string);
               if (nch->next != NULL) {
                       putchar(' ');
                       outcount++;
               }
       }
       outflags &= ~MD_spc;
       md_rawword(")");
       return 0;
}

static int
md_pre_Nd(struct roff_node *n)
{
       outflags &= ~MD_nl;
       outflags |= MD_spc;
       md_word("-");
       return 1;
}

static int
md_pre_Nm(struct roff_node *n)
{
       switch (n->type) {
       case ROFFT_BLOCK:
               outflags |= MD_Bk;
               md_pre_syn(n);
               break;
       case ROFFT_HEAD:
       case ROFFT_ELEM:
               md_pre_raw(n);
               break;
       default:
               break;
       }
       return 1;
}

static void
md_post_Nm(struct roff_node *n)
{
       switch (n->type) {
       case ROFFT_BLOCK:
               outflags &= ~MD_Bk;
               break;
       case ROFFT_HEAD:
       case ROFFT_ELEM:
               md_post_raw(n);
               break;
       default:
               break;
       }
}

static int
md_pre_No(struct roff_node *n)
{
       outflags |= MD_spc_force;
       return 1;
}

static int
md_pre_Ns(struct roff_node *n)
{
       outflags &= ~MD_spc;
       return 0;
}

static void
md_post_Pf(struct roff_node *n)
{
       if (n->next != NULL && (n->next->flags & NODE_LINE) == 0)
               outflags &= ~MD_spc;
}

static int
md_pre_Pp(struct roff_node *n)
{
       outflags |= MD_sp;
       return 0;
}

static int
md_pre_Rs(struct roff_node *n)
{
       if (n->sec == SEC_SEE_ALSO)
               outflags |= MD_sp;
       return 1;
}

static int
md_pre_Sh(struct roff_node *n)
{
       switch (n->type) {
       case ROFFT_BLOCK:
               if (n->sec == SEC_AUTHORS)
                       outflags &= ~(MD_An_split | MD_An_nosplit);
               break;
       case ROFFT_HEAD:
               outflags |= MD_sp;
               md_rawword(n->tok == MDOC_Sh ? "#" : "##");
               break;
       case ROFFT_BODY:
               outflags |= MD_sp;
               break;
       default:
               break;
       }
       return 1;
}

static int
md_pre_Sm(struct roff_node *n)
{
       if (n->child == NULL)
               outflags ^= MD_Sm;
       else if (strcmp("on", n->child->string) == 0)
               outflags |= MD_Sm;
       else
               outflags &= ~MD_Sm;

       if (outflags & MD_Sm)
               outflags |= MD_spc;

       return 0;
}

static int
md_pre_Vt(struct roff_node *n)
{
       switch (n->type) {
       case ROFFT_BLOCK:
               md_pre_syn(n);
               return 1;
       case ROFFT_BODY:
       case ROFFT_ELEM:
               md_pre_raw(n);
               return 1;
       default:
               return 0;
       }
}

static void
md_post_Vt(struct roff_node *n)
{
       switch (n->type) {
       case ROFFT_BODY:
       case ROFFT_ELEM:
               md_post_raw(n);
               break;
       default:
               break;
       }
}

static int
md_pre_Xr(struct roff_node *n)
{
       n = n->child;
       if (n == NULL)
               return 0;
       md_node(n);
       n = n->next;
       if (n == NULL)
               return 0;
       outflags &= ~MD_spc;
       md_word("(");
       md_node(n);
       md_word(")");
       return 0;
}

static int
md_pre__T(struct roff_node *n)
{
       if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T)
               md_word("\"");
       else
               md_rawword("*");
       outflags &= ~MD_spc;
       return 1;
}

static void
md_post__T(struct roff_node *n)
{
       outflags &= ~MD_spc;
       if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T)
               md_word("\"");
       else
               md_rawword("*");
       md_post_pc(n);
}

static int
md_pre_br(struct roff_node *n)
{
       outflags |= MD_br;
       return 0;
}