File: [cvsweb.bsd.lv] / docbook2mdoc / macro.c (download)
Revision 1.3, Tue Apr 2 15:53:02 2019 UTC (5 years, 5 months ago) by schwarze
Branch: MAIN
Changes since 1.2: +5 -4 lines
Translate XML character entity references to roff character escape sequences.
Missing feature reported by Stephen Gregoratto <dev at sgregoratto dot me>.
Remaining known issues:
* Whitespace handling isn't perfect yet.
* Numeric character references aren't handled yet.
* The list of entities is still very incomplete.
* When it grows longer, we may have to switch to binary search.
* Local entities declared in the DTD are not yet handled.
|
/* $Id: macro.c,v 1.3 2019/04/02 15:53:02 schwarze Exp $ */
/*
* 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 "node.h"
#include "macro.h"
/*
* The implementation of the macro line formatter,
* a part of the mdoc(7) formatter.
*/
void
macro_open(struct format *f, const char *name)
{
switch (f->linestate) {
case LINE_TEXT:
putchar('\n');
/* FALLTHROUGH */
case LINE_NEW:
putchar('.');
f->linestate = LINE_MACRO;
break;
case LINE_MACRO:
putchar(' ');
break;
}
fputs(name, stdout);
}
void
macro_close(struct format *f)
{
assert(f->linestate == LINE_MACRO);
putchar('\n');
f->linestate = LINE_NEW;
}
void
macro_line(struct format *f, const char *name)
{
macro_open(f, name);
macro_close(f);
}
/*
* If the next node is a text node starting with closing punctuation,
* emit the closing punctuation as a trailing macro argument.
*/
void
macro_closepunct(struct format *f, struct pnode *pn)
{
if ((pn = TAILQ_NEXT(pn, child)) != NULL &&
pn->node == NODE_TEXT && pn->bsz > 0 &&
(pn->b[0] == ',' || pn->b[0] == '.') &&
(pn->bsz == 1 || isspace((unsigned char)pn->b[1]))) {
putchar(' ');
putchar(pn->b[0]);
pn->b++;
pn->bsz--;
}
macro_close(f);
}
/*
* Print an argument string on a macro line, collapsing whitespace.
*/
void
macro_addarg(struct format *f, const char *arg, int flags)
{
const char *cp;
assert(f->linestate == LINE_MACRO);
/* Quote if requested and necessary. */
if ((flags & (ARG_SINGLE | ARG_QUOTED)) == ARG_SINGLE) {
for (cp = arg; *cp != '\0'; cp++)
if (isspace((unsigned char)*cp))
break;
if (*cp != '\0') {
if (flags & ARG_SPACE) {
putchar(' ');
flags &= ~ ARG_SPACE;
}
putchar('"');
flags = ARG_QUOTED;
}
}
for (cp = arg; *cp != '\0'; cp++) {
/* Collapse whitespace. */
if (isspace((unsigned char)*cp)) {
flags |= ARG_SPACE;
continue;
} else if (flags & ARG_SPACE) {
putchar(' ');
flags &= ~ ARG_SPACE;
}
/* Escape us if we look like a macro. */
if ((flags & ARG_QUOTED) == 0 &&
(cp == arg || isspace((unsigned char)cp[-1])) &&
isupper((unsigned char)cp[0]) &&
islower((unsigned char)cp[1]) &&
(cp[2] == '\0' || cp[2] == ' ' ||
(islower((unsigned char)cp[2]) &&
(cp[3] == '\0' || cp[3] == ' '))))
fputs("\\&", stdout);
if (*cp == '"')
fputs("\\(dq", stdout);
else if (flags & ARG_UPPER)
putchar(toupper((unsigned char)*cp));
else
putchar(*cp);
if (*cp == '\\')
putchar('e');
}
}
void
macro_argline(struct format *f, const char *name, const char *arg)
{
macro_open(f, name);
macro_addarg(f, arg, ARG_SPACE);
macro_close(f);
}
/*
* Recursively append text from the children of a node to a macro line.
*/
void
macro_addnode(struct format *f, struct pnode *pn, int flags)
{
int quote_now;
assert(f->linestate == LINE_MACRO);
/*
* If this node or its only child is a text node, just add
* that text, letting macro_addarg() decide about quoting.
*/
if (pn->node == NODE_TEXT || pn->node == NODE_ESCAPE ||
((pn = TAILQ_FIRST(&pn->childq)) != NULL &&
(pn->node == NODE_TEXT || pn->node == NODE_ESCAPE) &&
TAILQ_NEXT(pn, child) == NULL)) {
macro_addarg(f, pn->b, flags);
return;
}
/*
* If we want the argument quoted and are not already
* in a quoted context, quote now.
*/
quote_now = 0;
if (flags & ARG_SINGLE) {
if ((flags & ARG_QUOTED) == 0) {
if (flags & ARG_SPACE) {
putchar(' ');
flags &= ~ARG_SPACE;
}
putchar('"');
flags |= ARG_QUOTED;
quote_now = 1;
}
flags &= ~ARG_SINGLE;
}
/*
* Iterate to child and sibling nodes,
* inserting whitespace between nodes.
*/
while (pn != NULL) {
macro_addnode(f, pn, flags);
pn = TAILQ_NEXT(pn, child);
flags |= ARG_SPACE;
}
if (quote_now)
putchar('"');
}
void
macro_nodeline(struct format *f, const char *name, struct pnode *pn, int flags)
{
macro_open(f, name);
macro_addnode(f, pn, ARG_SPACE | flags);
macro_close(f);
}
/*
* Print a word on the current text line if one is open, or on a new text
* line otherwise. The flag ARG_SPACE inserts spaces between words.
*/
void
print_text(struct format *f, const char *word, int flags) {
switch (f->linestate) {
case LINE_NEW:
break;
case LINE_TEXT:
if (flags & ARG_SPACE)
putchar(' ');
break;
case LINE_MACRO:
macro_close(f);
break;
}
fputs(word, stdout);
f->linestate = LINE_TEXT;
}
/*
* Recursively print the content of a node on a text line.
*/
void
print_textnode(struct format *f, struct pnode *n)
{
struct pnode *nc;
if (n->node == NODE_TEXT || n->node == NODE_ESCAPE)
print_text(f, n->b, ARG_SPACE);
else
TAILQ_FOREACH(nc, &n->childq, child)
print_textnode(f, nc);
}