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