[BACK]Return to macro.c CVS log [TXT][DIR] Up to [cvsweb.bsd.lv] / docbook2mdoc

File: [cvsweb.bsd.lv] / docbook2mdoc / macro.c (download)

Revision 1.3, Tue Apr 2 15:53:02 2019 UTC (4 years, 11 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);
}