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