xref: /minix3/external/bsd/mdocml/dist/html.c (revision 0a6a1f1d05b60e214de2f05a7310ddd1f0e590e7)
1*0a6a1f1dSLionel Sambuc /*	Id: html.c,v 1.153 2014/01/05 19:10:56 joerg Exp  */
2d65f6f70SBen Gras /*
392395e9cSLionel Sambuc  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4*0a6a1f1dSLionel Sambuc  * Copyright (c) 2011, 2012, 2013 Ingo Schwarze <schwarze@openbsd.org>
5d65f6f70SBen Gras  *
6d65f6f70SBen Gras  * Permission to use, copy, modify, and distribute this software for any
7d65f6f70SBen Gras  * purpose with or without fee is hereby granted, provided that the above
8d65f6f70SBen Gras  * copyright notice and this permission notice appear in all copies.
9d65f6f70SBen Gras  *
10d65f6f70SBen Gras  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11d65f6f70SBen Gras  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12d65f6f70SBen Gras  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13d65f6f70SBen Gras  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14d65f6f70SBen Gras  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15d65f6f70SBen Gras  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16d65f6f70SBen Gras  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17d65f6f70SBen Gras  */
18d65f6f70SBen Gras #ifdef HAVE_CONFIG_H
19d65f6f70SBen Gras #include "config.h"
20d65f6f70SBen Gras #endif
21d65f6f70SBen Gras 
22d65f6f70SBen Gras #include <sys/types.h>
23d65f6f70SBen Gras 
24d65f6f70SBen Gras #include <assert.h>
25d65f6f70SBen Gras #include <ctype.h>
26d65f6f70SBen Gras #include <stdarg.h>
27d65f6f70SBen Gras #include <stdio.h>
28d65f6f70SBen Gras #include <stdint.h>
29d65f6f70SBen Gras #include <stdlib.h>
30d65f6f70SBen Gras #include <string.h>
31d65f6f70SBen Gras #include <unistd.h>
32d65f6f70SBen Gras 
33d65f6f70SBen Gras #include "mandoc.h"
3492395e9cSLionel Sambuc #include "libmandoc.h"
35d65f6f70SBen Gras #include "out.h"
36d65f6f70SBen Gras #include "html.h"
37d65f6f70SBen Gras #include "main.h"
38d65f6f70SBen Gras 
39d65f6f70SBen Gras struct	htmldata {
40d65f6f70SBen Gras 	const char	 *name;
41d65f6f70SBen Gras 	int		  flags;
42d65f6f70SBen Gras #define	HTML_CLRLINE	 (1 << 0)
43d65f6f70SBen Gras #define	HTML_NOSTACK	 (1 << 1)
44d65f6f70SBen Gras #define	HTML_AUTOCLOSE	 (1 << 2) /* Tag has auto-closure. */
45d65f6f70SBen Gras };
46d65f6f70SBen Gras 
47d65f6f70SBen Gras static	const struct htmldata htmltags[TAG_MAX] = {
48d65f6f70SBen Gras 	{"html",	HTML_CLRLINE}, /* TAG_HTML */
49d65f6f70SBen Gras 	{"head",	HTML_CLRLINE}, /* TAG_HEAD */
50d65f6f70SBen Gras 	{"body",	HTML_CLRLINE}, /* TAG_BODY */
51d65f6f70SBen Gras 	{"meta",	HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_META */
52d65f6f70SBen Gras 	{"title",	HTML_CLRLINE}, /* TAG_TITLE */
53d65f6f70SBen Gras 	{"div",		HTML_CLRLINE}, /* TAG_DIV */
54d65f6f70SBen Gras 	{"h1",		0}, /* TAG_H1 */
55d65f6f70SBen Gras 	{"h2",		0}, /* TAG_H2 */
56d65f6f70SBen Gras 	{"span",	0}, /* TAG_SPAN */
57d65f6f70SBen Gras 	{"link",	HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_LINK */
58d65f6f70SBen Gras 	{"br",		HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_BR */
59d65f6f70SBen Gras 	{"a",		0}, /* TAG_A */
60d65f6f70SBen Gras 	{"table",	HTML_CLRLINE}, /* TAG_TABLE */
61d65f6f70SBen Gras 	{"tbody",	HTML_CLRLINE}, /* TAG_TBODY */
62d65f6f70SBen Gras 	{"col",		HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_COL */
63d65f6f70SBen Gras 	{"tr",		HTML_CLRLINE}, /* TAG_TR */
64d65f6f70SBen Gras 	{"td",		HTML_CLRLINE}, /* TAG_TD */
65d65f6f70SBen Gras 	{"li",		HTML_CLRLINE}, /* TAG_LI */
66d65f6f70SBen Gras 	{"ul",		HTML_CLRLINE}, /* TAG_UL */
67d65f6f70SBen Gras 	{"ol",		HTML_CLRLINE}, /* TAG_OL */
68d65f6f70SBen Gras 	{"dl",		HTML_CLRLINE}, /* TAG_DL */
69d65f6f70SBen Gras 	{"dt",		HTML_CLRLINE}, /* TAG_DT */
70d65f6f70SBen Gras 	{"dd",		HTML_CLRLINE}, /* TAG_DD */
71d65f6f70SBen Gras 	{"blockquote",	HTML_CLRLINE}, /* TAG_BLOCKQUOTE */
72d65f6f70SBen Gras 	{"p",		HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_P */
73d65f6f70SBen Gras 	{"pre",		HTML_CLRLINE }, /* TAG_PRE */
74d65f6f70SBen Gras 	{"b",		0 }, /* TAG_B */
75d65f6f70SBen Gras 	{"i",		0 }, /* TAG_I */
76d65f6f70SBen Gras 	{"code",	0 }, /* TAG_CODE */
77d65f6f70SBen Gras 	{"small",	0 }, /* TAG_SMALL */
78d65f6f70SBen Gras };
79d65f6f70SBen Gras 
80d65f6f70SBen Gras static	const char	*const htmlattrs[ATTR_MAX] = {
81d65f6f70SBen Gras 	"http-equiv", /* ATTR_HTTPEQUIV */
82d65f6f70SBen Gras 	"content", /* ATTR_CONTENT */
83d65f6f70SBen Gras 	"name", /* ATTR_NAME */
84d65f6f70SBen Gras 	"rel", /* ATTR_REL */
85d65f6f70SBen Gras 	"href", /* ATTR_HREF */
86d65f6f70SBen Gras 	"type", /* ATTR_TYPE */
87d65f6f70SBen Gras 	"media", /* ATTR_MEDIA */
88d65f6f70SBen Gras 	"class", /* ATTR_CLASS */
89d65f6f70SBen Gras 	"style", /* ATTR_STYLE */
90d65f6f70SBen Gras 	"width", /* ATTR_WIDTH */
91d65f6f70SBen Gras 	"id", /* ATTR_ID */
92d65f6f70SBen Gras 	"summary", /* ATTR_SUMMARY */
93d65f6f70SBen Gras 	"align", /* ATTR_ALIGN */
9492395e9cSLionel Sambuc 	"colspan", /* ATTR_COLSPAN */
95d65f6f70SBen Gras };
96d65f6f70SBen Gras 
9792395e9cSLionel Sambuc static	const char	*const roffscales[SCALE_MAX] = {
9892395e9cSLionel Sambuc 	"cm", /* SCALE_CM */
9992395e9cSLionel Sambuc 	"in", /* SCALE_IN */
10092395e9cSLionel Sambuc 	"pc", /* SCALE_PC */
10192395e9cSLionel Sambuc 	"pt", /* SCALE_PT */
10292395e9cSLionel Sambuc 	"em", /* SCALE_EM */
10392395e9cSLionel Sambuc 	"em", /* SCALE_MM */
10492395e9cSLionel Sambuc 	"ex", /* SCALE_EN */
10592395e9cSLionel Sambuc 	"ex", /* SCALE_BU */
10692395e9cSLionel Sambuc 	"em", /* SCALE_VS */
10792395e9cSLionel Sambuc 	"ex", /* SCALE_FS */
10892395e9cSLionel Sambuc };
109d65f6f70SBen Gras 
11092395e9cSLionel Sambuc static	void	 bufncat(struct html *, const char *, size_t);
11192395e9cSLionel Sambuc static	void	 print_ctag(struct html *, enum htmltag);
11292395e9cSLionel Sambuc static	int	 print_encode(struct html *, const char *, int);
11392395e9cSLionel Sambuc static	void	 print_metaf(struct html *, enum mandoc_esc);
11492395e9cSLionel Sambuc static	void	 print_attr(struct html *, const char *, const char *);
11592395e9cSLionel Sambuc static	void	 *ml_alloc(char *, enum htmltype);
116d65f6f70SBen Gras 
117d65f6f70SBen Gras static void *
ml_alloc(char * outopts,enum htmltype type)118d65f6f70SBen Gras ml_alloc(char *outopts, enum htmltype type)
119d65f6f70SBen Gras {
120d65f6f70SBen Gras 	struct html	*h;
12192395e9cSLionel Sambuc 	const char	*toks[5];
122d65f6f70SBen Gras 	char		*v;
123d65f6f70SBen Gras 
124d65f6f70SBen Gras 	toks[0] = "style";
125d65f6f70SBen Gras 	toks[1] = "man";
126d65f6f70SBen Gras 	toks[2] = "includes";
12792395e9cSLionel Sambuc 	toks[3] = "fragment";
12892395e9cSLionel Sambuc 	toks[4] = NULL;
129d65f6f70SBen Gras 
13092395e9cSLionel Sambuc 	h = mandoc_calloc(1, sizeof(struct html));
131d65f6f70SBen Gras 
132d65f6f70SBen Gras 	h->type = type;
133d65f6f70SBen Gras 	h->tags.head = NULL;
13492395e9cSLionel Sambuc 	h->symtab = mchars_alloc();
135d65f6f70SBen Gras 
136d65f6f70SBen Gras 	while (outopts && *outopts)
137d65f6f70SBen Gras 		switch (getsubopt(&outopts, UNCONST(toks), &v)) {
138d65f6f70SBen Gras 		case (0):
139d65f6f70SBen Gras 			h->style = v;
140d65f6f70SBen Gras 			break;
141d65f6f70SBen Gras 		case (1):
142d65f6f70SBen Gras 			h->base_man = v;
143d65f6f70SBen Gras 			break;
144d65f6f70SBen Gras 		case (2):
145d65f6f70SBen Gras 			h->base_includes = v;
146d65f6f70SBen Gras 			break;
14792395e9cSLionel Sambuc 		case (3):
14892395e9cSLionel Sambuc 			h->oflags |= HTML_FRAGMENT;
14992395e9cSLionel Sambuc 			break;
150d65f6f70SBen Gras 		default:
151d65f6f70SBen Gras 			break;
152d65f6f70SBen Gras 		}
153d65f6f70SBen Gras 
154d65f6f70SBen Gras 	return(h);
155d65f6f70SBen Gras }
156d65f6f70SBen Gras 
157d65f6f70SBen Gras void *
html_alloc(char * outopts)158d65f6f70SBen Gras html_alloc(char *outopts)
159d65f6f70SBen Gras {
160d65f6f70SBen Gras 
161d65f6f70SBen Gras 	return(ml_alloc(outopts, HTML_HTML_4_01_STRICT));
162d65f6f70SBen Gras }
163d65f6f70SBen Gras 
164d65f6f70SBen Gras 
165d65f6f70SBen Gras void *
xhtml_alloc(char * outopts)166d65f6f70SBen Gras xhtml_alloc(char *outopts)
167d65f6f70SBen Gras {
168d65f6f70SBen Gras 
169d65f6f70SBen Gras 	return(ml_alloc(outopts, HTML_XHTML_1_0_STRICT));
170d65f6f70SBen Gras }
171d65f6f70SBen Gras 
172d65f6f70SBen Gras 
173d65f6f70SBen Gras void
html_free(void * p)174d65f6f70SBen Gras html_free(void *p)
175d65f6f70SBen Gras {
176d65f6f70SBen Gras 	struct tag	*tag;
177d65f6f70SBen Gras 	struct html	*h;
178d65f6f70SBen Gras 
179d65f6f70SBen Gras 	h = (struct html *)p;
180d65f6f70SBen Gras 
181d65f6f70SBen Gras 	while ((tag = h->tags.head) != NULL) {
182d65f6f70SBen Gras 		h->tags.head = tag->next;
183d65f6f70SBen Gras 		free(tag);
184d65f6f70SBen Gras 	}
185d65f6f70SBen Gras 
186d65f6f70SBen Gras 	if (h->symtab)
18792395e9cSLionel Sambuc 		mchars_free(h->symtab);
188d65f6f70SBen Gras 
189d65f6f70SBen Gras 	free(h);
190d65f6f70SBen Gras }
191d65f6f70SBen Gras 
192d65f6f70SBen Gras 
193d65f6f70SBen Gras void
print_gen_head(struct html * h)194d65f6f70SBen Gras print_gen_head(struct html *h)
195d65f6f70SBen Gras {
196d65f6f70SBen Gras 	struct htmlpair	 tag[4];
197d65f6f70SBen Gras 
198d65f6f70SBen Gras 	tag[0].key = ATTR_HTTPEQUIV;
199d65f6f70SBen Gras 	tag[0].val = "Content-Type";
200d65f6f70SBen Gras 	tag[1].key = ATTR_CONTENT;
201d65f6f70SBen Gras 	tag[1].val = "text/html; charset=utf-8";
202d65f6f70SBen Gras 	print_otag(h, TAG_META, 2, tag);
203d65f6f70SBen Gras 
204d65f6f70SBen Gras 	tag[0].key = ATTR_NAME;
205d65f6f70SBen Gras 	tag[0].val = "resource-type";
206d65f6f70SBen Gras 	tag[1].key = ATTR_CONTENT;
207d65f6f70SBen Gras 	tag[1].val = "document";
208d65f6f70SBen Gras 	print_otag(h, TAG_META, 2, tag);
209d65f6f70SBen Gras 
210d65f6f70SBen Gras 	if (h->style) {
211d65f6f70SBen Gras 		tag[0].key = ATTR_REL;
212d65f6f70SBen Gras 		tag[0].val = "stylesheet";
213d65f6f70SBen Gras 		tag[1].key = ATTR_HREF;
214d65f6f70SBen Gras 		tag[1].val = h->style;
215d65f6f70SBen Gras 		tag[2].key = ATTR_TYPE;
216d65f6f70SBen Gras 		tag[2].val = "text/css";
217d65f6f70SBen Gras 		tag[3].key = ATTR_MEDIA;
218d65f6f70SBen Gras 		tag[3].val = "all";
219d65f6f70SBen Gras 		print_otag(h, TAG_LINK, 4, tag);
220d65f6f70SBen Gras 	}
221d65f6f70SBen Gras }
222d65f6f70SBen Gras 
223d65f6f70SBen Gras static void
print_metaf(struct html * h,enum mandoc_esc deco)22492395e9cSLionel Sambuc print_metaf(struct html *h, enum mandoc_esc deco)
225d65f6f70SBen Gras {
226d65f6f70SBen Gras 	enum htmlfont	 font;
227d65f6f70SBen Gras 
228d65f6f70SBen Gras 	switch (deco) {
22992395e9cSLionel Sambuc 	case (ESCAPE_FONTPREV):
230d65f6f70SBen Gras 		font = h->metal;
231d65f6f70SBen Gras 		break;
23292395e9cSLionel Sambuc 	case (ESCAPE_FONTITALIC):
233d65f6f70SBen Gras 		font = HTMLFONT_ITALIC;
234d65f6f70SBen Gras 		break;
23592395e9cSLionel Sambuc 	case (ESCAPE_FONTBOLD):
236d65f6f70SBen Gras 		font = HTMLFONT_BOLD;
237d65f6f70SBen Gras 		break;
238*0a6a1f1dSLionel Sambuc 	case (ESCAPE_FONTBI):
239*0a6a1f1dSLionel Sambuc 		font = HTMLFONT_BI;
240*0a6a1f1dSLionel Sambuc 		break;
24192395e9cSLionel Sambuc 	case (ESCAPE_FONT):
24292395e9cSLionel Sambuc 		/* FALLTHROUGH */
24392395e9cSLionel Sambuc 	case (ESCAPE_FONTROMAN):
244d65f6f70SBen Gras 		font = HTMLFONT_NONE;
245d65f6f70SBen Gras 		break;
246d65f6f70SBen Gras 	default:
247d65f6f70SBen Gras 		abort();
248d65f6f70SBen Gras 		/* NOTREACHED */
249d65f6f70SBen Gras 	}
250d65f6f70SBen Gras 
251d65f6f70SBen Gras 	if (h->metaf) {
252d65f6f70SBen Gras 		print_tagq(h, h->metaf);
253d65f6f70SBen Gras 		h->metaf = NULL;
254d65f6f70SBen Gras 	}
255d65f6f70SBen Gras 
256d65f6f70SBen Gras 	h->metal = h->metac;
257d65f6f70SBen Gras 	h->metac = font;
258d65f6f70SBen Gras 
259*0a6a1f1dSLionel Sambuc 	switch (font) {
260*0a6a1f1dSLionel Sambuc 	case (HTMLFONT_ITALIC):
261*0a6a1f1dSLionel Sambuc 		h->metaf = print_otag(h, TAG_I, 0, NULL);
262*0a6a1f1dSLionel Sambuc 		break;
263*0a6a1f1dSLionel Sambuc 	case (HTMLFONT_BOLD):
264*0a6a1f1dSLionel Sambuc 		h->metaf = print_otag(h, TAG_B, 0, NULL);
265*0a6a1f1dSLionel Sambuc 		break;
266*0a6a1f1dSLionel Sambuc 	case (HTMLFONT_BI):
267*0a6a1f1dSLionel Sambuc 		h->metaf = print_otag(h, TAG_B, 0, NULL);
268d65f6f70SBen Gras 		print_otag(h, TAG_I, 0, NULL);
269*0a6a1f1dSLionel Sambuc 		break;
270*0a6a1f1dSLionel Sambuc 	default:
271*0a6a1f1dSLionel Sambuc 		break;
272*0a6a1f1dSLionel Sambuc 	}
273d65f6f70SBen Gras }
274d65f6f70SBen Gras 
27592395e9cSLionel Sambuc int
html_strlen(const char * cp)27692395e9cSLionel Sambuc html_strlen(const char *cp)
27792395e9cSLionel Sambuc {
278*0a6a1f1dSLionel Sambuc 	size_t		 rsz;
279*0a6a1f1dSLionel Sambuc 	int		 skip, sz;
28092395e9cSLionel Sambuc 
28192395e9cSLionel Sambuc 	/*
28292395e9cSLionel Sambuc 	 * Account for escaped sequences within string length
28392395e9cSLionel Sambuc 	 * calculations.  This follows the logic in term_strlen() as we
28492395e9cSLionel Sambuc 	 * must calculate the width of produced strings.
28592395e9cSLionel Sambuc 	 * Assume that characters are always width of "1".  This is
28692395e9cSLionel Sambuc 	 * hacky, but it gets the job done for approximation of widths.
28792395e9cSLionel Sambuc 	 */
28892395e9cSLionel Sambuc 
28992395e9cSLionel Sambuc 	sz = 0;
290*0a6a1f1dSLionel Sambuc 	skip = 0;
291*0a6a1f1dSLionel Sambuc 	while (1) {
292*0a6a1f1dSLionel Sambuc 		rsz = strcspn(cp, "\\");
293*0a6a1f1dSLionel Sambuc 		if (rsz) {
294*0a6a1f1dSLionel Sambuc 			cp += rsz;
295*0a6a1f1dSLionel Sambuc 			if (skip) {
296*0a6a1f1dSLionel Sambuc 				skip = 0;
297*0a6a1f1dSLionel Sambuc 				rsz--;
298*0a6a1f1dSLionel Sambuc 			}
299*0a6a1f1dSLionel Sambuc 			sz += rsz;
300*0a6a1f1dSLionel Sambuc 		}
301*0a6a1f1dSLionel Sambuc 		if ('\0' == *cp)
302*0a6a1f1dSLionel Sambuc 			break;
303*0a6a1f1dSLionel Sambuc 		cp++;
304*0a6a1f1dSLionel Sambuc 		switch (mandoc_escape(&cp, NULL, NULL)) {
30592395e9cSLionel Sambuc 		case (ESCAPE_ERROR):
30692395e9cSLionel Sambuc 			return(sz);
30792395e9cSLionel Sambuc 		case (ESCAPE_UNICODE):
30892395e9cSLionel Sambuc 			/* FALLTHROUGH */
30992395e9cSLionel Sambuc 		case (ESCAPE_NUMBERED):
31092395e9cSLionel Sambuc 			/* FALLTHROUGH */
31192395e9cSLionel Sambuc 		case (ESCAPE_SPECIAL):
312*0a6a1f1dSLionel Sambuc 			if (skip)
313*0a6a1f1dSLionel Sambuc 				skip = 0;
314*0a6a1f1dSLionel Sambuc 			else
31592395e9cSLionel Sambuc 				sz++;
31692395e9cSLionel Sambuc 			break;
317*0a6a1f1dSLionel Sambuc 		case (ESCAPE_SKIPCHAR):
318*0a6a1f1dSLionel Sambuc 			skip = 1;
319*0a6a1f1dSLionel Sambuc 			break;
32092395e9cSLionel Sambuc 		default:
32192395e9cSLionel Sambuc 			break;
32292395e9cSLionel Sambuc 		}
32392395e9cSLionel Sambuc 	}
324*0a6a1f1dSLionel Sambuc 	return(sz);
32592395e9cSLionel Sambuc }
326d65f6f70SBen Gras 
327d65f6f70SBen Gras static int
print_encode(struct html * h,const char * p,int norecurse)328d65f6f70SBen Gras print_encode(struct html *h, const char *p, int norecurse)
329d65f6f70SBen Gras {
330d65f6f70SBen Gras 	size_t		 sz;
33192395e9cSLionel Sambuc 	int		 c, len, nospace;
332d65f6f70SBen Gras 	const char	*seq;
33392395e9cSLionel Sambuc 	enum mandoc_esc	 esc;
334d65f6f70SBen Gras 	static const char rejs[6] = { '\\', '<', '>', '&', ASCII_HYPH, '\0' };
335d65f6f70SBen Gras 
336d65f6f70SBen Gras 	nospace = 0;
337d65f6f70SBen Gras 
33892395e9cSLionel Sambuc 	while ('\0' != *p) {
339*0a6a1f1dSLionel Sambuc 		if (HTML_SKIPCHAR & h->flags && '\\' != *p) {
340*0a6a1f1dSLionel Sambuc 			h->flags &= ~HTML_SKIPCHAR;
341*0a6a1f1dSLionel Sambuc 			p++;
342*0a6a1f1dSLionel Sambuc 			continue;
343*0a6a1f1dSLionel Sambuc 		}
344*0a6a1f1dSLionel Sambuc 
345d65f6f70SBen Gras 		sz = strcspn(p, rejs);
346d65f6f70SBen Gras 
347d65f6f70SBen Gras 		fwrite(p, 1, sz, stdout);
34892395e9cSLionel Sambuc 		p += (int)sz;
349d65f6f70SBen Gras 
35092395e9cSLionel Sambuc 		if ('\0' == *p)
35192395e9cSLionel Sambuc 			break;
35292395e9cSLionel Sambuc 
35392395e9cSLionel Sambuc 		switch (*p++) {
35492395e9cSLionel Sambuc 		case ('<'):
355d65f6f70SBen Gras 			printf("&lt;");
356d65f6f70SBen Gras 			continue;
35792395e9cSLionel Sambuc 		case ('>'):
358d65f6f70SBen Gras 			printf("&gt;");
359d65f6f70SBen Gras 			continue;
36092395e9cSLionel Sambuc 		case ('&'):
361d65f6f70SBen Gras 			printf("&amp;");
362d65f6f70SBen Gras 			continue;
36392395e9cSLionel Sambuc 		case (ASCII_HYPH):
364d65f6f70SBen Gras 			putchar('-');
365d65f6f70SBen Gras 			continue;
366d65f6f70SBen Gras 		default:
367d65f6f70SBen Gras 			break;
368d65f6f70SBen Gras 		}
369d65f6f70SBen Gras 
37092395e9cSLionel Sambuc 		esc = mandoc_escape(&p, &seq, &len);
37192395e9cSLionel Sambuc 		if (ESCAPE_ERROR == esc)
37292395e9cSLionel Sambuc 			break;
373d65f6f70SBen Gras 
37492395e9cSLionel Sambuc 		switch (esc) {
375*0a6a1f1dSLionel Sambuc 		case (ESCAPE_FONT):
376*0a6a1f1dSLionel Sambuc 			/* FALLTHROUGH */
377*0a6a1f1dSLionel Sambuc 		case (ESCAPE_FONTPREV):
378*0a6a1f1dSLionel Sambuc 			/* FALLTHROUGH */
379*0a6a1f1dSLionel Sambuc 		case (ESCAPE_FONTBOLD):
380*0a6a1f1dSLionel Sambuc 			/* FALLTHROUGH */
381*0a6a1f1dSLionel Sambuc 		case (ESCAPE_FONTITALIC):
382*0a6a1f1dSLionel Sambuc 			/* FALLTHROUGH */
383*0a6a1f1dSLionel Sambuc 		case (ESCAPE_FONTBI):
384*0a6a1f1dSLionel Sambuc 			/* FALLTHROUGH */
385*0a6a1f1dSLionel Sambuc 		case (ESCAPE_FONTROMAN):
386*0a6a1f1dSLionel Sambuc 			if (0 == norecurse)
387*0a6a1f1dSLionel Sambuc 				print_metaf(h, esc);
388*0a6a1f1dSLionel Sambuc 			continue;
389*0a6a1f1dSLionel Sambuc 		case (ESCAPE_SKIPCHAR):
390*0a6a1f1dSLionel Sambuc 			h->flags |= HTML_SKIPCHAR;
391*0a6a1f1dSLionel Sambuc 			continue;
392*0a6a1f1dSLionel Sambuc 		default:
393*0a6a1f1dSLionel Sambuc 			break;
394*0a6a1f1dSLionel Sambuc 		}
395*0a6a1f1dSLionel Sambuc 
396*0a6a1f1dSLionel Sambuc 		if (h->flags & HTML_SKIPCHAR) {
397*0a6a1f1dSLionel Sambuc 			h->flags &= ~HTML_SKIPCHAR;
398*0a6a1f1dSLionel Sambuc 			continue;
399*0a6a1f1dSLionel Sambuc 		}
400*0a6a1f1dSLionel Sambuc 
401*0a6a1f1dSLionel Sambuc 		switch (esc) {
40292395e9cSLionel Sambuc 		case (ESCAPE_UNICODE):
40392395e9cSLionel Sambuc 			/* Skip passed "u" header. */
40492395e9cSLionel Sambuc 			c = mchars_num2uc(seq + 1, len - 1);
40592395e9cSLionel Sambuc 			if ('\0' != c)
40692395e9cSLionel Sambuc 				printf("&#x%x;", c);
40792395e9cSLionel Sambuc 			break;
40892395e9cSLionel Sambuc 		case (ESCAPE_NUMBERED):
40992395e9cSLionel Sambuc 			c = mchars_num2char(seq, len);
41092395e9cSLionel Sambuc 			if ('\0' != c)
41192395e9cSLionel Sambuc 				putchar(c);
41292395e9cSLionel Sambuc 			break;
41392395e9cSLionel Sambuc 		case (ESCAPE_SPECIAL):
41492395e9cSLionel Sambuc 			c = mchars_spec2cp(h->symtab, seq, len);
41592395e9cSLionel Sambuc 			if (c > 0)
41692395e9cSLionel Sambuc 				printf("&#%d;", c);
41792395e9cSLionel Sambuc 			else if (-1 == c && 1 == len)
41892395e9cSLionel Sambuc 				putchar((int)*seq);
41992395e9cSLionel Sambuc 			break;
42092395e9cSLionel Sambuc 		case (ESCAPE_NOSPACE):
42192395e9cSLionel Sambuc 			if ('\0' == *p)
422d65f6f70SBen Gras 				nospace = 1;
42392395e9cSLionel Sambuc 			break;
42492395e9cSLionel Sambuc 		default:
42592395e9cSLionel Sambuc 			break;
42692395e9cSLionel Sambuc 		}
427d65f6f70SBen Gras 	}
428d65f6f70SBen Gras 
429d65f6f70SBen Gras 	return(nospace);
430d65f6f70SBen Gras }
431d65f6f70SBen Gras 
432d65f6f70SBen Gras 
433d65f6f70SBen Gras static void
print_attr(struct html * h,const char * key,const char * val)434d65f6f70SBen Gras print_attr(struct html *h, const char *key, const char *val)
435d65f6f70SBen Gras {
436d65f6f70SBen Gras 	printf(" %s=\"", key);
437d65f6f70SBen Gras 	(void)print_encode(h, val, 1);
438d65f6f70SBen Gras 	putchar('\"');
439d65f6f70SBen Gras }
440d65f6f70SBen Gras 
441d65f6f70SBen Gras 
442d65f6f70SBen Gras struct tag *
print_otag(struct html * h,enum htmltag tag,int sz,const struct htmlpair * p)443d65f6f70SBen Gras print_otag(struct html *h, enum htmltag tag,
444d65f6f70SBen Gras 		int sz, const struct htmlpair *p)
445d65f6f70SBen Gras {
446d65f6f70SBen Gras 	int		 i;
447d65f6f70SBen Gras 	struct tag	*t;
448d65f6f70SBen Gras 
449d65f6f70SBen Gras 	/* Push this tags onto the stack of open scopes. */
450d65f6f70SBen Gras 
451d65f6f70SBen Gras 	if ( ! (HTML_NOSTACK & htmltags[tag].flags)) {
45292395e9cSLionel Sambuc 		t = mandoc_malloc(sizeof(struct tag));
453d65f6f70SBen Gras 		t->tag = tag;
454d65f6f70SBen Gras 		t->next = h->tags.head;
455d65f6f70SBen Gras 		h->tags.head = t;
456d65f6f70SBen Gras 	} else
457d65f6f70SBen Gras 		t = NULL;
458d65f6f70SBen Gras 
459d65f6f70SBen Gras 	if ( ! (HTML_NOSPACE & h->flags))
460d65f6f70SBen Gras 		if ( ! (HTML_CLRLINE & htmltags[tag].flags)) {
461d65f6f70SBen Gras 			/* Manage keeps! */
462d65f6f70SBen Gras 			if ( ! (HTML_KEEP & h->flags)) {
463d65f6f70SBen Gras 				if (HTML_PREKEEP & h->flags)
464d65f6f70SBen Gras 					h->flags |= HTML_KEEP;
465d65f6f70SBen Gras 				putchar(' ');
466d65f6f70SBen Gras 			} else
467d65f6f70SBen Gras 				printf("&#160;");
468d65f6f70SBen Gras 		}
469d65f6f70SBen Gras 
470d65f6f70SBen Gras 	if ( ! (h->flags & HTML_NONOSPACE))
471d65f6f70SBen Gras 		h->flags &= ~HTML_NOSPACE;
472d65f6f70SBen Gras 	else
473d65f6f70SBen Gras 		h->flags |= HTML_NOSPACE;
474d65f6f70SBen Gras 
475d65f6f70SBen Gras 	/* Print out the tag name and attributes. */
476d65f6f70SBen Gras 
477d65f6f70SBen Gras 	printf("<%s", htmltags[tag].name);
478d65f6f70SBen Gras 	for (i = 0; i < sz; i++)
479d65f6f70SBen Gras 		print_attr(h, htmlattrs[p[i].key], p[i].val);
480d65f6f70SBen Gras 
481d65f6f70SBen Gras 	/* Add non-overridable attributes. */
482d65f6f70SBen Gras 
483d65f6f70SBen Gras 	if (TAG_HTML == tag && HTML_XHTML_1_0_STRICT == h->type) {
484d65f6f70SBen Gras 		print_attr(h, "xmlns", "http://www.w3.org/1999/xhtml");
485d65f6f70SBen Gras 		print_attr(h, "xml:lang", "en");
486d65f6f70SBen Gras 		print_attr(h, "lang", "en");
487d65f6f70SBen Gras 	}
488d65f6f70SBen Gras 
48992395e9cSLionel Sambuc 	/* Accommodate for XML "well-formed" singleton escaping. */
490d65f6f70SBen Gras 
491d65f6f70SBen Gras 	if (HTML_AUTOCLOSE & htmltags[tag].flags)
492d65f6f70SBen Gras 		switch (h->type) {
493d65f6f70SBen Gras 		case (HTML_XHTML_1_0_STRICT):
494d65f6f70SBen Gras 			putchar('/');
495d65f6f70SBen Gras 			break;
496d65f6f70SBen Gras 		default:
497d65f6f70SBen Gras 			break;
498d65f6f70SBen Gras 		}
499d65f6f70SBen Gras 
500d65f6f70SBen Gras 	putchar('>');
501d65f6f70SBen Gras 
502d65f6f70SBen Gras 	h->flags |= HTML_NOSPACE;
503d65f6f70SBen Gras 
504d65f6f70SBen Gras 	if ((HTML_AUTOCLOSE | HTML_CLRLINE) & htmltags[tag].flags)
505d65f6f70SBen Gras 		putchar('\n');
506d65f6f70SBen Gras 
507d65f6f70SBen Gras 	return(t);
508d65f6f70SBen Gras }
509d65f6f70SBen Gras 
510d65f6f70SBen Gras 
511d65f6f70SBen Gras static void
print_ctag(struct html * h,enum htmltag tag)512d65f6f70SBen Gras print_ctag(struct html *h, enum htmltag tag)
513d65f6f70SBen Gras {
514d65f6f70SBen Gras 
515d65f6f70SBen Gras 	printf("</%s>", htmltags[tag].name);
516d65f6f70SBen Gras 	if (HTML_CLRLINE & htmltags[tag].flags) {
517d65f6f70SBen Gras 		h->flags |= HTML_NOSPACE;
518d65f6f70SBen Gras 		putchar('\n');
519d65f6f70SBen Gras 	}
520d65f6f70SBen Gras }
521d65f6f70SBen Gras 
522d65f6f70SBen Gras void
print_gen_decls(struct html * h)523d65f6f70SBen Gras print_gen_decls(struct html *h)
524d65f6f70SBen Gras {
525d65f6f70SBen Gras 	const char	*doctype;
526d65f6f70SBen Gras 	const char	*dtd;
527d65f6f70SBen Gras 	const char	*name;
528d65f6f70SBen Gras 
529d65f6f70SBen Gras 	switch (h->type) {
530d65f6f70SBen Gras 	case (HTML_HTML_4_01_STRICT):
531d65f6f70SBen Gras 		name = "HTML";
532d65f6f70SBen Gras 		doctype = "-//W3C//DTD HTML 4.01//EN";
533d65f6f70SBen Gras 		dtd = "http://www.w3.org/TR/html4/strict.dtd";
534d65f6f70SBen Gras 		break;
535d65f6f70SBen Gras 	default:
53692395e9cSLionel Sambuc 		puts("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
537d65f6f70SBen Gras 		name = "html";
538d65f6f70SBen Gras 		doctype = "-//W3C//DTD XHTML 1.0 Strict//EN";
539d65f6f70SBen Gras 		dtd = "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd";
540d65f6f70SBen Gras 		break;
541d65f6f70SBen Gras 	}
542d65f6f70SBen Gras 
543d65f6f70SBen Gras 	printf("<!DOCTYPE %s PUBLIC \"%s\" \"%s\">\n",
544d65f6f70SBen Gras 			name, doctype, dtd);
545d65f6f70SBen Gras }
546d65f6f70SBen Gras 
547d65f6f70SBen Gras void
print_text(struct html * h,const char * word)548d65f6f70SBen Gras print_text(struct html *h, const char *word)
549d65f6f70SBen Gras {
550d65f6f70SBen Gras 
551d65f6f70SBen Gras 	if ( ! (HTML_NOSPACE & h->flags)) {
552d65f6f70SBen Gras 		/* Manage keeps! */
553d65f6f70SBen Gras 		if ( ! (HTML_KEEP & h->flags)) {
554d65f6f70SBen Gras 			if (HTML_PREKEEP & h->flags)
555d65f6f70SBen Gras 				h->flags |= HTML_KEEP;
556d65f6f70SBen Gras 			putchar(' ');
557d65f6f70SBen Gras 		} else
558d65f6f70SBen Gras 			printf("&#160;");
559d65f6f70SBen Gras 	}
560d65f6f70SBen Gras 
561d65f6f70SBen Gras 	assert(NULL == h->metaf);
562*0a6a1f1dSLionel Sambuc 	switch (h->metac) {
563*0a6a1f1dSLionel Sambuc 	case (HTMLFONT_ITALIC):
564*0a6a1f1dSLionel Sambuc 		h->metaf = print_otag(h, TAG_I, 0, NULL);
565*0a6a1f1dSLionel Sambuc 		break;
566*0a6a1f1dSLionel Sambuc 	case (HTMLFONT_BOLD):
567*0a6a1f1dSLionel Sambuc 		h->metaf = print_otag(h, TAG_B, 0, NULL);
568*0a6a1f1dSLionel Sambuc 		break;
569*0a6a1f1dSLionel Sambuc 	case (HTMLFONT_BI):
570*0a6a1f1dSLionel Sambuc 		h->metaf = print_otag(h, TAG_B, 0, NULL);
571d65f6f70SBen Gras 		print_otag(h, TAG_I, 0, NULL);
572*0a6a1f1dSLionel Sambuc 		break;
573*0a6a1f1dSLionel Sambuc 	default:
574*0a6a1f1dSLionel Sambuc 		break;
575*0a6a1f1dSLionel Sambuc 	}
576d65f6f70SBen Gras 
577d65f6f70SBen Gras 	assert(word);
57892395e9cSLionel Sambuc 	if ( ! print_encode(h, word, 0)) {
579d65f6f70SBen Gras 		if ( ! (h->flags & HTML_NONOSPACE))
580d65f6f70SBen Gras 			h->flags &= ~HTML_NOSPACE;
58192395e9cSLionel Sambuc 	} else
58292395e9cSLionel Sambuc 		h->flags |= HTML_NOSPACE;
583d65f6f70SBen Gras 
584d65f6f70SBen Gras 	if (h->metaf) {
585d65f6f70SBen Gras 		print_tagq(h, h->metaf);
586d65f6f70SBen Gras 		h->metaf = NULL;
587d65f6f70SBen Gras 	}
588d65f6f70SBen Gras 
589d65f6f70SBen Gras 	h->flags &= ~HTML_IGNDELIM;
590d65f6f70SBen Gras }
591d65f6f70SBen Gras 
592d65f6f70SBen Gras 
593d65f6f70SBen Gras void
print_tagq(struct html * h,const struct tag * until)594d65f6f70SBen Gras print_tagq(struct html *h, const struct tag *until)
595d65f6f70SBen Gras {
596d65f6f70SBen Gras 	struct tag	*tag;
597d65f6f70SBen Gras 
598d65f6f70SBen Gras 	while ((tag = h->tags.head) != NULL) {
59992395e9cSLionel Sambuc 		/*
60092395e9cSLionel Sambuc 		 * Remember to close out and nullify the current
60192395e9cSLionel Sambuc 		 * meta-font and table, if applicable.
60292395e9cSLionel Sambuc 		 */
603d65f6f70SBen Gras 		if (tag == h->metaf)
604d65f6f70SBen Gras 			h->metaf = NULL;
60592395e9cSLionel Sambuc 		if (tag == h->tblt)
60692395e9cSLionel Sambuc 			h->tblt = NULL;
607d65f6f70SBen Gras 		print_ctag(h, tag->tag);
608d65f6f70SBen Gras 		h->tags.head = tag->next;
609d65f6f70SBen Gras 		free(tag);
610d65f6f70SBen Gras 		if (until && tag == until)
611d65f6f70SBen Gras 			return;
612d65f6f70SBen Gras 	}
613d65f6f70SBen Gras }
614d65f6f70SBen Gras 
615d65f6f70SBen Gras 
616d65f6f70SBen Gras void
print_stagq(struct html * h,const struct tag * suntil)617d65f6f70SBen Gras print_stagq(struct html *h, const struct tag *suntil)
618d65f6f70SBen Gras {
619d65f6f70SBen Gras 	struct tag	*tag;
620d65f6f70SBen Gras 
621d65f6f70SBen Gras 	while ((tag = h->tags.head) != NULL) {
622d65f6f70SBen Gras 		if (suntil && tag == suntil)
623d65f6f70SBen Gras 			return;
62492395e9cSLionel Sambuc 		/*
62592395e9cSLionel Sambuc 		 * Remember to close out and nullify the current
62692395e9cSLionel Sambuc 		 * meta-font and table, if applicable.
62792395e9cSLionel Sambuc 		 */
628d65f6f70SBen Gras 		if (tag == h->metaf)
629d65f6f70SBen Gras 			h->metaf = NULL;
63092395e9cSLionel Sambuc 		if (tag == h->tblt)
63192395e9cSLionel Sambuc 			h->tblt = NULL;
632d65f6f70SBen Gras 		print_ctag(h, tag->tag);
633d65f6f70SBen Gras 		h->tags.head = tag->next;
634d65f6f70SBen Gras 		free(tag);
635d65f6f70SBen Gras 	}
636d65f6f70SBen Gras }
637d65f6f70SBen Gras 
638d65f6f70SBen Gras void
bufinit(struct html * h)639d65f6f70SBen Gras bufinit(struct html *h)
640d65f6f70SBen Gras {
641d65f6f70SBen Gras 
642d65f6f70SBen Gras 	h->buf[0] = '\0';
643d65f6f70SBen Gras 	h->buflen = 0;
644d65f6f70SBen Gras }
645d65f6f70SBen Gras 
646d65f6f70SBen Gras void
bufcat_style(struct html * h,const char * key,const char * val)647d65f6f70SBen Gras bufcat_style(struct html *h, const char *key, const char *val)
648d65f6f70SBen Gras {
649d65f6f70SBen Gras 
650d65f6f70SBen Gras 	bufcat(h, key);
65192395e9cSLionel Sambuc 	bufcat(h, ":");
652d65f6f70SBen Gras 	bufcat(h, val);
65392395e9cSLionel Sambuc 	bufcat(h, ";");
654d65f6f70SBen Gras }
655d65f6f70SBen Gras 
656d65f6f70SBen Gras void
bufcat(struct html * h,const char * p)657d65f6f70SBen Gras bufcat(struct html *h, const char *p)
658d65f6f70SBen Gras {
659d65f6f70SBen Gras 
66092395e9cSLionel Sambuc 	h->buflen = strlcat(h->buf, p, BUFSIZ);
66192395e9cSLionel Sambuc 	assert(h->buflen < BUFSIZ);
662d65f6f70SBen Gras }
663d65f6f70SBen Gras 
664d65f6f70SBen Gras void
bufcat_fmt(struct html * h,const char * fmt,...)66592395e9cSLionel Sambuc bufcat_fmt(struct html *h, const char *fmt, ...)
666d65f6f70SBen Gras {
667d65f6f70SBen Gras 	va_list		 ap;
668d65f6f70SBen Gras 
669d65f6f70SBen Gras 	va_start(ap, fmt);
670d65f6f70SBen Gras 	(void)vsnprintf(h->buf + (int)h->buflen,
671d65f6f70SBen Gras 			BUFSIZ - h->buflen - 1, fmt, ap);
672d65f6f70SBen Gras 	va_end(ap);
673d65f6f70SBen Gras 	h->buflen = strlen(h->buf);
674d65f6f70SBen Gras }
675d65f6f70SBen Gras 
67692395e9cSLionel Sambuc static void
bufncat(struct html * h,const char * p,size_t sz)677d65f6f70SBen Gras bufncat(struct html *h, const char *p, size_t sz)
678d65f6f70SBen Gras {
679d65f6f70SBen Gras 
68092395e9cSLionel Sambuc 	assert(h->buflen + sz + 1 < BUFSIZ);
68192395e9cSLionel Sambuc 	strncat(h->buf, p, sz);
682d65f6f70SBen Gras 	h->buflen += sz;
683d65f6f70SBen Gras }
684d65f6f70SBen Gras 
685d65f6f70SBen Gras void
buffmt_includes(struct html * h,const char * name)686d65f6f70SBen Gras buffmt_includes(struct html *h, const char *name)
687d65f6f70SBen Gras {
688d65f6f70SBen Gras 	const char	*p, *pp;
689d65f6f70SBen Gras 
690d65f6f70SBen Gras 	pp = h->base_includes;
691d65f6f70SBen Gras 
69292395e9cSLionel Sambuc 	bufinit(h);
693d65f6f70SBen Gras 	while (NULL != (p = strchr(pp, '%'))) {
694d65f6f70SBen Gras 		bufncat(h, pp, (size_t)(p - pp));
695d65f6f70SBen Gras 		switch (*(p + 1)) {
696d65f6f70SBen Gras 		case('I'):
697d65f6f70SBen Gras 			bufcat(h, name);
698d65f6f70SBen Gras 			break;
699d65f6f70SBen Gras 		default:
700d65f6f70SBen Gras 			bufncat(h, p, 2);
701d65f6f70SBen Gras 			break;
702d65f6f70SBen Gras 		}
703d65f6f70SBen Gras 		pp = p + 2;
704d65f6f70SBen Gras 	}
705d65f6f70SBen Gras 	if (pp)
706d65f6f70SBen Gras 		bufcat(h, pp);
707d65f6f70SBen Gras }
708d65f6f70SBen Gras 
709d65f6f70SBen Gras void
buffmt_man(struct html * h,const char * name,const char * sec)710d65f6f70SBen Gras buffmt_man(struct html *h,
711d65f6f70SBen Gras 		const char *name, const char *sec)
712d65f6f70SBen Gras {
713d65f6f70SBen Gras 	const char	*p, *pp;
714d65f6f70SBen Gras 
715d65f6f70SBen Gras 	pp = h->base_man;
716d65f6f70SBen Gras 
71792395e9cSLionel Sambuc 	bufinit(h);
718d65f6f70SBen Gras 	while (NULL != (p = strchr(pp, '%'))) {
719d65f6f70SBen Gras 		bufncat(h, pp, (size_t)(p - pp));
720d65f6f70SBen Gras 		switch (*(p + 1)) {
721d65f6f70SBen Gras 		case('S'):
722d65f6f70SBen Gras 			bufcat(h, sec ? sec : "1");
723d65f6f70SBen Gras 			break;
724d65f6f70SBen Gras 		case('N'):
725*0a6a1f1dSLionel Sambuc 			bufcat_fmt(h, "%s", name);
726d65f6f70SBen Gras 			break;
727d65f6f70SBen Gras 		default:
728d65f6f70SBen Gras 			bufncat(h, p, 2);
729d65f6f70SBen Gras 			break;
730d65f6f70SBen Gras 		}
731d65f6f70SBen Gras 		pp = p + 2;
732d65f6f70SBen Gras 	}
733d65f6f70SBen Gras 	if (pp)
734d65f6f70SBen Gras 		bufcat(h, pp);
735d65f6f70SBen Gras }
736d65f6f70SBen Gras 
737d65f6f70SBen Gras void
bufcat_su(struct html * h,const char * p,const struct roffsu * su)738d65f6f70SBen Gras bufcat_su(struct html *h, const char *p, const struct roffsu *su)
739d65f6f70SBen Gras {
740d65f6f70SBen Gras 	double		 v;
741d65f6f70SBen Gras 
742d65f6f70SBen Gras 	v = su->scale;
74392395e9cSLionel Sambuc 	if (SCALE_MM == su->unit && 0.0 == (v /= 100.0))
74492395e9cSLionel Sambuc 		v = 1.0;
745d65f6f70SBen Gras 
74692395e9cSLionel Sambuc 	bufcat_fmt(h, "%s: %.2f%s;", p, v, roffscales[su->unit]);
747d65f6f70SBen Gras }
748d65f6f70SBen Gras 
749d65f6f70SBen Gras void
bufcat_id(struct html * h,const char * src)75092395e9cSLionel Sambuc bufcat_id(struct html *h, const char *src)
751d65f6f70SBen Gras {
752d65f6f70SBen Gras 
753d65f6f70SBen Gras 	/* Cf. <http://www.w3.org/TR/html4/types.html#h-6.2>. */
754d65f6f70SBen Gras 
75592395e9cSLionel Sambuc 	while ('\0' != *src)
75692395e9cSLionel Sambuc 		bufcat_fmt(h, "%.2x", *src++);
757d65f6f70SBen Gras }
758