html.c revision 256281
1193323Sed/*	$Id: html.c,v 1.150 2011/10/05 21:35:17 kristaps Exp $ */
2193323Sed/*
3193323Sed * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4193323Sed * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
5193323Sed *
6193323Sed * Permission to use, copy, modify, and distribute this software for any
7193323Sed * purpose with or without fee is hereby granted, provided that the above
8193323Sed * copyright notice and this permission notice appear in all copies.
9193323Sed *
10193323Sed * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11193323Sed * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12193323Sed * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13193323Sed * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14193323Sed * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15193323Sed * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16193323Sed * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17193323Sed */
18193323Sed#ifdef HAVE_CONFIG_H
19193323Sed#include "config.h"
20193323Sed#endif
21193323Sed
22193323Sed#include <sys/types.h>
23193323Sed
24193323Sed#include <assert.h>
25193323Sed#include <ctype.h>
26193323Sed#include <stdarg.h>
27193323Sed#include <stdio.h>
28193323Sed#include <stdint.h>
29193323Sed#include <stdlib.h>
30193323Sed#include <string.h>
31193323Sed#include <unistd.h>
32193323Sed
33193323Sed#include "mandoc.h"
34193323Sed#include "libmandoc.h"
35193323Sed#include "out.h"
36207618Srdivacky#include "html.h"
37193323Sed#include "main.h"
38193323Sed
39193323Sedstruct	htmldata {
40193323Sed	const char	 *name;
41193323Sed	int		  flags;
42193323Sed#define	HTML_CLRLINE	 (1 << 0)
43193323Sed#define	HTML_NOSTACK	 (1 << 1)
44193323Sed#define	HTML_AUTOCLOSE	 (1 << 2) /* Tag has auto-closure. */
45193323Sed};
46193323Sed
47193323Sedstatic	const struct htmldata htmltags[TAG_MAX] = {
48193323Sed	{"html",	HTML_CLRLINE}, /* TAG_HTML */
49193323Sed	{"head",	HTML_CLRLINE}, /* TAG_HEAD */
50193323Sed	{"body",	HTML_CLRLINE}, /* TAG_BODY */
51193323Sed	{"meta",	HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_META */
52193323Sed	{"title",	HTML_CLRLINE}, /* TAG_TITLE */
53193323Sed	{"div",		HTML_CLRLINE}, /* TAG_DIV */
54193323Sed	{"h1",		0}, /* TAG_H1 */
55193323Sed	{"h2",		0}, /* TAG_H2 */
56193323Sed	{"span",	0}, /* TAG_SPAN */
57193323Sed	{"link",	HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_LINK */
58193323Sed	{"br",		HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_BR */
59193323Sed	{"a",		0}, /* TAG_A */
60193323Sed	{"table",	HTML_CLRLINE}, /* TAG_TABLE */
61193323Sed	{"tbody",	HTML_CLRLINE}, /* TAG_TBODY */
62193323Sed	{"col",		HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_COL */
63193323Sed	{"tr",		HTML_CLRLINE}, /* TAG_TR */
64193323Sed	{"td",		HTML_CLRLINE}, /* TAG_TD */
65193323Sed	{"li",		HTML_CLRLINE}, /* TAG_LI */
66193323Sed	{"ul",		HTML_CLRLINE}, /* TAG_UL */
67198090Srdivacky	{"ol",		HTML_CLRLINE}, /* TAG_OL */
68193323Sed	{"dl",		HTML_CLRLINE}, /* TAG_DL */
69193323Sed	{"dt",		HTML_CLRLINE}, /* TAG_DT */
70193323Sed	{"dd",		HTML_CLRLINE}, /* TAG_DD */
71193323Sed	{"blockquote",	HTML_CLRLINE}, /* TAG_BLOCKQUOTE */
72193323Sed	{"p",		HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_P */
73193323Sed	{"pre",		HTML_CLRLINE }, /* TAG_PRE */
74193323Sed	{"b",		0 }, /* TAG_B */
75193323Sed	{"i",		0 }, /* TAG_I */
76193323Sed	{"code",	0 }, /* TAG_CODE */
77198090Srdivacky	{"small",	0 }, /* TAG_SMALL */
78198090Srdivacky};
79193323Sed
80193323Sedstatic	const char	*const htmlattrs[ATTR_MAX] = {
81193323Sed	"http-equiv", /* ATTR_HTTPEQUIV */
82193323Sed	"content", /* ATTR_CONTENT */
83193323Sed	"name", /* ATTR_NAME */
84193323Sed	"rel", /* ATTR_REL */
85193323Sed	"href", /* ATTR_HREF */
86193323Sed	"type", /* ATTR_TYPE */
87193323Sed	"media", /* ATTR_MEDIA */
88193323Sed	"class", /* ATTR_CLASS */
89193323Sed	"style", /* ATTR_STYLE */
90193323Sed	"width", /* ATTR_WIDTH */
91193323Sed	"id", /* ATTR_ID */
92193323Sed	"summary", /* ATTR_SUMMARY */
93193323Sed	"align", /* ATTR_ALIGN */
94193323Sed	"colspan", /* ATTR_COLSPAN */
95193323Sed};
96193323Sed
97193323Sedstatic	const char	*const roffscales[SCALE_MAX] = {
98193323Sed	"cm", /* SCALE_CM */
99198090Srdivacky	"in", /* SCALE_IN */
100193323Sed	"pc", /* SCALE_PC */
101193323Sed	"pt", /* SCALE_PT */
102193323Sed	"em", /* SCALE_EM */
103193323Sed	"em", /* SCALE_MM */
104193323Sed	"ex", /* SCALE_EN */
105193323Sed	"ex", /* SCALE_BU */
106193323Sed	"em", /* SCALE_VS */
107193323Sed	"ex", /* SCALE_FS */
108193323Sed};
109193323Sed
110193323Sedstatic	void	 bufncat(struct html *, const char *, size_t);
111193323Sedstatic	void	 print_ctag(struct html *, enum htmltag);
112198090Srdivackystatic	int	 print_encode(struct html *, const char *, int);
113198090Srdivackystatic	void	 print_metaf(struct html *, enum mandoc_esc);
114198090Srdivackystatic	void	 print_attr(struct html *, const char *, const char *);
115193323Sedstatic	void	 *ml_alloc(char *, enum htmltype);
116193323Sed
117193323Sedstatic void *
118193323Sedml_alloc(char *outopts, enum htmltype type)
119193323Sed{
120193323Sed	struct html	*h;
121193323Sed	const char	*toks[5];
122193323Sed	char		*v;
123193323Sed
124193323Sed	toks[0] = "style";
125193323Sed	toks[1] = "man";
126193323Sed	toks[2] = "includes";
127193323Sed	toks[3] = "fragment";
128193323Sed	toks[4] = NULL;
129193323Sed
130193323Sed	h = mandoc_calloc(1, sizeof(struct html));
131193323Sed
132193323Sed	h->type = type;
133193323Sed	h->tags.head = NULL;
134193323Sed	h->symtab = mchars_alloc();
135193323Sed
136193323Sed	while (outopts && *outopts)
137193323Sed		switch (getsubopt(&outopts, UNCONST(toks), &v)) {
138193323Sed		case (0):
139193323Sed			h->style = v;
140193323Sed			break;
141193323Sed		case (1):
142193323Sed			h->base_man = v;
143193323Sed			break;
144193323Sed		case (2):
145193323Sed			h->base_includes = v;
146193323Sed			break;
147193323Sed		case (3):
148193323Sed			h->oflags |= HTML_FRAGMENT;
149193323Sed			break;
150193323Sed		default:
151193323Sed			break;
152193323Sed		}
153193323Sed
154193323Sed	return(h);
155193323Sed}
156193323Sed
157193323Sedvoid *
158193323Sedhtml_alloc(char *outopts)
159193323Sed{
160193323Sed
161193323Sed	return(ml_alloc(outopts, HTML_HTML_4_01_STRICT));
162193323Sed}
163195098Sed
164193323Sed
165193323Sedvoid *
166193323Sedxhtml_alloc(char *outopts)
167193323Sed{
168193323Sed
169193323Sed	return(ml_alloc(outopts, HTML_XHTML_1_0_STRICT));
170193323Sed}
171193323Sed
172193323Sed
173193323Sedvoid
174193323Sedhtml_free(void *p)
175193323Sed{
176193323Sed	struct tag	*tag;
177193323Sed	struct html	*h;
178193323Sed
179193323Sed	h = (struct html *)p;
180193323Sed
181193323Sed	while ((tag = h->tags.head) != NULL) {
182193323Sed		h->tags.head = tag->next;
183193323Sed		free(tag);
184193323Sed	}
185193323Sed
186193323Sed	if (h->symtab)
187193323Sed		mchars_free(h->symtab);
188193323Sed
189198090Srdivacky	free(h);
190198090Srdivacky}
191199989Srdivacky
192198090Srdivacky
193193323Sedvoid
194193323Sedprint_gen_head(struct html *h)
195198090Srdivacky{
196193323Sed	struct htmlpair	 tag[4];
197193323Sed
198198090Srdivacky	tag[0].key = ATTR_HTTPEQUIV;
199193323Sed	tag[0].val = "Content-Type";
200193323Sed	tag[1].key = ATTR_CONTENT;
201198090Srdivacky	tag[1].val = "text/html; charset=utf-8";
202193323Sed	print_otag(h, TAG_META, 2, tag);
203193323Sed
204193323Sed	tag[0].key = ATTR_NAME;
205193323Sed	tag[0].val = "resource-type";
206193323Sed	tag[1].key = ATTR_CONTENT;
207193323Sed	tag[1].val = "document";
208193323Sed	print_otag(h, TAG_META, 2, tag);
209193323Sed
210193323Sed	if (h->style) {
211193323Sed		tag[0].key = ATTR_REL;
212193323Sed		tag[0].val = "stylesheet";
213193323Sed		tag[1].key = ATTR_HREF;
214193323Sed		tag[1].val = h->style;
215193323Sed		tag[2].key = ATTR_TYPE;
216193323Sed		tag[2].val = "text/css";
217193323Sed		tag[3].key = ATTR_MEDIA;
218193323Sed		tag[3].val = "all";
219193323Sed		print_otag(h, TAG_LINK, 4, tag);
220193323Sed	}
221193323Sed}
222193323Sed
223193323Sedstatic void
224193323Sedprint_metaf(struct html *h, enum mandoc_esc deco)
225193323Sed{
226193323Sed	enum htmlfont	 font;
227193323Sed
228198090Srdivacky	switch (deco) {
229193323Sed	case (ESCAPE_FONTPREV):
230193323Sed		font = h->metal;
231193323Sed		break;
232193323Sed	case (ESCAPE_FONTITALIC):
233193323Sed		font = HTMLFONT_ITALIC;
234193323Sed		break;
235193323Sed	case (ESCAPE_FONTBOLD):
236193323Sed		font = HTMLFONT_BOLD;
237193323Sed		break;
238198090Srdivacky	case (ESCAPE_FONT):
239193323Sed		/* FALLTHROUGH */
240193323Sed	case (ESCAPE_FONTROMAN):
241193323Sed		font = HTMLFONT_NONE;
242193323Sed		break;
243193323Sed	default:
244193323Sed		abort();
245193323Sed		/* NOTREACHED */
246193323Sed	}
247193323Sed
248193323Sed	if (h->metaf) {
249193323Sed		print_tagq(h, h->metaf);
250193323Sed		h->metaf = NULL;
251193323Sed	}
252193323Sed
253193323Sed	h->metal = h->metac;
254193323Sed	h->metac = font;
255193323Sed
256193323Sed	if (HTMLFONT_NONE != font)
257193323Sed		h->metaf = HTMLFONT_BOLD == font ?
258193323Sed			print_otag(h, TAG_B, 0, NULL) :
259193323Sed			print_otag(h, TAG_I, 0, NULL);
260205218Srdivacky}
261193323Sed
262193323Sedint
263193323Sedhtml_strlen(const char *cp)
264193323Sed{
265193323Sed	int		 ssz, sz;
266193323Sed	const char	*seq, *p;
267193323Sed
268193323Sed	/*
269193323Sed	 * Account for escaped sequences within string length
270193323Sed	 * calculations.  This follows the logic in term_strlen() as we
271193323Sed	 * must calculate the width of produced strings.
272193323Sed	 * Assume that characters are always width of "1".  This is
273193323Sed	 * hacky, but it gets the job done for approximation of widths.
274193323Sed	 */
275193323Sed
276193323Sed	sz = 0;
277193323Sed	while (NULL != (p = strchr(cp, '\\'))) {
278193323Sed		sz += (int)(p - cp);
279193323Sed		++cp;
280193323Sed		switch (mandoc_escape(&cp, &seq, &ssz)) {
281193323Sed		case (ESCAPE_ERROR):
282193323Sed			return(sz);
283193323Sed		case (ESCAPE_UNICODE):
284193323Sed			/* FALLTHROUGH */
285193323Sed		case (ESCAPE_NUMBERED):
286193323Sed			/* FALLTHROUGH */
287193323Sed		case (ESCAPE_SPECIAL):
288193323Sed			sz++;
289193323Sed			break;
290193323Sed		default:
291193323Sed			break;
292193323Sed		}
293193323Sed	}
294193323Sed
295193323Sed	assert(sz >= 0);
296193323Sed	return(sz + strlen(cp));
297193323Sed}
298193323Sed
299193323Sedstatic int
300193323Sedprint_encode(struct html *h, const char *p, int norecurse)
301193323Sed{
302193323Sed	size_t		 sz;
303193323Sed	int		 c, len, nospace;
304193323Sed	const char	*seq;
305193323Sed	enum mandoc_esc	 esc;
306193323Sed	static const char rejs[6] = { '\\', '<', '>', '&', ASCII_HYPH, '\0' };
307193323Sed
308193323Sed	nospace = 0;
309193323Sed
310193323Sed	while ('\0' != *p) {
311193323Sed		sz = strcspn(p, rejs);
312193323Sed
313193323Sed		fwrite(p, 1, sz, stdout);
314193323Sed		p += (int)sz;
315193323Sed
316193323Sed		if ('\0' == *p)
317193323Sed			break;
318193323Sed
319193323Sed		switch (*p++) {
320193323Sed		case ('<'):
321193323Sed			printf("&lt;");
322193323Sed			continue;
323193323Sed		case ('>'):
324193323Sed			printf("&gt;");
325193323Sed			continue;
326193323Sed		case ('&'):
327193323Sed			printf("&amp;");
328193323Sed			continue;
329193323Sed		case (ASCII_HYPH):
330193323Sed			putchar('-');
331193323Sed			continue;
332193323Sed		default:
333193323Sed			break;
334193323Sed		}
335193323Sed
336193323Sed		esc = mandoc_escape(&p, &seq, &len);
337193323Sed		if (ESCAPE_ERROR == esc)
338193323Sed			break;
339193323Sed
340193323Sed		switch (esc) {
341193323Sed		case (ESCAPE_UNICODE):
342193323Sed			/* Skip passed "u" header. */
343193323Sed			c = mchars_num2uc(seq + 1, len - 1);
344193323Sed			if ('\0' != c)
345193323Sed				printf("&#x%x;", c);
346193323Sed			break;
347193323Sed		case (ESCAPE_NUMBERED):
348193323Sed			c = mchars_num2char(seq, len);
349193323Sed			if ('\0' != c)
350193323Sed				putchar(c);
351193323Sed			break;
352193323Sed		case (ESCAPE_SPECIAL):
353193323Sed			c = mchars_spec2cp(h->symtab, seq, len);
354193323Sed			if (c > 0)
355193323Sed				printf("&#%d;", c);
356193323Sed			else if (-1 == c && 1 == len)
357193323Sed				putchar((int)*seq);
358193323Sed			break;
359193323Sed		case (ESCAPE_FONT):
360193323Sed			/* FALLTHROUGH */
361193323Sed		case (ESCAPE_FONTPREV):
362193323Sed			/* FALLTHROUGH */
363193323Sed		case (ESCAPE_FONTBOLD):
364193323Sed			/* FALLTHROUGH */
365193323Sed		case (ESCAPE_FONTITALIC):
366200581Srdivacky			/* FALLTHROUGH */
367193323Sed		case (ESCAPE_FONTROMAN):
368193323Sed			if (norecurse)
369193323Sed				break;
370193323Sed			print_metaf(h, esc);
371193323Sed			break;
372193323Sed		case (ESCAPE_NOSPACE):
373193323Sed			if ('\0' == *p)
374193323Sed				nospace = 1;
375193323Sed			break;
376193323Sed		default:
377193323Sed			break;
378193323Sed		}
379193323Sed	}
380193323Sed
381193323Sed	return(nospace);
382193323Sed}
383193323Sed
384193323Sed
385193323Sedstatic void
386193323Sedprint_attr(struct html *h, const char *key, const char *val)
387193323Sed{
388193323Sed	printf(" %s=\"", key);
389193323Sed	(void)print_encode(h, val, 1);
390193323Sed	putchar('\"');
391193323Sed}
392193323Sed
393193323Sed
394193323Sedstruct tag *
395193323Sedprint_otag(struct html *h, enum htmltag tag,
396193323Sed		int sz, const struct htmlpair *p)
397193323Sed{
398193323Sed	int		 i;
399193323Sed	struct tag	*t;
400193323Sed
401193323Sed	/* Push this tags onto the stack of open scopes. */
402193323Sed
403193323Sed	if ( ! (HTML_NOSTACK & htmltags[tag].flags)) {
404193323Sed		t = mandoc_malloc(sizeof(struct tag));
405193323Sed		t->tag = tag;
406193323Sed		t->next = h->tags.head;
407193323Sed		h->tags.head = t;
408193323Sed	} else
409193323Sed		t = NULL;
410205218Srdivacky
411193323Sed	if ( ! (HTML_NOSPACE & h->flags))
412193323Sed		if ( ! (HTML_CLRLINE & htmltags[tag].flags)) {
413193323Sed			/* Manage keeps! */
414193323Sed			if ( ! (HTML_KEEP & h->flags)) {
415193323Sed				if (HTML_PREKEEP & h->flags)
416193323Sed					h->flags |= HTML_KEEP;
417193323Sed				putchar(' ');
418193323Sed			} else
419193323Sed				printf("&#160;");
420193323Sed		}
421193323Sed
422193323Sed	if ( ! (h->flags & HTML_NONOSPACE))
423193323Sed		h->flags &= ~HTML_NOSPACE;
424193323Sed	else
425193323Sed		h->flags |= HTML_NOSPACE;
426193323Sed
427193323Sed	/* Print out the tag name and attributes. */
428193323Sed
429193323Sed	printf("<%s", htmltags[tag].name);
430193323Sed	for (i = 0; i < sz; i++)
431193323Sed		print_attr(h, htmlattrs[p[i].key], p[i].val);
432193323Sed
433193323Sed	/* Add non-overridable attributes. */
434205218Srdivacky
435193323Sed	if (TAG_HTML == tag && HTML_XHTML_1_0_STRICT == h->type) {
436193323Sed		print_attr(h, "xmlns", "http://www.w3.org/1999/xhtml");
437193323Sed		print_attr(h, "xml:lang", "en");
438193323Sed		print_attr(h, "lang", "en");
439193323Sed	}
440193323Sed
441193323Sed	/* Accommodate for XML "well-formed" singleton escaping. */
442193323Sed
443193323Sed	if (HTML_AUTOCLOSE & htmltags[tag].flags)
444193323Sed		switch (h->type) {
445193323Sed		case (HTML_XHTML_1_0_STRICT):
446193323Sed			putchar('/');
447193323Sed			break;
448193323Sed		default:
449193323Sed			break;
450193323Sed		}
451193323Sed
452193323Sed	putchar('>');
453193323Sed
454193323Sed	h->flags |= HTML_NOSPACE;
455193323Sed
456193323Sed	if ((HTML_AUTOCLOSE | HTML_CLRLINE) & htmltags[tag].flags)
457193323Sed		putchar('\n');
458193323Sed
459193323Sed	return(t);
460193323Sed}
461205218Srdivacky
462193323Sed
463193323Sedstatic void
464193323Sedprint_ctag(struct html *h, enum htmltag tag)
465193323Sed{
466193323Sed
467193323Sed	printf("</%s>", htmltags[tag].name);
468193323Sed	if (HTML_CLRLINE & htmltags[tag].flags) {
469193323Sed		h->flags |= HTML_NOSPACE;
470193323Sed		putchar('\n');
471193323Sed	}
472193323Sed}
473193323Sed
474193323Sedvoid
475193323Sedprint_gen_decls(struct html *h)
476193323Sed{
477193323Sed	const char	*doctype;
478193323Sed	const char	*dtd;
479193323Sed	const char	*name;
480193323Sed
481193323Sed	switch (h->type) {
482193323Sed	case (HTML_HTML_4_01_STRICT):
483193323Sed		name = "HTML";
484193323Sed		doctype = "-//W3C//DTD HTML 4.01//EN";
485193323Sed		dtd = "http://www.w3.org/TR/html4/strict.dtd";
486193323Sed		break;
487193323Sed	default:
488193323Sed		puts("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
489193323Sed		name = "html";
490193323Sed		doctype = "-//W3C//DTD XHTML 1.0 Strict//EN";
491193323Sed		dtd = "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd";
492193323Sed		break;
493193323Sed	}
494193323Sed
495193323Sed	printf("<!DOCTYPE %s PUBLIC \"%s\" \"%s\">\n",
496193323Sed			name, doctype, dtd);
497193323Sed}
498193323Sed
499193323Sedvoid
500193323Sedprint_text(struct html *h, const char *word)
501193323Sed{
502193323Sed
503193323Sed	if ( ! (HTML_NOSPACE & h->flags)) {
504193323Sed		/* Manage keeps! */
505193323Sed		if ( ! (HTML_KEEP & h->flags)) {
506193323Sed			if (HTML_PREKEEP & h->flags)
507193323Sed				h->flags |= HTML_KEEP;
508193323Sed			putchar(' ');
509193323Sed		} else
510193323Sed			printf("&#160;");
511193323Sed	}
512193323Sed
513193323Sed	assert(NULL == h->metaf);
514193323Sed	if (HTMLFONT_NONE != h->metac)
515193323Sed		h->metaf = HTMLFONT_BOLD == h->metac ?
516202375Srdivacky			print_otag(h, TAG_B, 0, NULL) :
517193323Sed			print_otag(h, TAG_I, 0, NULL);
518193323Sed
519193323Sed	assert(word);
520193323Sed	if ( ! print_encode(h, word, 0)) {
521193323Sed		if ( ! (h->flags & HTML_NONOSPACE))
522193323Sed			h->flags &= ~HTML_NOSPACE;
523193323Sed	} else
524193323Sed		h->flags |= HTML_NOSPACE;
525200581Srdivacky
526193323Sed	if (h->metaf) {
527193323Sed		print_tagq(h, h->metaf);
528198090Srdivacky		h->metaf = NULL;
529193323Sed	}
530193323Sed
531193323Sed	h->flags &= ~HTML_IGNDELIM;
532193323Sed}
533193323Sed
534193323Sed
535193323Sedvoid
536193323Sedprint_tagq(struct html *h, const struct tag *until)
537193323Sed{
538193323Sed	struct tag	*tag;
539193323Sed
540193323Sed	while ((tag = h->tags.head) != NULL) {
541193323Sed		/*
542193323Sed		 * Remember to close out and nullify the current
543193323Sed		 * meta-font and table, if applicable.
544198090Srdivacky		 */
545198090Srdivacky		if (tag == h->metaf)
546193323Sed			h->metaf = NULL;
547193323Sed		if (tag == h->tblt)
548193323Sed			h->tblt = NULL;
549193323Sed		print_ctag(h, tag->tag);
550193323Sed		h->tags.head = tag->next;
551193323Sed		free(tag);
552193323Sed		if (until && tag == until)
553193323Sed			return;
554193323Sed	}
555193323Sed}
556193323Sed
557193323Sed
558202375Srdivackyvoid
559193323Sedprint_stagq(struct html *h, const struct tag *suntil)
560193323Sed{
561193323Sed	struct tag	*tag;
562193323Sed
563193323Sed	while ((tag = h->tags.head) != NULL) {
564193323Sed		if (suntil && tag == suntil)
565193323Sed			return;
566193323Sed		/*
567193323Sed		 * Remember to close out and nullify the current
568193323Sed		 * meta-font and table, if applicable.
569193323Sed		 */
570200581Srdivacky		if (tag == h->metaf)
571198090Srdivacky			h->metaf = NULL;
572193323Sed		if (tag == h->tblt)
573198090Srdivacky			h->tblt = NULL;
574193323Sed		print_ctag(h, tag->tag);
575193323Sed		h->tags.head = tag->next;
576193323Sed		free(tag);
577193323Sed	}
578193323Sed}
579193323Sed
580193323Sedvoid
581193323Sedbufinit(struct html *h)
582193323Sed{
583193323Sed
584193323Sed	h->buf[0] = '\0';
585193323Sed	h->buflen = 0;
586193323Sed}
587193323Sed
588193323Sedvoid
589193323Sedbufcat_style(struct html *h, const char *key, const char *val)
590193323Sed{
591193323Sed
592193323Sed	bufcat(h, key);
593193323Sed	bufcat(h, ":");
594193323Sed	bufcat(h, val);
595193323Sed	bufcat(h, ";");
596193323Sed}
597193323Sed
598193323Sedvoid
599193323Sedbufcat(struct html *h, const char *p)
600193323Sed{
601193323Sed
602193323Sed	h->buflen = strlcat(h->buf, p, BUFSIZ);
603193323Sed	assert(h->buflen < BUFSIZ);
604193323Sed}
605193323Sed
606193323Sedvoid
607193323Sedbufcat_fmt(struct html *h, const char *fmt, ...)
608193323Sed{
609193323Sed	va_list		 ap;
610193323Sed
611193323Sed	va_start(ap, fmt);
612193323Sed	(void)vsnprintf(h->buf + (int)h->buflen,
613200581Srdivacky			BUFSIZ - h->buflen - 1, fmt, ap);
614193323Sed	va_end(ap);
615193323Sed	h->buflen = strlen(h->buf);
616203954Srdivacky}
617193323Sed
618193323Sedstatic void
619193323Sedbufncat(struct html *h, const char *p, size_t sz)
620193323Sed{
621193323Sed
622193323Sed	assert(h->buflen + sz + 1 < BUFSIZ);
623193323Sed	strncat(h->buf, p, sz);
624193323Sed	h->buflen += sz;
625202375Srdivacky}
626193323Sed
627193323Sedvoid
628193323Sedbuffmt_includes(struct html *h, const char *name)
629193323Sed{
630193323Sed	const char	*p, *pp;
631193323Sed
632198396Srdivacky	pp = h->base_includes;
633193323Sed
634193323Sed	bufinit(h);
635193323Sed	while (NULL != (p = strchr(pp, '%'))) {
636193323Sed		bufncat(h, pp, (size_t)(p - pp));
637193323Sed		switch (*(p + 1)) {
638193323Sed		case('I'):
639193323Sed			bufcat(h, name);
640193323Sed			break;
641203954Srdivacky		default:
642193323Sed			bufncat(h, p, 2);
643203954Srdivacky			break;
644203954Srdivacky		}
645203954Srdivacky		pp = p + 2;
646203954Srdivacky	}
647193323Sed	if (pp)
648203954Srdivacky		bufcat(h, pp);
649203954Srdivacky}
650203954Srdivacky
651203954Srdivackyvoid
652203954Srdivackybuffmt_man(struct html *h,
653203954Srdivacky		const char *name, const char *sec)
654203954Srdivacky{
655203954Srdivacky	const char	*p, *pp;
656193323Sed
657193323Sed	pp = h->base_man;
658193323Sed
659203954Srdivacky	bufinit(h);
660203954Srdivacky	while (NULL != (p = strchr(pp, '%'))) {
661193323Sed		bufncat(h, pp, (size_t)(p - pp));
662203954Srdivacky		switch (*(p + 1)) {
663203954Srdivacky		case('S'):
664203954Srdivacky			bufcat(h, sec ? sec : "1");
665203954Srdivacky			break;
666203954Srdivacky		case('N'):
667203954Srdivacky			bufcat_fmt(h, name);
668203954Srdivacky			break;
669193323Sed		default:
670193323Sed			bufncat(h, p, 2);
671198090Srdivacky			break;
672193323Sed		}
673193323Sed		pp = p + 2;
674193323Sed	}
675193323Sed	if (pp)
676193323Sed		bufcat(h, pp);
677193323Sed}
678193323Sed
679193323Sedvoid
680193323Sedbufcat_su(struct html *h, const char *p, const struct roffsu *su)
681193323Sed{
682193323Sed	double		 v;
683193323Sed
684193323Sed	v = su->scale;
685193323Sed	if (SCALE_MM == su->unit && 0.0 == (v /= 100.0))
686193323Sed		v = 1.0;
687193323Sed
688193323Sed	bufcat_fmt(h, "%s: %.2f%s;", p, v, roffscales[su->unit]);
689193323Sed}
690193323Sed
691193323Sedvoid
692193323Sedbufcat_id(struct html *h, const char *src)
693198090Srdivacky{
694193323Sed
695193323Sed	/* Cf. <http://www.w3.org/TR/html4/types.html#h-6.2>. */
696193323Sed
697193323Sed	while ('\0' != *src)
698193323Sed		bufcat_fmt(h, "%.2x", *src++);
699193323Sed}
700193323Sed