[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.114, Fri Apr 12 19:14:50 2019 UTC (4 years, 11 months ago) by schwarze
Branch: MAIN
Changes since 1.113: +2 -2 lines

Implement lint and tree dump output modes.
Thanks to the previously committed node property infrastructure
in node.c, this needs only 110 lines of code (including the license
and the documentation).

/* $Id: docbook2mdoc.c,v 1.114 2019/04/12 19:14:50 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 <string.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;
	int		 accept_arg;

	cp = n->b;
	accept_arg = f->flags & FMT_ARG;
	if (f->linestate == LINE_MACRO && !n->spc && !accept_arg) {
		for (;;) {
			if (*cp == '\0')
				return;
			if (strchr("!),.:;?]", *cp) == NULL)
				break;
			printf(" %c", *cp++);
		}
		if (isspace((unsigned char)*cp)) {
			while (isspace((unsigned char)*cp))
				cp++;
			macro_close(f);
		} else {
			fputs(" Ns", stdout);
			f->flags &= FMT_IMPL;
			accept_arg = 1;
		}
	}
	if (f->linestate == LINE_MACRO && !accept_arg &&
	    (f->flags & (FMT_CHILD | FMT_IMPL)) == 0)
		macro_close(f);

	/*
	 * 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) {
		switch (pnode_class(nn->node)) {
		case CLASS_LINE:
		case CLASS_ENCL:
			macro_open(f, "Pf");
			accept_arg = 1;
			f->flags |= FMT_CHILD;
			nn->spc = 1;
			break;
		default:
			break;
		}
	}

	switch (f->linestate) {
	case LINE_NEW:
		break;
	case LINE_TEXT:
		if (n->spc) {
			if (n->node == NODE_TEXT)
				macro_close(f);
			else
				putchar(' ');
		}
		break;
	case LINE_MACRO:
		if (accept_arg)
			putchar(' ');
		else
			macro_close(f);
		break;
	}

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

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

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

	if (f->linestate == LINE_MACRO)
		macro_addarg(f, cp, 0);
	else
		print_text(f, cp, 0);
}

static void
pnode_printpara(struct format *f, struct pnode *n)
{
	struct pnode	*np;

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

	if ((np = TAILQ_PREV(n, pnodeq, child)) == NULL)
	    np = n->parent;

	f->flags = 0;

	switch (np->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 (f->level < 3)
			return;
		break;
	default:
		break;
	}
	macro_line(f, "Pp");
}

static void
pnode_printrefnamediv(struct format *f, struct pnode *n)
{
	struct pnode	*nc, *nn;
	int		 comma;

	macro_line(f, "Sh NAME");
	comma = 0;
	TAILQ_FOREACH_SAFE(nc, &n->childq, child, nn) {
		if (nc->node != NODE_REFNAME)
			continue;
		if (comma)
			macro_addarg(f, ",", ARG_SPACE);
		macro_open(f, "Nm");
		macro_addnode(f, nc, ARG_SPACE);
		pnode_unlink(nc);
		comma = 1;
	}
	macro_close(f);
}

/*
 * If the SYNOPSIS macro has a superfluous title, kill it.
 */
static void
pnode_printrefsynopsisdiv(struct format *f, struct pnode *n)
{
	struct pnode	*nc, *nn;

	TAILQ_FOREACH_SAFE(nc, &n->childq, child, nn)
		if (nc->node == NODE_TITLE)
			pnode_unlink(nc);

	macro_line(f, "Sh SYNOPSIS");
}

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

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

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

	TAILQ_FOREACH(nc, &n->childq, child)
		if (nc->node == NODE_TITLE)
			break;

	if (nc == NULL) {
		switch (n->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(f);
		macro_open(f, "Sh");
		break;
	case 2:
		macro_close(f);
		macro_open(f, "Ss");
		break;
	default:
		pnode_printpara(f, n);
		macro_open(f, "Sy");
		break;
	}

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

/*
 * Start a reference, extracting the title and volume.
 */
static void
pnode_printciterefentry(struct format *f, struct pnode *n)
{
	struct pnode	*nc, *title, *manvol;

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

static void
pnode_printrefmeta(struct format *f, struct pnode *n)
{
	struct pnode	*nc, *title, *manvol;

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

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 *f, struct pnode *n)
{
	struct pnode	*nc;

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

	nc = TAILQ_FIRST(&n->childq);
	pnode_print(f, nc);

	while ((nc = TAILQ_NEXT(nc, child)) != NULL) {
		putchar(',');
		pnode_print(f, nc);
	}
	printf("right %s ", pnode_getattr_raw(n, ATTRKEY_CLOSE, ")"));
	pnode_unlinksub(n);
}

/*
 * 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 *f, struct pnode *n)
{
	struct pnode	*nc;

	nc = TAILQ_FIRST(&n->childq);
	pnode_print(f, nc);

	switch (n->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;
	}

	nc = TAILQ_NEXT(nc, child);
	pnode_print(f, nc);
	pnode_unlinksub(n);
}

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

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

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

	TAILQ_FOREACH(nc, &n->childq, child)
		macro_nodeline(f, "Fa", nc, ARG_SINGLE);

	macro_line(f, "Fc");
	pnode_unlinksub(n);
}

/*
 * 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 *f, struct pnode *n)
{
	struct pnode	*nc;
	struct pattr	*a;
	int		 isop, isrep, was_impl;

	isop = 1;
	isrep = was_impl = 0;
	TAILQ_FOREACH(a, &n->attrq, child) {
		if (a->key == ATTRKEY_CHOICE &&
		    (a->val == ATTRVAL_PLAIN || a->val == ATTRVAL_REQ))
			isop = 0;
		else if (a->key == ATTRKEY_REP && a->val == ATTRVAL_REPEAT)
			isrep = 1;
	}
	if (isop) {
		if (f->flags & FMT_IMPL) {
			was_impl = 1;
			macro_open(f, "Oo");
		} else {
			macro_open(f, "Op");
			f->flags |= FMT_IMPL;
		}
	}

	TAILQ_FOREACH(nc, &n->childq, child) {
		if (nc->node == NODE_TEXT)
			macro_open(f, "Ar");
		pnode_print(f, nc);
		if (isrep && nc->node == NODE_TEXT)
			macro_addarg(f, "...", ARG_SPACE);
	}
	if (isop) {
		if (was_impl)
			macro_open(f, "Oc");
		else
			f->flags &= ~FMT_IMPL;
	}
	pnode_unlinksub(n);
}

static void
pnode_printgroup(struct format *f, struct pnode *n)
{
	struct pnode	*nc, *nn;
	struct pattr	*a;
	int		 isop, sv;

	isop = 1;
	TAILQ_FOREACH(a, &n->attrq, child)
		if (a->key == ATTRKEY_CHOICE &&
		    (a->val == ATTRVAL_PLAIN || a->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 = f->linestate == LINE_NEW;
	if (isop)
		macro_open(f, "Op");
	else if (sv)
		macro_open(f, "No");
	f->flags |= FMT_IMPL;

	/*
	 * 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(nc, &n->childq, child) {
		pnode_print(f, nc);
		nn = TAILQ_NEXT(nc, child);
		while (nn != NULL) {
			if (nc->node != nn->node)
				break;
			macro_addarg(f, "|", ARG_SPACE);
			macro_addnode(f, nn, ARG_SPACE);
			nc = nn;
			nn = TAILQ_NEXT(nn, child);
		}
	}
	if (sv)
		macro_close(f);
	f->flags &= ~FMT_IMPL;
	pnode_unlinksub(n);
}

static void
pnode_printauthor(struct format *f, struct pnode *n)
{
	struct pnode	*nc, *nn;
	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, nn) {
		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, nn) {
		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) {
		f->flags |= FMT_CHILD;
		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)
{
	struct pnode	*nc;
	const char	*uri, *text;

	uri = pnode_getattr_raw(n, ATTRKEY_LINKEND, NULL);
	if (uri != NULL) {
		if (TAILQ_FIRST(&n->childq) != NULL) {
			TAILQ_FOREACH(nc, &n->childq, child)
				pnode_print(f, nc);
			text = "";
		} else if ((text = pnode_getattr_raw(n,
		    ATTRKEY_ENDTERM, NULL)) != NULL) {
			if (f->linestate == LINE_MACRO && f->flags & FMT_ARG)
				macro_addarg(f, text, ARG_SPACE);
			else
				print_text(f, text, ARG_SPACE);
		}
		if (text != NULL) {
			if (f->flags & FMT_IMPL)
				macro_open(f, "Po");
			else {
				macro_open(f, "Pq");
				f->flags |= FMT_CHILD;
			}
		}
		macro_open(f, "Sx");
		macro_addarg(f, uri, ARG_SPACE);
		if (text != NULL && f->flags & FMT_IMPL)
			macro_open(f, "Pc");
		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 *f, struct ptree *tree)
{
	struct pnode	*refmeta;

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

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

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

	macro_open(f, "It");
	f->flags |= FMT_IMPL;
	TAILQ_FOREACH_SAFE(nc, &n->childq, child, nn) {
		if (nc->node != NODE_TERM && nc->node != NODE_GLOSSTERM)
			continue;
		if (first == 0) {
			switch (f->linestate) {
			case LINE_NEW:
				break;
			case LINE_TEXT:
				print_text(f, ",", 0);
				break;
			case LINE_MACRO:
				macro_addarg(f, ",", 0);
				break;
			}
		}
		pnode_print(f, nc);
		pnode_unlink(nc);
		first = 0;
	}
	macro_close(f);
	while ((nc = TAILQ_FIRST(&n->childq)) != NULL) {
		pnode_print(f, nc);
		pnode_unlink(nc);
	}
	macro_close(f);
}

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

	TAILQ_FOREACH_SAFE(nc, &n->childq, child, nn) {
		if (nc->node == NODE_TITLE) {
			pnode_printpara(f, nc);
			pnode_print(f, nc);
			pnode_unlink(nc);
		}
	}
}

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

	macro_line(f, "Bl -dash -compact");
	TAILQ_FOREACH(nc, &n->childq, child) {
		macro_line(f, "It");
		pnode_print(f, nc);
	}
	macro_line(f, "El");
	pnode_unlink(n);
}

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_open(f, "It");
		f->flags |= FMT_IMPL;
		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 *f, struct pnode *n)
{
	struct pnode	*nc;

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

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

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

/*
 * 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 *f, struct pnode *n)
{
	struct pnode	*nc, *nn;
	int		 was_impl;

	if (n == NULL)
		return;

	was_impl = f->flags & FMT_IMPL;
	if (n->spc)
		f->flags &= ~FMT_NOSPC;
	else
		f->flags |= FMT_NOSPC;

	switch (n->node) {
	case NODE_APPLICATION:
		macro_open(f, "Nm");
		break;
	case NODE_ARG:
		pnode_printarg(f, n);
		break;
	case NODE_AUTHOR:
		pnode_printauthor(f, n);
		break;
	case NODE_AUTHORGROUP:
		macro_line(f, "An -split");
		break;
	case NODE_BLOCKQUOTE:
		macro_line(f, "Bd -ragged -offset indent");
		break;
	case NODE_BOOKINFO:
		macro_line(f, "Sh NAME");
		break;
	case NODE_CITEREFENTRY:
		pnode_printciterefentry(f, n);
		break;
	case NODE_CITETITLE:
		macro_open(f, "%T");
		break;
	case NODE_COMMAND:
		macro_open(f, "Nm");
		break;
	case NODE_CONSTANT:
		macro_open(f, "Dv");
		break;
	case NODE_EDITOR:
		print_text(f, "editor:", ARG_SPACE);
		macro_open(f, "An");
		break;
	case NODE_EMAIL:
		macro_open(f, "Aq Mt");
		break;
	case NODE_EMPHASIS:
	case NODE_FIRSTTERM:
	case NODE_GLOSSTERM:
		macro_open(f, "Em");
		break;
	case NODE_ENVAR:
		macro_open(f, "Ev");
		break;
	case NODE_ERRORNAME:
		macro_open(f, "Er");
		break;
	case NODE_FILENAME:
		macro_open(f, "Pa");
		break;
	case NODE_FUNCTION:
		macro_open(f, "Fn");
		break;
	case NODE_FUNCPROTOTYPE:
		pnode_printfuncprototype(f, n);
		break;
	case NODE_FUNCSYNOPSISINFO:
		macro_open(f, "Fd");
		break;
	case NODE_INFORMALEQUATION:
		macro_line(f, "Bd -ragged -offset indent");
		/* FALLTHROUGH */
	case NODE_INLINEEQUATION:
		macro_line(f, "EQ");
		break;
	case NODE_ITEMIZEDLIST:
		pnode_printlist(f, n);
		break;
	case NODE_GROUP:
		pnode_printgroup(f, n);
		break;
	case NODE_KEYSYM:
		macro_open(f, "Sy");
		break;
	case NODE_LINK:
		pnode_printlink(f, n);
		break;
	case NODE_LITERAL:
		if (was_impl)
			macro_open(f, "So");
		else {
			macro_open(f, "Ql");
			f->flags |= FMT_IMPL;
		}
		break;
	case NODE_LITERALLAYOUT:
		macro_close(f);
		macro_argline(f, "Bd", pnode_getattr(n, ATTRKEY_CLASS) ==
		    ATTRVAL_MONOSPACED ? "-literal" : "-unfilled");
		break;
	case NODE_MARKUP:
		macro_open(f, "Ic");
		break;
	case NODE_MML_MFENCED:
		pnode_printmathfenced(f, n);
		break;
	case NODE_MML_MROW:
	case NODE_MML_MI:
	case NODE_MML_MN:
	case NODE_MML_MO:
		if (TAILQ_EMPTY(&n->childq))
			break;
		fputs(" { ", stdout);
		break;
	case NODE_MML_MFRAC:
	case NODE_MML_MSUB:
	case NODE_MML_MSUP:
		pnode_printmath(f, n);
		break;
	case NODE_OPTION:
		macro_open(f, "Fl");
		break;
	case NODE_ORDEREDLIST:
		pnode_printlist(f, n);
		break;
	case NODE_PARA:
		pnode_printpara(f, n);
		break;
	case NODE_PARAMDEF:
	case NODE_PARAMETER:
		/* More often, these appear inside NODE_FUNCPROTOTYPE. */
		macro_open(f, "Fa");
		macro_addnode(f, n, ARG_SPACE | ARG_SINGLE);
		pnode_unlinksub(n);
		break;
	case NODE_QUOTE:
		if (was_impl)
			macro_open(f, "Do");
		else {
			macro_open(f, "Dq");
			f->flags |= FMT_IMPL;
		}
		break;
	case NODE_PROGRAMLISTING:
	case NODE_SCREEN:
	case NODE_SYNOPSIS:
		macro_line(f, "Bd -literal");
		break;
	case NODE_REFENTRYINFO:
		/* Suppress. */
		pnode_unlinksub(n);
		break;
	case NODE_REFNAME:
		/* More often, these appear inside NODE_REFNAMEDIV. */
		macro_open(f, "Nm");
		break;
	case NODE_REFNAMEDIV:
		pnode_printrefnamediv(f, n);
		break;
	case NODE_REFPURPOSE:
		macro_open(f, "Nd");
		break;
	case NODE_REFSYNOPSISDIV:
		pnode_printrefsynopsisdiv(f, n);
		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(f, n);
		break;
	case NODE_REPLACEABLE:
		macro_open(f, "Ar");
		break;
	case NODE_SBR:
		macro_line(f, "br");
		break;
	case NODE_TEXT:
	case NODE_ESCAPE:
		pnode_printtext(f, n);
		break;
	case NODE_TGROUP:
		pnode_printtgroup(f, n);
		break;
	case NODE_TITLE:
		if (n->parent != NULL &&
		    n->parent->node == NODE_BOOKINFO) {
			macro_open(f, "Nd");
			break;
		}
		pnode_printpara(f, n);
		macro_nodeline(f, "Sy", n, 0);
		pnode_unlinksub(n);
		break;
	case NODE_TYPE:
		macro_open(f, "Vt");
		break;
	case NODE_VARIABLELIST:
		pnode_printvariablelist(f, n);
		break;
	case NODE_VARNAME:
		macro_open(f, "Va");
		break;
	default:
		break;
	}

	TAILQ_FOREACH(nc, &n->childq, child)
		pnode_print(f, nc);

	switch (n->node) {
	case NODE_ESCAPE:
	case NODE_TERM:
	case NODE_TEXT:
		/* Accept more arguments to the previous macro. */
		return;
	case NODE_INFORMALEQUATION:
		macro_line(f, "EN");
		macro_line(f, "Ed");
		break;
	case NODE_INLINEEQUATION:
		macro_line(f, "EN");
		break;
	case NODE_LITERAL:
		if (was_impl)
			macro_open(f, "Sc");
		else
			f->flags &= ~FMT_IMPL;
		break;
	case NODE_MEMBER:
		if ((nn = TAILQ_NEXT(n, child)) != NULL &&
		    nn->node != NODE_MEMBER)
			nn = NULL;
		switch (f->linestate) {
		case LINE_TEXT:
			if (nn != NULL)
				print_text(f, ",", 0);
			break;
		case LINE_MACRO:
			if (nn != NULL)
				macro_addarg(f, ",", ARG_SPACE);
			macro_close(f);
			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(&n->childq))
			break;
		fputs(" } ", stdout);
		break;
	case NODE_QUOTE:
		if (was_impl)
			macro_open(f, "Dc");
		else
			f->flags &= ~FMT_IMPL;
		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:
		f->level--;
		break;
	case NODE_BLOCKQUOTE:
	case NODE_LITERALLAYOUT:
	case NODE_PROGRAMLISTING:
	case NODE_SCREEN:
	case NODE_SYNOPSIS:
		macro_line(f, "Ed");
		break;
	case NODE_TITLE:
		if (n->parent != NULL &&
		    n->parent->node == NODE_BOOKINFO)
			macro_line(f, "Sh AUTHORS");
		break;
	default:
		break;
	}
	f->flags &= ~FMT_ARG;
}

void
ptree_print_mdoc(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');
}