xref: /openbsd/usr.bin/mandoc/mdoc_markdown.c (revision bcf2ba68)
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("&nbsp;", 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("&zwnj;", 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