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