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