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