[BACK]Return to docbook2mdoc.c CVS log [TXT][DIR] Up to [cvsweb.bsd.lv] / docbook2mdoc

File: [cvsweb.bsd.lv] / docbook2mdoc / docbook2mdoc.c (download)

Revision 1.100, Sun Apr 7 19:33:27 2019 UTC (4 years, 11 months ago) by schwarze
Branch: MAIN
Changes since 1.99: +14 -5 lines

handle <appendix>, <article>, <book>, and <legalnotice> similar to <section>

/* $Id: docbook2mdoc.c,v 1.100 2019/04/07 19:33:27 schwarze Exp $ */
/*
 * Copyright (c) 2014 Kristaps Dzonsons <kristaps@bsd.lv>
 * Copyright (c) 2019 Ingo Schwarze <schwarze@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>

#include "node.h"
#include "macro.h"
#include "format.h"

/*
 * The implementation of the mdoc(7) formatter.
 */

static void	 pnode_print(struct format *, struct pnode *);


static void
pnode_printtext(struct format *f, struct pnode *n)
{
	struct pnode	*nn;
	char		*cp;
	char		 last;

	if (n->bsz == 0) {
		assert(n->real < n->b);
		return;
	}

	/*
	 * Text preceding a macro without intervening whitespace
	 * requires a .Pf macro.
	 * Set the spacing flag to avoid a redundant .Ns macro.
	 */

	if (f->linestate != LINE_MACRO &&
	    (nn = TAILQ_NEXT(n, child)) != NULL && nn->spc == 0 &&
	    (nn->node != NODE_TEXT && nn->node != NODE_ESCAPE)) {
		macro_open(f, "Pf");
		nn->spc = 1;
	}

	if (f->linestate == LINE_NEW) {
		last = '\n';
		f->linestate = LINE_TEXT;
	} else {
		last = ' ';
		if (n->spc || f->linestate == LINE_MACRO)
			putchar(' ');
	}

	if (n->node == NODE_ESCAPE) {
		fputs(n->b, stdout);
		return;
	}

	/*
	 * Remove the prefix '-' from <option> elements
	 * because the arguments of .Fl macros do not need it.
	 */

	cp = n->b;
	if (n->parent != NULL && n->parent->node == NODE_OPTION && *cp == '-')
		cp++;

	/*
	 * Print the text, skipping whitespace on new lines,
	 * escaping control characters on new lines,
	 * and escaping backslashes.
	 */

	for (; *cp != '\0'; cp++) {
		if (last == '\n') {
			if (isspace((unsigned char)*cp))
				continue;
			if (*cp == '\'' || *cp == '.')
				fputs("\\&", stdout);
		}
		putchar(last = *cp);
		if (last == '\\')
			putchar('e');
	}
}

static void
pnode_printpara(struct format *p, struct pnode *pn)
{
	struct pnode	*pp;

	if (pn->parent == NULL)
		return;

	if ((pp = TAILQ_PREV(pn, pnodeq, child)) == NULL)
	    pp = pn->parent;

	switch (pp->node) {
	case NODE_ENTRY:
	case NODE_GLOSSTERM:
	case NODE_LISTITEM:
	case NODE_TERM:
		return;
	case NODE_APPENDIX:
	case NODE_LEGALNOTICE:
	case NODE_PREFACE:
	case NODE_SECTION:
		if (p->level < 3)
			return;
		break;
	default:
		break;
	}
	macro_line(p, "Pp");
}

/*
 * If the SYNOPSIS macro has a superfluous title, kill it.
 */
static void
pnode_printrefsynopsisdiv(struct format *p, struct pnode *pn)
{
	struct pnode	*pp, *pq;

	TAILQ_FOREACH_SAFE(pp, &pn->childq, child, pq)
		if (pp->node == NODE_TITLE)
			pnode_unlink(pp);

	macro_line(p, "Sh SYNOPSIS");
}

/*
 * Start a hopefully-named `Sh' section.
 */
static void
pnode_printrefsect(struct format *p, struct pnode *pn)
{
	struct pnode	*pp;
	const char	*title;
	int		 flags, level;

	if (pn->parent == NULL)
		return;

	level = ++p->level;
	flags = ARG_SPACE;
	if (level == 1)
		flags |= ARG_UPPER;
	if (level < 3) {
		switch (pn->node) {
		case NODE_CAUTION:
		case NODE_NOTE:
		case NODE_TIP:
		case NODE_WARNING:
			level = 3;
			break;
		default:
			break;
		}
	}

	TAILQ_FOREACH(pp, &pn->childq, child)
		if (pp->node == NODE_TITLE)
			break;

	if (pp == NULL) {
		switch (pn->node) {
		case NODE_PREFACE:
			title = "Preface";
			break;
		case NODE_APPENDIX:
			title = "Appendix";
			break;
		case NODE_LEGALNOTICE:
			title = "Legal Notice";
			break;
		case NODE_CAUTION:
			title = "Caution";
			break;
		case NODE_NOTE:
			title = "Note";
			break;
		case NODE_TIP:
			title = "Tip";
			break;
		case NODE_WARNING:
			title = "Warning";
			break;
		default:
			title = "Unknown";
			break;
		}
	}

	switch (level) {
	case 1:
		macro_close(p);
		macro_open(p, "Sh");
		break;
	case 2:
		macro_close(p);
		macro_open(p, "Ss");
		break;
	default:
		pnode_printpara(p, pn);
		macro_open(p, "Sy");
		break;
	}

	if (pp != NULL) {
		macro_addnode(p, pp, flags);
		pnode_unlink(pp);
	} else
		macro_addarg(p, title, flags | ARG_QUOTED);
	macro_close(p);
}

/*
 * Start a reference, extracting the title and volume.
 */
static void
pnode_printciterefentry(struct format *p, struct pnode *pn)
{
	struct pnode	*pp, *title, *manvol;

	title = manvol = NULL;
	TAILQ_FOREACH(pp, &pn->childq, child) {
		if (pp->node == NODE_MANVOLNUM)
			manvol = pp;
		else if (pp->node == NODE_REFENTRYTITLE)
			title = pp;
	}
	macro_open(p, "Xr");
	if (title == NULL)
		macro_addarg(p, "unknown", ARG_SPACE);
	else
		macro_addnode(p, title, ARG_SPACE | ARG_SINGLE);
	if (manvol == NULL)
		macro_addarg(p, "1", ARG_SPACE);
	else
		macro_addnode(p, manvol, ARG_SPACE | ARG_SINGLE);
	pnode_unlinksub(pn);
}

static void
pnode_printrefmeta(struct format *p, struct pnode *pn)
{
	struct pnode	*pp, *title, *manvol;

	title = manvol = NULL;
	TAILQ_FOREACH(pp, &pn->childq, child) {
		if (pp->node == NODE_MANVOLNUM)
			manvol = pp;
		else if (pp->node == NODE_REFENTRYTITLE)
			title = pp;
	}
	macro_close(p);
	macro_open(p, "Dt");
	if (title == NULL)
		macro_addarg(p, "UNKNOWN", ARG_SPACE);
	else
		macro_addnode(p, title, ARG_SPACE | ARG_SINGLE | ARG_UPPER);
	if (manvol == NULL)
		macro_addarg(p, "1", ARG_SPACE);
	else
		macro_addnode(p, manvol, ARG_SPACE | ARG_SINGLE);
	macro_close(p);
	pnode_unlink(pn);
}

static void
pnode_printfuncdef(struct format *f, struct pnode *n)
{
	struct pnode	*nc;

	nc = TAILQ_FIRST(&n->childq);
	if (nc != NULL && nc->node == NODE_TEXT) {
		macro_argline(f, "Ft", nc->b);
		pnode_unlink(nc);
	}
	macro_nodeline(f, "Fo", n, ARG_SINGLE);
	pnode_unlinksub(n);
}

/*
 * The <mml:mfenced> node is a little peculiar.
 * First, it can have arbitrary open and closing tokens, which default
 * to parentheses.
 * Second, >1 arguments are separated by commas.
 */
static void
pnode_printmathfenced(struct format *p, struct pnode *pn)
{
	struct pnode	*pp;

	printf("left %s ", pnode_getattr_raw(pn, ATTRKEY_OPEN, "("));

	pp = TAILQ_FIRST(&pn->childq);
	pnode_print(p, pp);

	while ((pp = TAILQ_NEXT(pp, child)) != NULL) {
		putchar(',');
		pnode_print(p, pp);
	}
	printf("right %s ", pnode_getattr_raw(pn, ATTRKEY_CLOSE, ")"));
	pnode_unlinksub(pn);
}

/*
 * These math nodes require special handling because they have infix
 * syntax, instead of the usual prefix or prefix.
 * So we need to break up the first and second child node with a
 * particular eqn(7) word.
 */
static void
pnode_printmath(struct format *p, struct pnode *pn)
{
	struct pnode	*pp;

	pp = TAILQ_FIRST(&pn->childq);
	pnode_print(p, pp);

	switch (pn->node) {
	case NODE_MML_MSUP:
		fputs(" sup ", stdout);
		break;
	case NODE_MML_MFRAC:
		fputs(" over ", stdout);
		break;
	case NODE_MML_MSUB:
		fputs(" sub ", stdout);
		break;
	default:
		break;
	}

	pp = TAILQ_NEXT(pp, child);
	pnode_print(p, pp);
	pnode_unlinksub(pn);
}

static void
pnode_printfuncprototype(struct format *p, struct pnode *pn)
{
	struct pnode	*pp, *fdef;

	TAILQ_FOREACH(fdef, &pn->childq, child)
		if (fdef->node == NODE_FUNCDEF)
			break;

	if (fdef != NULL) {
		pnode_printfuncdef(p, fdef);
		pnode_unlink(fdef);
	} else
		macro_line(p, "Fo UNKNOWN");

	TAILQ_FOREACH(pp, &pn->childq, child)
		macro_nodeline(p, "Fa", pp, ARG_SINGLE);

	macro_line(p, "Fc");
	pnode_unlinksub(pn);
}

/*
 * The <arg> element is more complicated than it should be because text
 * nodes are treated like ".Ar foo", but non-text nodes need to be
 * re-sent into the printer (i.e., without the preceding ".Ar").
 * This also handles the case of "repetition" (or in other words, the
 * ellipsis following an argument) and optionality.
 */
static void
pnode_printarg(struct format *p, struct pnode *pn)
{
	struct pnode	*pp;
	struct pattr	*ap;
	int		 isop, isrep;

	isop = 1;
	isrep = 0;
	TAILQ_FOREACH(ap, &pn->attrq, child) {
		if (ap->key == ATTRKEY_CHOICE &&
		    (ap->val == ATTRVAL_PLAIN || ap->val == ATTRVAL_REQ))
			isop = 0;
		else if (ap->key == ATTRKEY_REP && ap->val == ATTRVAL_REPEAT)
			isrep = 1;
	}
	if (isop)
		macro_open(p, "Op");

	TAILQ_FOREACH(pp, &pn->childq, child) {
		if (pp->node == NODE_TEXT)
			macro_open(p, "Ar");
		pnode_print(p, pp);
		if (isrep && pp->node == NODE_TEXT)
			macro_addarg(p, "...", ARG_SPACE);
	}
	pnode_unlinksub(pn);
}

static void
pnode_printgroup(struct format *p, struct pnode *pn)
{
	struct pnode	*pp, *np;
	struct pattr	*ap;
	int		 isop, sv;

	isop = 1;
	TAILQ_FOREACH(ap, &pn->attrq, child)
		if (ap->key == ATTRKEY_CHOICE &&
		    (ap->val == ATTRVAL_PLAIN || ap->val == ATTRVAL_REQ)) {
			isop = 0;
			break;
		}

	/*
	 * Make sure we're on a macro line.
	 * This will prevent pnode_print() for putting us on a
	 * subsequent line.
	 */
	sv = p->linestate == LINE_NEW;
	if (isop)
		macro_open(p, "Op");
	else if (sv)
		macro_open(p, "No");

	/*
	 * Keep on printing text separated by the vertical bar as long
	 * as we're within the same origin node as the group.
	 * This is kind of a nightmare.
	 * Eh, DocBook...
	 * FIXME: if there's a "Fl", we don't cut off the leading "-"
	 * like we do in pnode_print().
	 */
	TAILQ_FOREACH(pp, &pn->childq, child) {
		pnode_print(p, pp);
		np = TAILQ_NEXT(pp, child);
		while (np != NULL) {
			if (pp->node != np->node)
				break;
			macro_addarg(p, "|", ARG_SPACE);
			macro_addnode(p, np, ARG_SPACE);
			pp = np;
			np = TAILQ_NEXT(np, child);
		}
	}
	if (sv)
		macro_close(p);
	pnode_unlinksub(pn);
}

static void
pnode_printauthor(struct format *f, struct pnode *n)
{
	struct pnode	*nc, *ncn;
	int		 have_contrib, have_name;

	/*
	 * Print <contrib> children up front, before the .An scope,
	 * and figure out whether we a name of a person.
	 */

	have_contrib = have_name = 0;
	TAILQ_FOREACH_SAFE(nc, &n->childq, child, ncn) {
		switch (nc->node) {
		case NODE_CONTRIB:
			if (have_contrib)
				print_text(f, ",", 0);
			print_textnode(f, nc);
			pnode_unlink(nc);
			have_contrib = 1;
			break;
		case NODE_PERSONNAME:
			have_name = 1;
			break;
		default:
			break;
		}
	}
	if (TAILQ_FIRST(&n->childq) == NULL)
		return;

	if (have_contrib)
		print_text(f, ":", 0);

	/*
         * If we have a name, print it in the .An scope and leave
         * all other content for child handlers, to print after the
         * scope.  Otherwise, print everything in the scope.
	 */

	macro_open(f, "An");
	TAILQ_FOREACH_SAFE(nc, &n->childq, child, ncn) {
		if (nc->node == NODE_PERSONNAME || have_name == 0) {
			macro_addnode(f, nc, ARG_SPACE);
			pnode_unlink(nc);
		}
	}

	/*
	 * If there is an email address,
	 * print it on the same macro line.
	 */

	if ((nc = pnode_findfirst(n, NODE_EMAIL)) != NULL) {
		pnode_print(f, nc);
		pnode_unlink(nc);
	}

	/*
	 * If there are still unprinted children, end the scope
	 * with a comma.  Otherwise, leave the scope open in case
	 * a text node follows that starts with closing punctuation.
	 */

	if (TAILQ_FIRST(&n->childq) != NULL) {
		macro_addarg(f, ",", ARG_SPACE);
		macro_close(f);
	}
}

static void
pnode_printlink(struct format *f, struct pnode *n)
{
	const char	*uri, *text;

	uri = pnode_getattr_raw(n, ATTRKEY_LINKEND, NULL);
	if (uri != NULL) {
		if (TAILQ_FIRST(&n->childq) != NULL) {
			print_textnode(f, n);
			text = "";
		} else {
			text = pnode_getattr_raw(n, ATTRKEY_ENDTERM, NULL);
			if (text != NULL)
				print_text(f, text, ARG_SPACE);
		}
		if (text != NULL)
			macro_open(f, "Pq");
		macro_open(f, "Sx");
		macro_addarg(f, uri, ARG_SPACE);
		pnode_unlinksub(n);
		return;
	}
	uri = pnode_getattr_raw(n, ATTRKEY_XLINK_HREF, NULL);
	if (uri == NULL)
		uri = pnode_getattr_raw(n, ATTRKEY_URL, NULL);
	if (uri != NULL) {
		macro_open(f, "Lk");
		macro_addarg(f, uri, ARG_SPACE | ARG_SINGLE);
		if (TAILQ_FIRST(&n->childq) != NULL)
			macro_addnode(f, n, ARG_SPACE | ARG_SINGLE);
		pnode_unlinksub(n);
		return;
	}
}

static void
pnode_printprologue(struct format *p, struct ptree *tree)
{
	struct pnode	*refmeta;

	refmeta = tree->root == NULL ? NULL :
	    pnode_findfirst(tree->root, NODE_REFMETA);

	macro_line(p, "Dd $Mdocdate" "$");
	if (refmeta == NULL) {
		macro_open(p, "Dt");
		macro_addarg(p,
		    pnode_getattr_raw(tree->root, ATTRKEY_ID, "UNKNOWN"),
		    ARG_SPACE | ARG_SINGLE | ARG_UPPER);
		macro_addarg(p, "1", ARG_SPACE);
		macro_close(p);
	} else
		pnode_printrefmeta(p, refmeta);
	macro_line(p, "Os");

	if (tree->flags & TREE_EQN) {
		macro_line(p, "EQ");
		print_text(p, "delim $$", 0);
		macro_line(p, "EN");
	}
}

/*
 * We can have multiple <term> elements within a <varlistentry>, which
 * we should comma-separate as list headers.
 */
static void
pnode_printvarlistentry(struct format *p, struct pnode *pn)
{
	struct pnode	*pp;
	int		 first = 1;

	macro_close(p);
	macro_open(p, "It");
	TAILQ_FOREACH(pp, &pn->childq, child) {
		if (pp->node != NODE_TERM && pp->node != NODE_GLOSSTERM)
			continue;
		if ( ! first)
			macro_addarg(p, ",", 0);
		pnode_print(p, pp);
		first = 0;
	}
	macro_close(p);
	TAILQ_FOREACH(pp, &pn->childq, child)
		if (pp->node != NODE_TERM && pp->node != NODE_GLOSSTERM)
			pnode_print(p, pp);
	pnode_unlinksub(pn);
}

static void
pnode_printtitle(struct format *p, struct pnode *pn)
{
	struct pnode	*pp, *pq;

	TAILQ_FOREACH_SAFE(pp, &pn->childq, child, pq) {
		if (pp->node == NODE_TITLE) {
			pnode_printpara(p, pp);
			pnode_print(p, pp);
			pnode_unlink(pp);
		}
	}
}

static void
pnode_printrow(struct format *p, struct pnode *pn)
{
	struct pnode	*pp;

	macro_line(p, "Bl -dash -compact");
	TAILQ_FOREACH(pp, &pn->childq, child) {
		macro_line(p, "It");
		pnode_print(p, pp);
	}
	macro_line(p, "El");
	pnode_unlink(pn);
}

static void
pnode_printtgroup1(struct format *f, struct pnode *n)
{
	struct pnode	*nc;

	macro_line(f, "Bl -bullet -compact");
	while ((nc = pnode_findfirst(n, NODE_ENTRY)) != NULL) {
		macro_line(f, "It");
		pnode_print(f, nc);
		pnode_unlink(nc);
	}
	macro_line(f, "El");
	pnode_unlinksub(n);
}

static void
pnode_printtgroup2(struct format *f, struct pnode *n)
{
	struct pnode	*nr, *ne;

	macro_line(f, "Bl -tag -width Ds");
	while ((nr = pnode_findfirst(n, NODE_ROW)) != NULL) {
		if ((ne = pnode_findfirst(n, NODE_ENTRY)) == NULL)
			break;
		macro_close(f);
		macro_open(f, "It");
		pnode_print(f, ne);
		macro_close(f);
		pnode_unlink(ne);
		pnode_print(f, nr);
		pnode_unlink(nr);
	}
	macro_line(f, "El");
	pnode_unlinksub(n);
}

static void
pnode_printtgroup(struct format *f, struct pnode *n)
{
	struct pnode	*nc;

	switch (atoi(pnode_getattr_raw(n, ATTRKEY_COLS, "0"))) {
	case 1:
		pnode_printtgroup1(f, n);
		return;
	case 2:
		pnode_printtgroup2(f, n);
		return;
	default:
		break;
	}

	macro_line(f, "Bl -ohang");
	while ((nc = pnode_findfirst(n, NODE_ROW)) != NULL) {
		macro_line(f, "It Table Row");
		pnode_printrow(f, nc);
	}
	macro_line(f, "El");
	pnode_unlinksub(n);
}

static void
pnode_printlist(struct format *p, struct pnode *pn)
{
	struct pnode	*pp;

	pnode_printtitle(p, pn);
	macro_argline(p, "Bl",
	    pn->node == NODE_ORDEREDLIST ? "-enum" : "-bullet");
	TAILQ_FOREACH(pp, &pn->childq, child) {
		macro_line(p, "It");
		pnode_print(p, pp);
	}
	macro_line(p, "El");
	pnode_unlinksub(pn);
}

static void
pnode_printvariablelist(struct format *p, struct pnode *pn)
{
	struct pnode	*pp;

	pnode_printtitle(p, pn);
	macro_line(p, "Bl -tag -width Ds");
	TAILQ_FOREACH(pp, &pn->childq, child) {
		if (pp->node == NODE_VARLISTENTRY)
			pnode_printvarlistentry(p, pp);
		else
			macro_nodeline(p, "It", pp, 0);
	}
	macro_line(p, "El");
	pnode_unlinksub(pn);
}

/*
 * Print a parsed node (or ignore it--whatever).
 * This is a recursive function.
 * FIXME: if we're in a literal context (<screen> or <programlisting> or
 * whatever), don't print inline macros.
 */
static void
pnode_print(struct format *p, struct pnode *pn)
{
	struct pnode	*pp;
	enum linestate	 sv;

	if (pn == NULL)
		return;

	p->spc = pn->spc;
	sv = p->linestate;

	switch (pn->node) {
	case NODE_APPLICATION:
		macro_open(p, "Nm");
		break;
	case NODE_ARG:
		pnode_printarg(p, pn);
		break;
	case NODE_AUTHOR:
		pnode_printauthor(p, pn);
		break;
	case NODE_AUTHORGROUP:
		macro_line(p, "An -split");
		break;
	case NODE_BLOCKQUOTE:
		macro_line(p, "Bd -ragged -offset indent");
		break;
	case NODE_BOOKINFO:
		macro_line(p, "Sh NAME");
		break;
	case NODE_CITEREFENTRY:
		pnode_printciterefentry(p, pn);
		break;
	case NODE_CITETITLE:
		macro_open(p, "%T");
		break;
	case NODE_COMMAND:
		macro_open(p, "Nm");
		break;
	case NODE_CONSTANT:
		macro_open(p, "Dv");
		break;
	case NODE_EDITOR:
		print_text(p, "editor:", ARG_SPACE);
		sv = LINE_TEXT;
		macro_open(p, "An");
		break;
	case NODE_EMAIL:
		macro_open(p, "Aq Mt");
		break;
	case NODE_EMPHASIS:
	case NODE_FIRSTTERM:
	case NODE_GLOSSTERM:
		macro_open(p, "Em");
		break;
	case NODE_ENVAR:
		macro_open(p, "Ev");
		break;
	case NODE_ERRORNAME:
		macro_open(p, "Er");
		break;
	case NODE_FILENAME:
		macro_open(p, "Pa");
		break;
	case NODE_FUNCTION:
		macro_open(p, "Fn");
		break;
	case NODE_FUNCPROTOTYPE:
		pnode_printfuncprototype(p, pn);
		break;
	case NODE_FUNCSYNOPSISINFO:
		macro_open(p, "Fd");
		break;
	case NODE_INFORMALEQUATION:
		macro_line(p, "EQ");
		break;
	case NODE_INLINEEQUATION:
		if (p->linestate == LINE_NEW)
			p->linestate = LINE_TEXT;
		putchar('$');
		break;
	case NODE_ITEMIZEDLIST:
		pnode_printlist(p, pn);
		break;
	case NODE_GROUP:
		pnode_printgroup(p, pn);
		break;
	case NODE_KEYSYM:
		macro_open(p, "Sy");
		break;
	case NODE_LINK:
		pnode_printlink(p, pn);
		break;
	case NODE_LITERAL:
		macro_open(p, "Ql");
		break;
	case NODE_LITERALLAYOUT:
		macro_close(p);
		macro_argline(p, "Bd", pnode_getattr(pn, ATTRKEY_CLASS) ==
		    ATTRVAL_MONOSPACED ? "-literal" : "-unfilled");
		break;
	case NODE_MML_MFENCED:
		pnode_printmathfenced(p, pn);
		break;
	case NODE_MML_MROW:
	case NODE_MML_MI:
	case NODE_MML_MN:
	case NODE_MML_MO:
		if (TAILQ_EMPTY(&pn->childq))
			break;
		fputs(" { ", stdout);
		break;
	case NODE_MML_MFRAC:
	case NODE_MML_MSUB:
	case NODE_MML_MSUP:
		pnode_printmath(p, pn);
		break;
	case NODE_OPTION:
		macro_open(p, "Fl");
		break;
	case NODE_ORDEREDLIST:
		pnode_printlist(p, pn);
		break;
	case NODE_PARA:
		pnode_printpara(p, pn);
		break;
	case NODE_PARAMDEF:
	case NODE_PARAMETER:
		macro_nodeline(p, "Fa", pn, ARG_SINGLE);
		pnode_unlinksub(pn);
		break;
	case NODE_QUOTE:
		macro_open(p, "Qo");
		break;
	case NODE_PROGRAMLISTING:
	case NODE_SCREEN:
	case NODE_SYNOPSIS:
		macro_line(p, "Bd -literal");
		break;
	case NODE_REFENTRYINFO:
		/* Suppress. */
		pnode_unlinksub(pn);
		break;
	case NODE_REFNAME:
		/* Suppress non-text children... */
		macro_open(p, "Nm");
		macro_addnode(p, pn, ARG_SPACE | ARG_SINGLE);
		pnode_unlinksub(pn);
		break;
	case NODE_REFNAMEDIV:
		macro_line(p, "Sh NAME");
		break;
	case NODE_REFPURPOSE:
		macro_open(p, "Nd");
		break;
	case NODE_REFSYNOPSISDIV:
		pnode_printrefsynopsisdiv(p, pn);
		break;
	case NODE_PREFACE:
	case NODE_SECTION:
	case NODE_APPENDIX:
	case NODE_LEGALNOTICE:
	case NODE_NOTE:
	case NODE_TIP:
	case NODE_CAUTION:
	case NODE_WARNING:
		pnode_printrefsect(p, pn);
		break;
	case NODE_REPLACEABLE:
		macro_open(p, "Ar");
		break;
	case NODE_SBR:
		macro_line(p, "br");
		break;
	case NODE_SGMLTAG:
		macro_open(p, "Ic");
		break;
	case NODE_TEXT:
	case NODE_ESCAPE:
		pnode_printtext(p, pn);
		break;
	case NODE_TGROUP:
		pnode_printtgroup(p, pn);
		break;
	case NODE_TITLE:
		if (pn->parent != NULL &&
		    pn->parent->node == NODE_BOOKINFO) {
			macro_open(p, "Nd");
			break;
		}
		pnode_printpara(p, pn);
		macro_nodeline(p, "Sy", pn, 0);
		pnode_unlinksub(pn);
		break;
	case NODE_TYPE:
		macro_open(p, "Vt");
		break;
	case NODE_VARIABLELIST:
		pnode_printvariablelist(p, pn);
		break;
	case NODE_VARNAME:
		macro_open(p, "Va");
		break;
	default:
		break;
	}

	TAILQ_FOREACH(pp, &pn->childq, child)
		pnode_print(p, pp);

	switch (pn->node) {
	case NODE_INFORMALEQUATION:
		macro_line(p, "EN");
		break;
	case NODE_INLINEEQUATION:
		fputs("$ ", stdout);
		p->linestate = sv;
		break;
	case NODE_MEMBER:
		if ((pp = TAILQ_NEXT(pn, child)) != NULL &&
		    pp->node != NODE_MEMBER)
			pp = NULL;
		switch (p->linestate) {
		case LINE_TEXT:
			if (pp != NULL)
				print_text(p, ",", 0);
			break;
		case LINE_MACRO:
			if (pp != NULL)
				macro_addarg(p, ",", ARG_SPACE);
			macro_close(p);
			break;
		case LINE_NEW:
			break;
		}
		break;
	case NODE_MML_MROW:
	case NODE_MML_MI:
	case NODE_MML_MN:
	case NODE_MML_MO:
		if (TAILQ_EMPTY(&pn->childq))
			break;
		fputs(" } ", stdout);
		break;
	case NODE_APPLICATION:
	case NODE_ARG:
	case NODE_AUTHOR:
	case NODE_CITEREFENTRY:
	case NODE_CITETITLE:
	case NODE_COMMAND:
	case NODE_CONSTANT:
	case NODE_EDITOR:
	case NODE_EMAIL:
	case NODE_EMPHASIS:
	case NODE_ENVAR:
	case NODE_ERRORNAME:
	case NODE_FILENAME:
	case NODE_FIRSTTERM:
	case NODE_FUNCTION:
	case NODE_FUNCSYNOPSISINFO:
	case NODE_KEYSYM:
	case NODE_LINK:
	case NODE_LITERAL:
	case NODE_OPTION:
	case NODE_PARAMETER:
	case NODE_REPLACEABLE:
	case NODE_REFPURPOSE:
	case NODE_SGMLTAG:
	case NODE_TYPE:
	case NODE_VARNAME:
		if (sv != LINE_MACRO && p->linestate == LINE_MACRO &&
		    (pn->parent == NULL || pn->parent->node != NODE_MEMBER))
			macro_closepunct(p, pn);
		break;
	case NODE_QUOTE:
		if (sv == LINE_NEW)
			macro_close(p);
		sv = p->linestate;
		macro_open(p, "Qc");
		if (sv == LINE_NEW)
			macro_close(p);
		break;
	case NODE_REFNAME:
		/*
		 * If we're in the NAME macro and we have multiple
		 * <refname> macros in sequence, then print out a
		 * trailing comma before the newline.
		 */
		if (pn->parent != NULL &&
		    pn->parent->node == NODE_REFNAMEDIV &&
		    TAILQ_NEXT(pn, child) != NULL &&
		    TAILQ_NEXT(pn, child)->node == NODE_REFNAME)
			macro_addarg(p, ",", ARG_SPACE);
		if (sv == LINE_NEW)
			macro_close(p);
		break;
	case NODE_PREFACE:
	case NODE_SECTION:
	case NODE_APPENDIX:
	case NODE_LEGALNOTICE:
	case NODE_NOTE:
	case NODE_TIP:
	case NODE_CAUTION:
	case NODE_WARNING:
		p->level--;
		break;
	case NODE_BLOCKQUOTE:
	case NODE_LITERALLAYOUT:
	case NODE_PROGRAMLISTING:
	case NODE_SCREEN:
	case NODE_SYNOPSIS:
		macro_line(p, "Ed");
		break;
	case NODE_TITLE:
		if (pn->parent != NULL &&
		    pn->parent->node == NODE_BOOKINFO)
			macro_line(p, "Sh AUTHORS");
		break;
	default:
		break;
	}
}

void
ptree_print(struct ptree *tree)
{
	struct format	 formatter;

	formatter.level = 0;
	formatter.linestate = LINE_NEW;
	pnode_printprologue(&formatter, tree);
	pnode_print(&formatter, tree->root);
	if (formatter.linestate != LINE_NEW)
		putchar('\n');
}