xref: /dragonfly/contrib/mdocml/mdoc_markdown.c (revision 1e4d43f9c96723e4e55543d240f182e1aac9a4c2)
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 *
md_act(enum roff_tok tok)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
markdown_mdoc(void * arg,const struct roff_meta * mdoc)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
md_nodelist(struct roff_node * n)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
md_node(struct roff_node * n)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 *
md_stack(char c)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
md_preword(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
md_rawword(const char * s)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
md_word(const char * s)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
md_named(const char * s)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
md_char(unsigned char c)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
md_cond_head(struct roff_node * n)728 md_cond_head(struct roff_node *n)
729 {
730           return n->type == ROFFT_HEAD;
731 }
732 
733 static int
md_cond_body(struct roff_node * n)734 md_cond_body(struct roff_node *n)
735 {
736           return n->type == ROFFT_BODY;
737 }
738 
739 static int
md_pre_abort(struct roff_node * n)740 md_pre_abort(struct roff_node *n)
741 {
742           abort();
743 }
744 
745 static int
md_pre_raw(struct roff_node * n)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
md_post_raw(struct roff_node * n)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
md_pre_word(struct roff_node * n)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
md_post_word(struct roff_node * n)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
md_post_pc(struct roff_node * n)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
md_pre_skip(struct roff_node * n)817 md_pre_skip(struct roff_node *n)
818 {
819           return 0;
820 }
821 
822 static void
md_pre_syn(struct roff_node * n)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
md_pre_An(struct roff_node * n)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
md_pre_Ap(struct roff_node * n)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
md_pre_Bd(struct roff_node * n)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
md_pre_Bk(struct roff_node * n)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
md_post_Bk(struct roff_node * n)917 md_post_Bk(struct roff_node *n)
918 {
919           if (n->type == ROFFT_BODY)
920                     outflags &= ~MD_Bk;
921 }
922 
923 static int
md_pre_Bl(struct roff_node * n)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
md_post_Bl(struct roff_node * n)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
md_pre_D1(struct roff_node * n)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
md_post_D1(struct roff_node * n)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
md_pre_Dl(struct roff_node * n)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
md_pre_En(struct roff_node * n)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
md_post_En(struct roff_node * n)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
md_pre_Eo(struct roff_node * n)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
md_post_Eo(struct roff_node * n)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
md_pre_Fa(struct roff_node * n)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
md_post_Fa(struct roff_node * n)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
md_pre_Fd(struct roff_node * n)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
md_post_Fd(struct roff_node * n)1085 md_post_Fd(struct roff_node *n)
1086 {
1087           md_post_raw(n);
1088           outflags |= MD_br;
1089 }
1090 
1091 static void
md_post_Fl(struct roff_node * n)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
md_pre_Fn(struct roff_node * n)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
md_post_Fn(struct roff_node * n)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
md_pre_Fo(struct roff_node * n)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
md_post_Fo(struct roff_node * n)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
md_pre_In(struct roff_node * n)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
md_post_In(struct roff_node * n)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
md_pre_It(struct roff_node * n)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
md_post_It(struct roff_node * n)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
md_post_Lb(struct roff_node * n)1337 md_post_Lb(struct roff_node *n)
1338 {
1339           if (n->sec == SEC_LIBRARY)
1340                     outflags |= MD_br;
1341 }
1342 
1343 static void
md_uri(const char * s)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
md_pre_Lk(struct roff_node * n)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
md_pre_Mt(struct roff_node * n)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
md_pre_Nd(struct roff_node * n)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
md_pre_Nm(struct roff_node * n)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
md_post_Nm(struct roff_node * n)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
md_pre_No(struct roff_node * n)1465 md_pre_No(struct roff_node *n)
1466 {
1467           outflags |= MD_spc_force;
1468           return 1;
1469 }
1470 
1471 static int
md_pre_Ns(struct roff_node * n)1472 md_pre_Ns(struct roff_node *n)
1473 {
1474           outflags &= ~MD_spc;
1475           return 0;
1476 }
1477 
1478 static void
md_post_Pf(struct roff_node * n)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
md_pre_Pp(struct roff_node * n)1486 md_pre_Pp(struct roff_node *n)
1487 {
1488           outflags |= MD_sp;
1489           return 0;
1490 }
1491 
1492 static int
md_pre_Rs(struct roff_node * n)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
md_pre_Sh(struct roff_node * n)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
md_pre_Sm(struct roff_node * n)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
md_pre_Vt(struct roff_node * n)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
md_post_Vt(struct roff_node * n)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
md_pre_Xr(struct roff_node * n)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
md_pre__T(struct roff_node * n)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
md_post__T(struct roff_node * n)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
md_pre_br(struct roff_node * n)1606 md_pre_br(struct roff_node *n)
1607 {
1608           outflags |= MD_br;
1609           return 0;
1610 }
1611