File: [cvsweb.bsd.lv] / docbook2mdoc / macro.c (download)
Revision 1.6, Sat Apr 6 22:37:57 2019 UTC (5 years, 5 months ago) by schwarze
Branch: MAIN
Changes since 1.5: +62 -11 lines
Store the information whether a node is preceded by whitespace
into the node tree. Use that information in the formatter
to suppress the insertion of whitespace
in text-text, macro-text, and macro-macro node sequences.
Text-macro sequences are not yet handled. They are more complicated
because they require emitting a .Pf macro as part of a text node
depending on the spacing properties of the *following* macro node.
|
/* $Id: macro.c,v 1.6 2019/04/06 22:37:57 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 <string.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(' ');
if (f->spc == 0)
fputs("Ns ", stdout);
break;
}
fputs(name, stdout);
}
void
macro_close(struct format *f)
{
if (f->linestate == LINE_NEW)
return;
putchar('\n');
f->linestate = LINE_NEW;
}
void
macro_line(struct format *f, const char *name)
{
macro_open(f, name);
macro_close(f);
}
/*
* At the end of a macro, decide whether the line needs to remain open
* because the next node follows without intervening whitespace;
* otherwise, close the line.
*/
void
macro_closepunct(struct format *f, struct pnode *pn)
{
char *cp;
if ((pn = TAILQ_NEXT(pn, child)) != NULL && pn->spc == 0) {
/*
* If a non-text node follows without intervening
* whitespace, the handler of that node will decide
* whether and how to suppress whitespace. To allow
* that, the macro line needs to remain open.
*/
if (pn->node != NODE_TEXT && pn->node != NODE_ESCAPE)
return;
/*
* Give closing punctuation
* in the form of trailing macro arguments.
*/
while (*pn->b != '\0' &&
strchr("!),.:;?]", *pn->b) != NULL) {
putchar(' ');
putchar(*pn->b);
pn->b++;
pn->bsz--;
}
/*
* Text follows without intervening whitespace.
* Append the first word with .Ns.
*/
if (*pn->b != '\0' && isspace((unsigned char)*pn->b) == 0) {
fputs(" Ns", stdout);
for (cp = pn->b; *cp != '\0'; cp++)
if (isspace((unsigned char)*cp))
break;
*cp = '\0';
macro_addarg(f, pn->b, ARG_SPACE);
pn->bsz -= cp - pn->b;
pn->b = cp;
if (pn->bsz > 0) {
pn->b++;
pn->bsz--;
pn->spc = 1;
}
}
/* Skip whitespace after the first word. */
while (isspace((unsigned char)*pn->b)) {
pn->b++;
pn->bsz--;
pn->spc = 1;
}
}
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)
{
struct pnode *nc;
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.
*/
while ((nc = TAILQ_FIRST(&pn->childq)) != NULL &&
TAILQ_NEXT(nc, child) == NULL)
pn = nc;
if (pn->node == NODE_TEXT || pn->node == NODE_ESCAPE) {
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 (nc != NULL) {
macro_addnode(f, nc, flags);
nc = TAILQ_NEXT(nc, 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);
}