[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.139, Sun Apr 28 15:03:28 2019 UTC (4 years, 11 months ago) by schwarze
Branch: MAIN
Changes since 1.138: +6 -17 lines

In this program, there is never a need to survive memory allocation
failure, and there are many places allocating memory.  Consequently,
the code can be simplified providing memory allocation functions
that error out on failure, in the conventional way.

/* $Id: docbook2mdoc.c,v 1.139 2019/04/28 15:03:28 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 "xmalloc.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_printrefentry(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 && !accept_arg &&
	    (n->flags & NFLAG_SPC) == 0) {
		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->flags & NFLAG_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->flags |= NFLAG_SPC;
			break;
		default:
			break;
		}
	}

	switch (f->linestate) {
	case LINE_NEW:
		break;
	case LINE_TEXT:
		if (n->flags & NFLAG_SPC) {
			if (n->flags & NFLAG_LINE &&
			    pnode_class(n->node) == CLASS_TEXT)
				macro_close(f);
			else
				putchar(' ');
		}
		break;
	case LINE_MACRO:
		if (accept_arg == 0)
			macro_close(f);
		else if (n->flags & NFLAG_SPC ||
		    (f->flags & FMT_ARG) == 0 ||
		    (nn = TAILQ_PREV(n, pnodeq, child)) == NULL ||
		    pnode_class(nn->node) != CLASS_TEXT)
			putchar(' ');
		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_printimagedata(struct format *f, struct pnode *n)
{
	const char	*cp;

	if ((cp = pnode_getattr_raw(n, ATTRKEY_FILEREF, NULL)) == NULL)
		cp = pnode_getattr_raw(n, ATTRKEY_ENTITYREF, NULL);
	if (cp != NULL) {
		print_text(f, "[image:", ARG_SPACE);
		print_text(f, cp, ARG_SPACE);
		print_text(f, "]", 0);
	} else
		print_text(f, "[image]", ARG_SPACE);
}

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

	f->parastate = PARA_HAVE;
	macro_line(f, "Sh NAME");
	f->parastate = PARA_HAVE;
	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);

	f->parastate = PARA_HAVE;
	macro_line(f, "Sh SYNOPSIS");
	f->parastate = PARA_HAVE;
}

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

	if (n->parent == NULL) {
		pnode_printrefentry(f, n);
		return;
	}

	level = ++f->level;
	flags = ARG_SPACE;
	switch (n->node) {
	case NODE_PREFACE:
	case NODE_SECTION:
	case NODE_APPENDIX:
		if (level == 1)
			flags |= ARG_UPPER;
		break;
	case NODE_SIMPLESECT:
	case NODE_LEGALNOTICE:
		if (level < 2)
			level = 2;
		break;
	default:
		if (level < 3)
			level = 3;
		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);
		f->parastate = PARA_HAVE;
		macro_open(f, "Sh");
		break;
	case 2:
		macro_close(f);
		f->parastate = PARA_HAVE;
		macro_open(f, "Ss");
		break;
	default:
		if (f->parastate == PARA_MID)
			f->parastate = PARA_WANT;
		macro_open(f, "Sy");
		break;
	}

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

	/*
	 * DocBook has no equivalent for -split mode,
	 * so just switch the default in the AUTHORS section.
	 */

	if (nc != NULL) {
		if (level == 1 &&
		    (ncc = TAILQ_FIRST(&nc->childq)) != NULL &&
		    ncc->node == NODE_TEXT &&
		    strcasecmp(ncc->b, "AUTHORS") == 0)
			macro_line(f, "An -nosplit");
		pnode_unlink(nc);
	}
	f->parastate = level > 2 ? PARA_WANT : PARA_HAVE;
}

/*
 * 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);
}

/*
 * 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	*fdef, *ftype, *nc, *nn;

	/*
	 * Extract <funcdef> child and ignore <void> child.
	 * Leave other children in place, to be treated as parameters.
	 */

	fdef = NULL;
	TAILQ_FOREACH_SAFE(nc, &n->childq, child, nn) {
		switch (nc->node) {
		case NODE_FUNCDEF:
			if (fdef == NULL) {
				fdef = nc;
				TAILQ_REMOVE(&n->childq, nc, child);
				nc->parent = NULL;
			}
			break;
		case NODE_VOID:
			pnode_unlink(nc);
			break;
		default:
			break;
		}
	}

	/*
	 * If no children are left, the function is void; use .Fn.
	 * Otherwise, use .Fo.
	 */

	nc = TAILQ_FIRST(&n->childq);
	if (fdef != NULL) {
		ftype = TAILQ_FIRST(&fdef->childq);
		if (ftype != NULL && ftype->node == NODE_TEXT) {
			macro_argline(f, "Ft", ftype->b);
			pnode_unlink(ftype);
		}
		if (nc == NULL) {
			macro_open(f, "Fn");
			macro_addnode(f, fdef, ARG_SPACE | ARG_SINGLE);
			macro_addarg(f, "void", ARG_SPACE);
			macro_close(f);
		} else
			macro_nodeline(f, "Fo", fdef, ARG_SINGLE);
		pnode_unlink(fdef);
	} else if (nc == NULL)
		macro_line(f, "Fn UNKNOWN void");
	else
		macro_line(f, "Fo UNKNOWN");

	if (nc == NULL)
		return;

	while (nc != NULL) {
		macro_nodeline(f, "Fa", nc, ARG_SINGLE);
		pnode_unlink(nc);
		nc = TAILQ_FIRST(&n->childq);
	}
	macro_line(f, "Fc");
}

/*
 * 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 && f->linestate == LINE_MACRO)
		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;
	struct pattr	*a;
	int		 bar, 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;
		}
	} else if (isrep) {
		if (f->flags & FMT_IMPL) {
			was_impl = 1;
			macro_open(f, "Bro");
		} else {
			macro_open(f, "Brq");
			f->flags |= FMT_IMPL;
		}
	}
	bar = 0;
	TAILQ_FOREACH(nc, &n->childq, child) {
		if (bar && f->linestate == LINE_MACRO)
			macro_addarg(f, "|", ARG_SPACE);
		pnode_print(f, nc);
		bar = 1;
	}
	if (isop) {
		if (was_impl)
			macro_open(f, "Oc");
		else
			f->flags &= ~FMT_IMPL;
	} else if (isrep) {
		if (was_impl)
			macro_open(f, "Brc");
		else
			f->flags &= ~FMT_IMPL;
	}
	if (isrep && f->linestate == LINE_MACRO)
		macro_addarg(f, "...", ARG_SPACE);
	pnode_unlinksub(n);
}

static void
pnode_printsystemitem(struct format *f, struct pnode *n)
{
	switch (pnode_getattr(n, ATTRKEY_CLASS)) {
	case ATTRVAL_IPADDRESS:
		break;
	case ATTRVAL_SYSTEMNAME:
		macro_open(f, "Pa");
		break;
	case ATTRVAL_EVENT:
	default:
		macro_open(f, "Sy");
		break;
	}
}

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;
		macro_open(f, "Aq Mt");
		macro_addnode(f, nc, ARG_SPACE);
		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_printxref(struct format *f, struct pnode *n)
{
	const char	*linkend;

	linkend = pnode_getattr_raw(n, ATTRKEY_LINKEND, NULL);
	if (linkend != NULL) {
		macro_open(f, "Sx");
		macro_addarg(f, linkend, ARG_SPACE);
	}
}

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);
	}
}

static void
pnode_printolink(struct format *f, struct pnode *n)
{
	const char	*uri, *ptr, *local;

	uri = pnode_getattr_raw(n, ATTRKEY_TARGETDOC, NULL);
	ptr = pnode_getattr_raw(n, ATTRKEY_TARGETPTR, NULL);
	local = pnode_getattr_raw(n, ATTRKEY_LOCALINFO, NULL);
	if (uri == NULL) {
		uri = ptr;
		ptr = NULL;
	}
	if (uri == NULL) {
		uri = local;
		local = NULL;
	}
	if (uri == NULL)
		return;

	macro_open(f, "Lk");
	macro_addarg(f, uri, ARG_SPACE | ARG_SINGLE);
	macro_addnode(f, n, ARG_SPACE | ARG_SINGLE);
	if (ptr != NULL || local != NULL) {
		macro_close(f);
		macro_open(f, "Pq");
		if (ptr != NULL)
			macro_addarg(f, ptr, ARG_SPACE);
		if (local != NULL)
			macro_addarg(f, local, ARG_SPACE);
	}
	pnode_unlinksub(n);
}

static void
pnode_printprologue(struct format *f, struct pnode *root)
{
	struct pnode	*date, *refmeta, *name, *vol, *descr, *nc, *nn;
	const char	*sname;

	/* Collect information. */

	if ((date = pnode_takefirst(root, NODE_PUBDATE)) == NULL)
		date = pnode_takefirst(root, NODE_DATE);

	name = vol = NULL;
	if ((refmeta = pnode_findfirst(root, NODE_REFMETA)) != NULL) {
		TAILQ_FOREACH_SAFE(nc, &refmeta->childq, child, nn) {
			switch (nc->node) {
			case NODE_REFENTRYTITLE:
				name = nc;
				break;
			case NODE_MANVOLNUM:
				vol = nc;
				break;
			default:
				continue;
			}
			TAILQ_REMOVE(&refmeta->childq, nc, child);
		}
	}

	if (pnode_findfirst(root, NODE_REFNAMEDIV) == NULL &&
	    ((nc = pnode_findfirst(root, NODE_BOOKINFO)) != NULL ||
	     (nc = pnode_findfirst(root, NODE_REFENTRYINFO)) != NULL))
		descr = pnode_takefirst(nc, NODE_TITLE);
	else
		descr = NULL;

	/* Print prologue. */

	if (date == NULL)
		macro_line(f, "Dd $Mdocdate" "$");
	else
		macro_nodeline(f, "Dd", date, 0);

	macro_open(f, "Dt");
	if (name == NULL) {
		sname = pnode_getattr_raw(root, ATTRKEY_ID, "UNKNOWN");
		macro_addarg(f, sname, ARG_SPACE | ARG_SINGLE | ARG_UPPER);
	} else
		macro_addnode(f, name, ARG_SPACE | ARG_SINGLE | ARG_UPPER);
	if (vol == NULL)
		macro_addarg(f, "1", ARG_SPACE);
	else
		macro_addnode(f, vol, ARG_SPACE | ARG_SINGLE);

	macro_line(f, "Os");

	if (descr != NULL) {
		macro_line(f, "Sh NAME");
		if (name == NULL)
			macro_argline(f, "Nm", sname);
		else
			macro_nodeline(f, "Nm", name, ARG_SINGLE);
		macro_nodeline(f, "Nd", descr, 0);
	}

	/* Clean up. */

	pnode_unlink(date);
	pnode_unlink(name);
	pnode_unlink(vol);
	pnode_unlink(descr);
	f->parastate = PARA_HAVE;
}

static void
pnode_printrefentry(struct format *f, struct pnode *n)
{
	struct pnode	*info, *meta, *nc, *title;
	struct pnode	*match, *later;

	/* Collect nodes that remained behind when writing the prologue. */

	meta = NULL;
	info = pnode_takefirst(n, NODE_BOOKINFO);
	if (info != NULL && TAILQ_FIRST(&info->childq) == NULL) {
		pnode_unlink(info);
		info = NULL;
	}
	if (info == NULL) {
		info = pnode_takefirst(n, NODE_REFENTRYINFO);
		if (info != NULL && TAILQ_FIRST(&info->childq) == NULL) {
			pnode_unlink(info);
			info = NULL;
		}
		if (info == NULL)
			info = pnode_takefirst(n, NODE_INFO);
		meta = pnode_takefirst(n, NODE_REFMETA);
		if (meta != NULL && TAILQ_FIRST(&meta->childq) == NULL) {
			pnode_unlink(meta);
			meta = NULL;
		}
	}
	if (info == NULL && meta == NULL)
		return;

	/*
	 * Find the best place to put this information.
	 * Use the last existing AUTHORS node, if any.
	 * Otherwise, put it behind all standard sections that
	 * conventionally precede AUTHORS, and also behind any
	 * non-standard sections that follow the last of these,
	 * but before the next standard section.
	 */

	match = later = NULL;
	TAILQ_FOREACH(nc, &n->childq, child) {
		switch (nc->node) {
		case NODE_REFENTRY:
		case NODE_REFNAMEDIV:
		case NODE_REFSYNOPSISDIV:
		case NODE_PREFACE:
			later = NULL;
			continue;
		case NODE_APPENDIX:
		case NODE_INDEX:
			if (later == NULL)
				later = nc;
			continue;
		default:
			break;
		}
		if ((title = pnode_findfirst(nc, NODE_TITLE)) == NULL ||
		    (title = TAILQ_FIRST(&title->childq)) == NULL ||
		    title->node != NODE_TEXT)
			continue;
		if (strcasecmp(title->b, "AUTHORS") == 0 ||
		    strcasecmp(title->b, "AUTHOR") == 0)
			match = nc;
		else if (strcasecmp(title->b, "NAME") == 0 ||
		    strcasecmp(title->b, "SYNOPSIS") == 0 ||
		    strcasecmp(title->b, "DESCRIPTION") == 0 ||
		    strcasecmp(title->b, "RETURN VALUES") == 0 ||
		    strcasecmp(title->b, "ENVIRONMENT") == 0 ||
		    strcasecmp(title->b, "FILES") == 0 ||
		    strcasecmp(title->b, "EXIT STATUS") == 0 ||
		    strcasecmp(title->b, "EXAMPLES") == 0 ||
		    strcasecmp(title->b, "DIAGNOSTICS") == 0 ||
		    strcasecmp(title->b, "ERRORS") == 0 ||
		    strcasecmp(title->b, "SEE ALSO") == 0 ||
		    strcasecmp(title->b, "STANDARDS") == 0 ||
		    strcasecmp(title->b, "HISTORY") == 0)
			later = NULL;
		else if ((strcasecmp(title->b, "CAVEATS") == 0 ||
		    strcasecmp(title->b, "BUGS") == 0) &&
		    later == NULL)
			later = nc;
	}

	/*
	 * If no AUTHORS section was found, create one from scratch,
	 * and insert that at the place selected earlier.
	 */

	if (match == NULL) {
		match = xcalloc(1, sizeof(*match));
		match->node = NODE_SECTION;
		match->flags |= NFLAG_SPC;
		match->parent = n;
		TAILQ_INIT(&match->childq);
		TAILQ_INIT(&match->attrq);
		nc = pnode_alloc(match);
		nc->node = NODE_TITLE;
		nc->flags |= NFLAG_SPC;
		nc = pnode_alloc(nc);
		nc->node = NODE_TEXT;
		nc->b = xstrdup("AUTHORS");
		nc->flags |= NFLAG_SPC;
		if (later == NULL)
			TAILQ_INSERT_TAIL(&n->childq, match, child);
		else
			TAILQ_INSERT_BEFORE(later, match, child);
	}

	/*
	 * Dump the stuff excised at the beginning
	 * into this AUTHORS section.
	 */

	if (info != NULL)
		TAILQ_INSERT_TAIL(&match->childq, info, child);
	if (meta != NULL)
		TAILQ_INSERT_TAIL(&match->childq, meta, child);
}

/*
 * 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, *ncc;
	int		 comma;

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

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) {
			if (f->parastate == PARA_MID)
				f->parastate = PARA_WANT;
			macro_nodeline(f, "Sy", nc, 0);
			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");
		f->parastate = PARA_HAVE;
		pnode_print(f, nc);
		f->parastate = PARA_HAVE;
		pnode_unlink(nc);
	}
	macro_line(f, "El");
	pnode_unlinksub(n);
}

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

	f->parastate = PARA_HAVE;
	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;
		f->parastate = PARA_HAVE;
		pnode_print(f, ne);
		macro_close(f);
		pnode_unlink(ne);
		f->parastate = PARA_HAVE;
		pnode_print(f, nr);
		f->parastate = PARA_HAVE;
		pnode_unlink(nr);
	}
	macro_line(f, "El");
	f->parastate = PARA_WANT;
	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;
	}

	f->parastate = PARA_HAVE;
	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");
	f->parastate = PARA_WANT;
	pnode_unlinksub(n);
}

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

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

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

	pnode_printtitle(f, n);
	f->parastate = PARA_HAVE;
	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");
	f->parastate = PARA_WANT;
	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->flags & NFLAG_SPC)
		f->flags &= ~FMT_NOSPC;
	else
		f->flags |= FMT_NOSPC;

	switch (n->node) {
	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:
		f->parastate = PARA_HAVE;
		macro_line(f, "Bd -ragged -offset indent");
		f->parastate = PARA_HAVE;
		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_COPYRIGHT:
		print_text(f, "Copyright", ARG_SPACE);
		fputs(" \\(co", stdout);
		break;
	case NODE_EDITOR:
		print_text(f, "editor:", ARG_SPACE);
		pnode_printauthor(f, n);
		break;
	case NODE_EMAIL:
		if (was_impl)
			macro_open(f, "Ao Mt");
		else {
			macro_open(f, "Aq Mt");
			f->flags |= FMT_IMPL;
		}
		break;
	case NODE_EMPHASIS:
	case NODE_FIRSTTERM:
	case NODE_GLOSSTERM:
		if ((nc = TAILQ_FIRST(&n->childq)) != NULL &&
		    pnode_class(nc->node) < CLASS_LINE)
			macro_open(f, "Em");
		if (n->node == NODE_GLOSSTERM)
			f->parastate = PARA_HAVE;
		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_FOOTNOTE:
		macro_line(f, "Bo");
		f->parastate = PARA_HAVE;
		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_IMAGEDATA:
		pnode_printimagedata(f, n);
		break;
	case NODE_INFORMALEQUATION:
		f->parastate = PARA_HAVE;
		macro_line(f, "Bd -ragged -offset indent");
		f->parastate = PARA_HAVE;
		/* 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:
	case NODE_PRODUCTNAME:
		macro_open(f, "Sy");
		break;
	case NODE_LINK:
		pnode_printlink(f, n);
		break;
	case NODE_LITERAL:
		if (n->parent != NULL && n->parent->node == NODE_QUOTE)
			macro_open(f, "Li");
		else if (was_impl)
			macro_open(f, "So Li");
		else {
			macro_open(f, "Ql");
			f->flags |= FMT_IMPL;
		}
		break;
	case NODE_LITERALLAYOUT:
		macro_close(f);
		f->parastate = PARA_HAVE;
		macro_argline(f, "Bd", pnode_getattr(n, ATTRKEY_CLASS) ==
		    ATTRVAL_MONOSPACED ? "-literal" : "-unfilled");
		f->parastate = PARA_HAVE;
		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_OLINK:
		pnode_printolink(f, n);
		break;
	case NODE_OPTION:
		if ((nc = TAILQ_FIRST(&n->childq)) != NULL &&
		    pnode_class(nc->node) < CLASS_LINE)
			macro_open(f, "Fl");
		break;
	case NODE_ORDEREDLIST:
		pnode_printlist(f, n);
		break;
	case NODE_PARA:
		if (f->parastate == PARA_MID)
			f->parastate = PARA_WANT;
		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 ((nc = TAILQ_FIRST(&n->childq)) != NULL &&
		    nc->node == NODE_FILENAME &&
		    TAILQ_NEXT(nc, child) == NULL) {
			if (n->flags & NFLAG_SPC)
				nc->flags |= NFLAG_SPC;
		} else 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:
		f->parastate = PARA_HAVE;
		macro_line(f, "Bd -literal");
		f->parastate = PARA_HAVE;
		break;
	case NODE_SYSTEMITEM:
		pnode_printsystemitem(f, n);
		break;
	case NODE_REFENTRY:
		pnode_printrefentry(f, 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_SIMPLESECT:
	case NODE_APPENDIX:
	case NODE_LEGALNOTICE:
	case NODE_NOTE:
	case NODE_TIP:
	case NODE_CAUTION:
	case NODE_WARNING:
		pnode_printsection(f, n);
		break;
	case NODE_REPLACEABLE:
		macro_open(f, "Ar");
		break;
	case NODE_SBR:
		if (f->parastate == PARA_MID)
			macro_line(f, "br");
		break;
	case NODE_SUBSCRIPT:
		if (f->linestate == LINE_MACRO)
			macro_addarg(f, "_", 0);
		else
			print_text(f, "_", 0);
		if ((nc = TAILQ_FIRST(&n->childq)) != NULL)
			nc->flags &= ~(NFLAG_LINE | NFLAG_SPC);
		break;
	case NODE_SUPERSCRIPT:
		fputs("\\(ha", stdout);
		if ((nc = TAILQ_FIRST(&n->childq)) != NULL)
			nc->flags &= ~(NFLAG_LINE | NFLAG_SPC);
		break;
	case NODE_TEXT:
	case NODE_ESCAPE:
		pnode_printtext(f, n);
		break;
	case NODE_TGROUP:
		pnode_printtgroup(f, n);
		break;
	case NODE_TITLE:
	case NODE_SUBTITLE:
		if (f->parastate == PARA_MID)
			f->parastate = PARA_WANT;
		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;
	case NODE_VOID:
		print_text(f, "void", ARG_SPACE);
		break;
	case NODE_XREF:
		pnode_printxref(f, n);
		break;
	default:
		break;
	}

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

	switch (n->node) {
	case NODE_EMAIL:
		if (was_impl) {
			f->flags &= ~FMT_NOSPC;
			macro_open(f, "Ac");
		} else
			f->flags &= ~FMT_IMPL;
		break;
	case NODE_ESCAPE:
	case NODE_TERM:
	case NODE_TEXT:
		/* Accept more arguments to the previous macro. */
		return;
	case NODE_FOOTNOTE:
		f->parastate = PARA_HAVE;
		macro_line(f, "Bc");
		break;
	case NODE_GLOSSTERM:
		f->parastate = PARA_HAVE;
		break;
	case NODE_INFORMALEQUATION:
		macro_line(f, "EN");
		macro_line(f, "Ed");
		break;
	case NODE_INLINEEQUATION:
		macro_line(f, "EN");
		break;
	case NODE_LITERAL:
		if (n->parent != NULL && n->parent->node == NODE_QUOTE)
			/* nothing */;
		else if (was_impl) {
			f->flags &= ~FMT_NOSPC;
			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_PARA:
		if (f->parastate == PARA_MID)
			f->parastate = PARA_WANT;
		break;
	case NODE_QUOTE:
		if ((nc = TAILQ_FIRST(&n->childq)) != NULL &&
		    nc->node == NODE_FILENAME &&
		    TAILQ_NEXT(nc, child) == NULL)
			/* nothing */;
		else if (was_impl) {
			f->flags &= ~FMT_NOSPC;
			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:
		f->parastate = PARA_HAVE;
		macro_line(f, "Ed");
		f->parastate = PARA_WANT;
		break;
	case NODE_TITLE:
	case NODE_SUBTITLE:
		f->parastate = PARA_WANT;
		break;
	default:
		break;
	}
	f->flags &= ~FMT_ARG;
}

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

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