xref: /freebsd/contrib/mandoc/mdoc_markdown.c (revision 80c12959679ab203459dc20eb9ece3a7328b7de5)
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("&nbsp;", 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("&zwnj;", 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