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