Plan 9 from Bell Labs’s /usr/web/sources/patch/maybe/html2ms-crnl/html2ms.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <bio.h>

enum
{
	SSIZE = 10,

	/* list types */
	Lordered = 0,
	Lunordered,
	Lmenu,
	Ldir,

};

Biobuf in, out;
int lastc = '\n';
int inpre = 0;

/* stack for fonts */
char *fontstack[SSIZE];
char *font = "R";
int fsp;

/* stack for lists */
struct
{
	int	type;
	int	ord;
} liststack[SSIZE];
int lsp;

int quoting;

typedef struct Goobie Goobie;
struct Goobie
{
	char *name;
	void (*f)(Goobie*, char*);
	void (*ef)(Goobie*, char*);
};

void	eatwhite(void);
void	escape(void);

typedef void Action(Goobie*, char*);

Action	g_ignore;
Action	g_unexpected;
Action	g_title;
Action	g_p;
Action	g_h;
Action	g_li;
Action	g_list, g_listend;
Action	g_pre;
Action	g_fpush, g_fpop;
Action	g_indent, g_exdent;
Action	g_dt;
Action	g_display;
Action	g_displayend;
Action	g_table, g_tableend, g_caption, g_captionend;
Action	g_br, g_hr;

Goobie gtab[] =
{
	"!--",		g_ignore,	g_unexpected,
	"!doctype",	g_ignore,	g_unexpected,
	"a",		g_ignore,	g_ignore,
	"address",	g_display,	g_displayend,
	"b",		g_fpush,	g_fpop,
	"base",		g_ignore,	g_unexpected,
	"blink",	g_ignore,	g_ignore,
	"blockquote",	g_ignore,	g_ignore,
	"body",		g_ignore,	g_ignore,
	"br",		g_br,		g_unexpected,
	"caption",	g_caption,	g_captionend,
	"center",	g_ignore,	g_ignore,
	"cite",		g_ignore,	g_ignore,
	"code",		g_ignore,	g_ignore,
	"dd",		g_ignore,	g_unexpected,
	"dfn",		g_ignore,	g_ignore,
	"dir",		g_list,		g_listend,
	"div",		g_ignore,	g_ignore,
	"dl",		g_indent,	g_exdent,
	"dt",		g_dt,		g_unexpected,
	"em",		g_ignore,	g_ignore,
	"font",		g_ignore,	g_ignore,
	"form",		g_ignore,	g_ignore,
	"h1",		g_h,		g_p,
	"h2",		g_h,		g_p,
	"h3",		g_h,		g_p,
	"h4",		g_h,		g_p,
	"h5",		g_h,		g_p,
	"h6",		g_h,		g_p,
	"head",		g_ignore,	g_ignore,
	"hr",		g_hr,		g_unexpected,
	"html",		g_ignore,	g_ignore,
	"i",		g_fpush,	g_fpop,
	"input",	g_ignore,	g_unexpected,
	"img",		g_ignore,	g_unexpected,
	"isindex",	g_ignore,	g_unexpected,
	"kbd",		g_fpush,	g_fpop,
	"key",		g_ignore,	g_ignore,
	"li",		g_li,		g_ignore,
	"link",		g_ignore,	g_unexpected,
	"listing",	g_ignore,	g_ignore,
	"menu",		g_list,		g_listend,
	"meta",		g_ignore,	g_unexpected,
	"nextid",	g_ignore,	g_unexpected,
	"ol",		g_list,		g_listend,
	"option",	g_ignore,	g_unexpected,
	"p",		g_p,		g_ignore,
	"plaintext",	g_ignore,	g_unexpected,
	"pre",		g_pre,		g_displayend,
	"script",	g_ignore,	g_ignore,
	"samp",		g_ignore,	g_ignore,
	"select",	g_ignore,	g_ignore,
	"span",		g_ignore,	g_ignore,
	"strike",	g_ignore,	g_ignore,
	"strong",	g_ignore,	g_ignore,
	"table",	g_table,	g_tableend,
	"textarea",	g_ignore,	g_ignore,
	"title",	g_title,	g_ignore,
	"tt",		g_fpush,	g_fpop,
	"u",		g_ignore,	g_ignore,
	"ul",		g_list,		g_listend,
	"var",		g_ignore,	g_ignore,
	"xmp",		g_ignore,	g_ignore,
	0,		0,	0,
};

typedef struct Entity Entity;
struct Entity
{
	char *name;
	Rune value;
};

Entity pl_entity[]=
{
	{ "quot",	34 },
	{ "amp",	38 },
	{ "lt",		60 },
	{ "gt",		62 },
	{ "nbsp",	160 },
	{ "iexcl",	161 },
	{ "cent",	162 },
	{ "pound",	163 },
	{ "curren",	164 },
	{ "yen",	165 },
	{ "brvbar",	166 },
	{ "sect",	167 },
	{ "uml",	168 },
	{ "copy",	169 },
	{ "ordf",	170 },
	{ "laquo",	171 },
	{ "not",	172 },
	{ "shy",	173 },
	{ "reg",	174 },
	{ "macr",	175 },
	{ "deg",	176 },
	{ "plusmn",	177 },
	{ "sup2",	178 },
	{ "sup3",	179 },
	{ "acute",	180 },
	{ "micro",	181 },
	{ "para",	182 },
	{ "middot",	183 },
	{ "cedil",	184 },
	{ "sup1",	185 },
	{ "ordm",	186 },
	{ "raquo",	187 },
	{ "frac14",	188 },
	{ "frac12",	189 },
	{ "frac34",	190 },
	{ "iquest",	191 },
	{ "Agrave",	192 },
	{ "Aacute",	193 },
	{ "Acirc",	194 },
	{ "Atilde",	195 },
	{ "Auml",	196 },
	{ "Aring",	197 },
	{ "AElig",	198 },
	{ "Ccedil",	199 },
	{ "Egrave",	200 },
	{ "Eacute",	201 },
	{ "Ecirc",	202 },
	{ "Euml",	203 },
	{ "Igrave",	204 },
	{ "Iacute",	205 },
	{ "Icirc",	206 },
	{ "Iuml",	207 },
	{ "ETH",	208 },
	{ "Ntilde",	209 },
	{ "Ograve",	210 },
	{ "Oacute",	211 },
	{ "Ocirc",	212 },
	{ "Otilde",	213 },
	{ "Ouml",	214 },
	{ "times",	215 },
	{ "Oslash",	216 },
	{ "Ugrave",	217 },
	{ "Uacute",	218 },
	{ "Ucirc",	219 },
	{ "Uuml",	220 },
	{ "Yacute",	221 },
	{ "THORN",	222 },
	{ "szlig",	223 },
	{ "agrave",	224 },
	{ "aacute",	225 },
	{ "acirc",	226 },
	{ "atilde",	227 },
	{ "auml",	228 },
	{ "aring",	229 },
	{ "aelig",	230 },
	{ "ccedil",	231 },
	{ "egrave",	232 },
	{ "eacute",	233 },
	{ "ecirc",	234 },
	{ "euml",	235 },
	{ "igrave",	236 },
	{ "iacute",	237 },
	{ "icirc",	238 },
	{ "iuml",	239 },
	{ "eth",	240 },
	{ "ntilde",	241 },
	{ "ograve",	242 },
	{ "oacute",	243 },
	{ "ocirc",	244 },
	{ "otilde",	245 },
	{ "ouml",	246 },
	{ "divide",	247 },
	{ "oslash",	248 },
	{ "ugrave",	249 },
	{ "uacute",	250 },
	{ "ucirc",	251 },
	{ "uuml",	252 },
	{ "yacute",	253 },
	{ "thorn",	254 },
	{ "yuml",	255 },
	{ "OElig",	338 },
	{ "oelig",	339 },
	{ "Scaron",	352 },
	{ "scaron",	353 },
	{ "Yuml",	376 },
	{ "fnof",	402 },
	{ "circ",	710 },
	{ "tilde",	732 },
	{ "Alpha",	913 },
	{ "Beta",	914 },
	{ "Gamma",	915 },
	{ "Delta",	916 },
	{ "Epsilon",	917 },
	{ "Zeta",	918 },
	{ "Eta",	919 },
	{ "Theta",	920 },
	{ "Iota",	921 },
	{ "Kappa",	922 },
	{ "Lambda",	923 },
	{ "Mu",		924 },
	{ "Nu",		925 },
	{ "Xi",		926 },
	{ "Omicron",	927 },
	{ "Pi",		928 },
	{ "Rho",	929 },
	{ "Sigma",	931 },
	{ "Tau",	932 },
	{ "Upsilon",	933 },
	{ "Phi",	934 },
	{ "Chi",	935 },
	{ "Psi",	936 },
	{ "Omega",	937 },
	{ "alpha",	945 },
	{ "beta",	946 },
	{ "gamma",	947 },
	{ "delta",	948 },
	{ "epsilon",	949 },
	{ "zeta",	950 },
	{ "eta",	951 },
	{ "theta",	952 },
	{ "iota",	953 },
	{ "kappa",	954 },
	{ "lambda",	955 },
	{ "mu",		956 },
	{ "nu",		957 },
	{ "xi",		958 },
	{ "omicron",	959 },
	{ "pi",		960 },
	{ "rho",	961 },
	{ "sigmaf",	962 },
	{ "sigma",	963 },
	{ "tau",	964 },
	{ "upsilon",	965 },
	{ "phi",	966 },
	{ "chi",	967 },
	{ "psi",	968 },
	{ "omega",	969 },
	{ "thetasym",	977 },
	{ "upsih",	978 },
	{ "piv",	982 },
	{ "ensp",	8194 },
	{ "emsp",	8195 },
	{ "thinsp",	8201 },
	{ "zwnj",	8204 },
	{ "zwj",	8205 },
	{ "lrm",	8206 },
	{ "rlm",	8207 },
	{ "ndash",	8211 },
	{ "mdash",	8212 },
	{ "lsquo",	8216 },
	{ "rsquo",	8217 },
	{ "sbquo",	8218 },
	{ "ldquo",	8220 },
	{ "rdquo",	8221 },
	{ "bdquo",	8222 },
	{ "dagger",	8224 },
	{ "Dagger",	8225 },
	{ "bull",	8226 },
	{ "hellip",	8230 },
	{ "permil",	8240 },
	{ "prime",	8242 },
	{ "Prime",	8243 },
	{ "lsaquo",	8249 },
	{ "rsaquo",	8250 },
	{ "oline",	8254 },
	{ "frasl",	8260 },
	{ "euro",	8364 },
	{ "image",	8465 },
	{ "weierp",	8472 },
	{ "real",	8476 },
	{ "trade",	8482 },
	{ "alefsym",	8501 },
	{ "larr",	8592 },
	{ "uarr",	8593 },
	{ "rarr",	8594 },
	{ "darr",	8595 },
	{ "harr",	8596 },
	{ "crarr",	8629 },
	{ "lArr",	8656 },
	{ "uArr",	8657 },
	{ "rArr",	8658 },
	{ "dArr",	8659 },
	{ "hArr",	8660 },
	{ "forall",	8704 },
	{ "part",	8706 },
	{ "exist",	8707 },
	{ "empty",	8709 },
	{ "nabla",	8711 },
	{ "isin",	8712 },
	{ "notin",	8713 },
	{ "ni",		8715 },
	{ "prod",	8719 },
	{ "sum",	8721 },
	{ "minus",	8722 },
	{ "lowast",	8727 },
	{ "radic",	8730 },
	{ "prop",	8733 },
	{ "infin",	8734 },
	{ "ang",	8736 },
	{ "and",	8743 },
	{ "or",		8744 },
	{ "cap",	8745 },
	{ "cup",	8746 },
	{ "int",	8747 },
	{ "there4",	8756 },
	{ "sim",	8764 },
	{ "cong",	8773 },
	{ "asymp",	8776 },
	{ "ne",		8800 },
	{ "equiv",	8801 },
	{ "le",		8804 },
	{ "ge",		8805 },
	{ "sub",	8834 },
	{ "sup",	8835 },
	{ "nsub",	8836 },
	{ "sube",	8838 },
	{ "supe",	8839 },
	{ "oplus",	8853 },
	{ "otimes",	8855 },
	{ "perp",	8869 },
	{ "sdot",	8901 },
	{ "lceil",	8968 },
	{ "rceil",	8969 },
	{ "lfloor",	8970 },
	{ "rfloor",	8971 },
	{ "lang",	9001 },
	{ "rang",	9002 },
	{ "loz",	9674 },
	{ "spades",	9824 },
	{ "clubs",	9827 },
	{ "hearts",	9829 },
	{ "diams",	9830 },
	{ nil,		0 },
};

int
cistrcmp(char *a, char *b)
{
	int c, d;

	for(;; a++, b++){
		d = tolower(*a);
		c = d - tolower(*b);
		if(c)
			break;
		if(d == 0)
			break;
	}
	return c;
}

int
readupto(char *buf, int n, char d, char notme)
{
	char *p;
	int c;

	buf[0] = 0;
	for(p = buf;; p++){
		c = Bgetc(&in);
		if(c < 0){
			*p = 0;
			return -1;
		}
		if(c == notme){
			Bungetc(&in);
			return -1;
		}
		if(c == d){
			*p = 0;
			return 0;
		}
		*p = c;
		if(p == buf + n){
			*p = 0;
			Bprint(&out, "<%s", buf);
			return -1;
		}
	}
}

void
dogoobie(void)
{
	char *arg, *type;
	Goobie *g;
	char buf[1024];
	int closing;

	if(readupto(buf, sizeof(buf), '>', '<') < 0){
		Bprint(&out, "<%s", buf);
		return;
	}
	type = buf;
	if(*type == '/'){
		type++;
		closing = 1;
	} else
		closing = 0;
	arg = strchr(type, ' ');
	if(arg == 0)
		arg = strchr(type, '\r');
	if(arg == 0)
		arg = strchr(type, '\n');
	if(arg)
		*arg++ = 0;
	for(g = gtab; g->name; g++)
		if(cistrcmp(type, g->name) == 0){
			if(closing){
				if(g->ef){
					(*g->ef)(g, arg);
					return;
				}
			} else {
				if(g->f){
					(*g->f)(g, arg);
					return;
				}
			}
		}
	if(closing)
		type--;
	if(arg)
		Bprint(&out, "<%s %s>\n", type, arg);
	else
		Bprint(&out, "<%s>\n", type);
}

void
main(int, char *argv[])
{
	int c, pos;

	argv0 = argv[0];
	Binit(&in, 0, OREAD);
	Binit(&out, 1, OWRITE);

	pos = 0;
	for(;;){
		c = Bgetc(&in);
		if(c < 0)
			return;
		switch(c){
		case '<':
			dogoobie();
			break;
		case '&':
			escape();
			break;
		case '\\':
			Bprint(&out, "\\(bs");
			break;
		case '\r':
		case '\n':
			if(quoting){
				Bputc(&out, '"');
				quoting = 0;
			}
			if(lastc != '\n')
				Bputc(&out, '\n');
			/* can't emit leading spaces in filled troff docs */
			if (!inpre)
				eatwhite();
			lastc = '\n';
			pos = 0;
			break;
		default:
			++pos;
			if(!inpre && isascii(c) && isspace(c) && pos > 80){
				Bputc(&out, '\n');
				eatwhite();
				pos = 0;
			}else
				Bputc(&out, c);
			lastc = c;
			break;
		}
	}
}

void
escape(void)
{
	int c;
	Entity *e;
	char buf[8];

	if(readupto(buf, sizeof(buf), ';', '\n') < 0){
		Bprint(&out, "&%s", buf);
		return;
	}
	for(e = pl_entity; e->name; e++)
		if(strcmp(buf, e->name) == 0){
			Bprint(&out, "%C", e->value);
			return;
		}
	if(*buf == '#'){
		c = strtol(buf+1, nil, 10);
		Bprint(&out, "%C", c);
		return;
	}
	fprint(2, "%s: unknown entity reference &%s;\n", argv0, buf);
	Bprint(&out, "&%s;", buf);
}

/*
 * whitespace is not significant to HTML, but newlines
 * and leading spaces are significant to troff.
 */
void
eatwhite(void)
{
	int c;

	for(;;){
		c = Bgetc(&in);
		if(c < 0)
			break;
		if(!isspace(c)){
			Bungetc(&in);
			break;
		}
	}
}

/*
 *  print at start of line
 */
void
printsol(char *fmt, ...)
{
	va_list arg;

	if(quoting){
		Bputc(&out, '"');
		quoting = 0;
	}
	if(lastc != '\n')
		Bputc(&out, '\n');
	va_start(arg, fmt);
	Bvprint(&out, fmt, arg);
	va_end(arg);
	lastc = '\n';
}

void
g_ignore(Goobie *g, char *arg)
{
	USED(g, arg);
}

void
g_unexpected(Goobie *g, char *arg)
{
	USED(arg);
	fprint(2, "%s: unexpected %s ending\n", argv0, g->name);
}

void
g_title(Goobie *g, char *arg)
{
	USED(arg);
	printsol(".TL\n", g->name);
}

void
g_p(Goobie *g, char *arg)
{
	USED(arg);
	printsol(".LP\n", g->name);
}

void
g_h(Goobie *g, char *arg)
{
	USED(arg);
	printsol(".SH %c\n", g->name[1]);
}

void
g_list(Goobie *g, char *arg)
{
	USED(arg);

	if(lsp != SSIZE){
		switch(g->name[0]){
		case 'o':
			liststack[lsp].type  = Lordered;
			liststack[lsp].ord = 0;
			break;
		default:
			liststack[lsp].type = Lunordered;
			break;
		}
	}
	lsp++;
}

void
g_br(Goobie *g, char *arg)
{
	USED(g, arg);
	printsol(".br\n");
}

void
g_li(Goobie *g, char *arg)
{
	USED(g, arg);
	if(lsp <= 0 || lsp > SSIZE){
		printsol(".IP \\(bu\n");
		return;
	}
	switch(liststack[lsp-1].type){
	case Lunordered:
		printsol(".IP \\(bu\n");
		break;
	case Lordered:
		printsol(".IP %d\n", ++liststack[lsp-1].ord);
		break;
	}
}

void
g_listend(Goobie *g, char *arg)
{
	USED(g, arg);
	if(--lsp < 0)
		lsp = 0;
	printsol(".LP\n");
}

void
g_display(Goobie *g, char *arg)
{
	USED(g, arg);
	printsol(".DS\n");
}

void
g_pre(Goobie *g, char *arg)
{
	USED(g, arg);
	printsol(".DS L\n");
	inpre = 1;
}

void
g_displayend(Goobie *g, char *arg)
{
	USED(g, arg);
	printsol(".DE\n");
	inpre = 0;
}

void
g_fpush(Goobie *g, char *arg)
{
	USED(arg);
	if(fsp < SSIZE)
		fontstack[fsp] = font;
	fsp++;
	switch(g->name[0]){
	case 'b':
		font = "B";
		break;
	case 'i':
		font = "I";
		break;
	case 'k':		/* kbd */
	case 't':		/* tt */
		font = "(CW";
		break;
	}
	Bprint(&out, "\\f%s", font);
}

void
g_fpop(Goobie *g, char *arg)
{
	USED(g, arg);
	fsp--;
	if(fsp < SSIZE)
		font = fontstack[fsp];
	else
		font = "R";

	Bprint(&out, "\\f%s", font);
}

void
g_indent(Goobie *g, char *arg)
{
	USED(g, arg);
	printsol(".RS\n");
}

void
g_exdent(Goobie *g, char *arg)
{
	USED(g, arg);
	printsol(".RE\n");
}

void
g_dt(Goobie *g, char *arg)
{
	USED(g, arg);
	printsol(".IP \"");
	quoting = 1;
}

void
g_hr(Goobie *g, char *arg)
{
	USED(g, arg);
	printsol(".br\n");
	printsol("\\l'5i'\n");
}


/*
<table border>
<caption><font size="+1"><b>Cumulative Class Data</b></font></caption>
<tr><th rowspan=2>DOSE<br>mg/kg</th><th colspan=2>PARALYSIS</th><th colspan=2>DEATH</th>
</tr>
<tr><th width=80>Number</th><th width=80>Percent</th><th width=80>Number</th><th width=80>Percent</th>
</tr>
<tr align=center>
<td>0.1</td><td><br></td> <td><br></td> <td><br></td> <td><br></td>
</tr>
<tr align=center>
<td>0.2</td><td><br></td> <td><br></td> <td><br></td> <td><br></td>
</tr>
<tr align=center>
<td>0.3</td><td><br></td> <td><br></td> <td><br></td> <td><br></td>
</tr>
<tr align=center>
<td>0.4</td><td><br></td> <td><br></td> <td><br></td> <td><br></td>
</tr>
<tr align=center>
<td>0.5</td><td><br></td> <td><br></td> <td><br></td> <td><br></td>
</tr>
<tr align=center>
<td>0.6</td><td><br></td> <td><br></td> <td><br></td> <td><br></td>
</tr>
<tr align=center>
<td>0.7</td><td><br></td> <td><br></td> <td><br></td> <td><br></td>
</tr>
<tr align=center>
<td>0.8</td><td><br></td> <td><br></td> <td><br></td> <td><br></td>
</tr>
<tr align=center>
<td>0.8 oral</td><td><br></td> <td><br></td> <td><br></td> <td><br></td>
</tr>
</table>
*/

void
g_table(Goobie *g, char *arg)
{
	USED(g, arg);
	printsol(".TS\ncenter ;\n");
}

void
g_tableend(Goobie *g, char *arg)
{
	USED(g, arg);
	printsol(".TE\n");
}

void
g_caption(Goobie *g, char *arg)
{
	USED(g, arg);
}

void
g_captionend(Goobie *g, char *arg)
{
	USED(g, arg);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to [email protected].