File: [cvsweb.bsd.lv] / docbook2mdoc / docbook2mdoc.c (download)
Revision 1.126, Sun Apr 14 22:37:56 2019 UTC (5 years, 5 months ago) by schwarze
Branch: MAIN
Changes since 1.125: +19 -1 lines
Rudimentary implementation of <imagedata> by printing the filename.
Ignore the wrappers <caption>, <figure>, <imageobject>, <mediaobject>.
|
/* $Id: docbook2mdoc.c,v 1.126 2019/04/14 22:37:56 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_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 && !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_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_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_FOOTNOTE:
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_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);
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);
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) {
ncc = TAILQ_FIRST(&nc->childq);
if (ncc != NULL && ncc->node == NODE_TEXT &&
strcasecmp(ncc->b, "AUTHORS") == 0)
macro_line(f, "An -nosplit");
pnode_unlink(nc);
}
}
/*
* 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_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 && 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_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 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);
}
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;
}
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) {
if ((match = calloc(1, sizeof(*match))) == NULL) {
perror(NULL);
exit(1);
}
match->node = NODE_SECTION;
match->spc = 1;
match->parent = n;
TAILQ_INIT(&match->childq);
TAILQ_INIT(&match->attrq);
if ((nc = pnode_alloc(match)) == NULL) {
perror(NULL);
exit(1);
}
nc->node = NODE_TITLE;
nc->spc = 1;
if ((nc = pnode_alloc(nc)) == NULL) {
perror(NULL);
exit(1);
}
nc->node = NODE_TEXT;
if ((nc->b = strdup("AUTHORS")) == NULL) {
perror(NULL);
exit(1);
}
nc->spc = 1;
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;
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_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_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");
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");
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:
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 (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);
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:
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:
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_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:
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->spc = 0;
break;
case NODE_SUPERSCRIPT:
fputs("\\(ha", stdout);
if ((nc = TAILQ_FIRST(&n->childq)) != NULL)
nc->spc = 0;
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:
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_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:
macro_line(f, "Bc");
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_QUOTE:
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:
macro_line(f, "Ed");
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->root);
pnode_print(&formatter, tree->root);
if (formatter.linestate != LINE_NEW)
putchar('\n');
}