xref: /freebsd/contrib/mandoc/html.c (revision c1c95add8c80843ba15d784f95c361d795b1f593)
1*c1c95addSBrooks Davis /* $Id: html.c,v 1.279 2022/08/09 11:23:11 schwarze Exp $ */
261d06d6bSBaptiste Daroussin /*
361d06d6bSBaptiste Daroussin  * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
46d38604fSBaptiste Daroussin  * Copyright (c) 2011-2015, 2017-2021 Ingo Schwarze <schwarze@openbsd.org>
5*c1c95addSBrooks Davis  * Copyright (c) 2022 Anna Vyalkova <cyber@sysrq.in>
661d06d6bSBaptiste Daroussin  *
761d06d6bSBaptiste Daroussin  * Permission to use, copy, modify, and distribute this software for any
861d06d6bSBaptiste Daroussin  * purpose with or without fee is hereby granted, provided that the above
961d06d6bSBaptiste Daroussin  * copyright notice and this permission notice appear in all copies.
1061d06d6bSBaptiste Daroussin  *
1161d06d6bSBaptiste Daroussin  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1261d06d6bSBaptiste Daroussin  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1361d06d6bSBaptiste Daroussin  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1461d06d6bSBaptiste Daroussin  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1561d06d6bSBaptiste Daroussin  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1661d06d6bSBaptiste Daroussin  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1761d06d6bSBaptiste Daroussin  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
186d38604fSBaptiste Daroussin  *
196d38604fSBaptiste Daroussin  * Common functions for mandoc(1) HTML formatters.
206d38604fSBaptiste Daroussin  * For use by individual formatters and by the main program.
2161d06d6bSBaptiste Daroussin  */
2261d06d6bSBaptiste Daroussin #include "config.h"
2361d06d6bSBaptiste Daroussin 
2461d06d6bSBaptiste Daroussin #include <sys/types.h>
257295610fSBaptiste Daroussin #include <sys/stat.h>
2661d06d6bSBaptiste Daroussin 
2761d06d6bSBaptiste Daroussin #include <assert.h>
2861d06d6bSBaptiste Daroussin #include <ctype.h>
2961d06d6bSBaptiste Daroussin #include <stdarg.h>
3061d06d6bSBaptiste Daroussin #include <stddef.h>
3161d06d6bSBaptiste Daroussin #include <stdio.h>
3261d06d6bSBaptiste Daroussin #include <stdint.h>
3361d06d6bSBaptiste Daroussin #include <stdlib.h>
3461d06d6bSBaptiste Daroussin #include <string.h>
3561d06d6bSBaptiste Daroussin #include <unistd.h>
3661d06d6bSBaptiste Daroussin 
3761d06d6bSBaptiste Daroussin #include "mandoc_aux.h"
3861d06d6bSBaptiste Daroussin #include "mandoc_ohash.h"
3961d06d6bSBaptiste Daroussin #include "mandoc.h"
4061d06d6bSBaptiste Daroussin #include "roff.h"
4161d06d6bSBaptiste Daroussin #include "out.h"
4261d06d6bSBaptiste Daroussin #include "html.h"
4361d06d6bSBaptiste Daroussin #include "manconf.h"
4461d06d6bSBaptiste Daroussin #include "main.h"
4561d06d6bSBaptiste Daroussin 
4661d06d6bSBaptiste Daroussin struct	htmldata {
4761d06d6bSBaptiste Daroussin 	const char	 *name;
4861d06d6bSBaptiste Daroussin 	int		  flags;
496d38604fSBaptiste Daroussin #define	HTML_INPHRASE	 (1 << 0)  /* Can appear in phrasing context. */
506d38604fSBaptiste Daroussin #define	HTML_TOPHRASE	 (1 << 1)  /* Establishes phrasing context. */
516d38604fSBaptiste Daroussin #define	HTML_NOSTACK	 (1 << 2)  /* Does not have an end tag. */
526d38604fSBaptiste Daroussin #define	HTML_NLBEFORE	 (1 << 3)  /* Output line break before opening. */
536d38604fSBaptiste Daroussin #define	HTML_NLBEGIN	 (1 << 4)  /* Output line break after opening. */
546d38604fSBaptiste Daroussin #define	HTML_NLEND	 (1 << 5)  /* Output line break before closing. */
556d38604fSBaptiste Daroussin #define	HTML_NLAFTER	 (1 << 6)  /* Output line break after closing. */
5661d06d6bSBaptiste Daroussin #define	HTML_NLAROUND	 (HTML_NLBEFORE | HTML_NLAFTER)
5761d06d6bSBaptiste Daroussin #define	HTML_NLINSIDE	 (HTML_NLBEGIN | HTML_NLEND)
5861d06d6bSBaptiste Daroussin #define	HTML_NLALL	 (HTML_NLAROUND | HTML_NLINSIDE)
596d38604fSBaptiste Daroussin #define	HTML_INDENT	 (1 << 7)  /* Indent content by two spaces. */
606d38604fSBaptiste Daroussin #define	HTML_NOINDENT	 (1 << 8)  /* Exception: never indent content. */
6161d06d6bSBaptiste Daroussin };
6261d06d6bSBaptiste Daroussin 
6361d06d6bSBaptiste Daroussin static	const struct htmldata htmltags[TAG_MAX] = {
6461d06d6bSBaptiste Daroussin 	{"html",	HTML_NLALL},
6561d06d6bSBaptiste Daroussin 	{"head",	HTML_NLALL | HTML_INDENT},
666d38604fSBaptiste Daroussin 	{"meta",	HTML_NOSTACK | HTML_NLALL},
676d38604fSBaptiste Daroussin 	{"link",	HTML_NOSTACK | HTML_NLALL},
686d38604fSBaptiste Daroussin 	{"style",	HTML_NLALL | HTML_INDENT},
6961d06d6bSBaptiste Daroussin 	{"title",	HTML_NLAROUND},
706d38604fSBaptiste Daroussin 	{"body",	HTML_NLALL},
71*c1c95addSBrooks Davis 	{"main",	HTML_NLALL},
7261d06d6bSBaptiste Daroussin 	{"div",		HTML_NLAROUND},
737295610fSBaptiste Daroussin 	{"section",	HTML_NLALL},
74*c1c95addSBrooks Davis 	{"nav",		HTML_NLALL},
7561d06d6bSBaptiste Daroussin 	{"table",	HTML_NLALL | HTML_INDENT},
7661d06d6bSBaptiste Daroussin 	{"tr",		HTML_NLALL | HTML_INDENT},
7761d06d6bSBaptiste Daroussin 	{"td",		HTML_NLAROUND},
7861d06d6bSBaptiste Daroussin 	{"li",		HTML_NLAROUND | HTML_INDENT},
7961d06d6bSBaptiste Daroussin 	{"ul",		HTML_NLALL | HTML_INDENT},
8061d06d6bSBaptiste Daroussin 	{"ol",		HTML_NLALL | HTML_INDENT},
8161d06d6bSBaptiste Daroussin 	{"dl",		HTML_NLALL | HTML_INDENT},
8261d06d6bSBaptiste Daroussin 	{"dt",		HTML_NLAROUND},
8361d06d6bSBaptiste Daroussin 	{"dd",		HTML_NLAROUND | HTML_INDENT},
846d38604fSBaptiste Daroussin 	{"h2",		HTML_TOPHRASE | HTML_NLAROUND},
85*c1c95addSBrooks Davis 	{"h3",		HTML_TOPHRASE | HTML_NLAROUND},
866d38604fSBaptiste Daroussin 	{"p",		HTML_TOPHRASE | HTML_NLAROUND | HTML_INDENT},
876d38604fSBaptiste Daroussin 	{"pre",		HTML_TOPHRASE | HTML_NLAROUND | HTML_NOINDENT},
886d38604fSBaptiste Daroussin 	{"a",		HTML_INPHRASE | HTML_TOPHRASE},
896d38604fSBaptiste Daroussin 	{"b",		HTML_INPHRASE | HTML_TOPHRASE},
906d38604fSBaptiste Daroussin 	{"cite",	HTML_INPHRASE | HTML_TOPHRASE},
916d38604fSBaptiste Daroussin 	{"code",	HTML_INPHRASE | HTML_TOPHRASE},
926d38604fSBaptiste Daroussin 	{"i",		HTML_INPHRASE | HTML_TOPHRASE},
936d38604fSBaptiste Daroussin 	{"small",	HTML_INPHRASE | HTML_TOPHRASE},
946d38604fSBaptiste Daroussin 	{"span",	HTML_INPHRASE | HTML_TOPHRASE},
956d38604fSBaptiste Daroussin 	{"var",		HTML_INPHRASE | HTML_TOPHRASE},
966d38604fSBaptiste Daroussin 	{"br",		HTML_INPHRASE | HTML_NOSTACK | HTML_NLALL},
976d38604fSBaptiste Daroussin 	{"hr",		HTML_INPHRASE | HTML_NOSTACK},
986d38604fSBaptiste Daroussin 	{"mark",	HTML_INPHRASE },
996d38604fSBaptiste Daroussin 	{"math",	HTML_INPHRASE | HTML_NLALL | HTML_INDENT},
10061d06d6bSBaptiste Daroussin 	{"mrow",	0},
10161d06d6bSBaptiste Daroussin 	{"mi",		0},
10261d06d6bSBaptiste Daroussin 	{"mn",		0},
10361d06d6bSBaptiste Daroussin 	{"mo",		0},
10461d06d6bSBaptiste Daroussin 	{"msup",	0},
10561d06d6bSBaptiste Daroussin 	{"msub",	0},
10661d06d6bSBaptiste Daroussin 	{"msubsup",	0},
10761d06d6bSBaptiste Daroussin 	{"mfrac",	0},
10861d06d6bSBaptiste Daroussin 	{"msqrt",	0},
10961d06d6bSBaptiste Daroussin 	{"mfenced",	0},
11061d06d6bSBaptiste Daroussin 	{"mtable",	0},
11161d06d6bSBaptiste Daroussin 	{"mtr",		0},
11261d06d6bSBaptiste Daroussin 	{"mtd",		0},
11361d06d6bSBaptiste Daroussin 	{"munderover",	0},
11461d06d6bSBaptiste Daroussin 	{"munder",	0},
11561d06d6bSBaptiste Daroussin 	{"mover",	0},
11661d06d6bSBaptiste Daroussin };
11761d06d6bSBaptiste Daroussin 
11861d06d6bSBaptiste Daroussin /* Avoid duplicate HTML id= attributes. */
1196d38604fSBaptiste Daroussin 
1206d38604fSBaptiste Daroussin struct	id_entry {
1216d38604fSBaptiste Daroussin 	int	 ord;	/* Ordinal number of the latest occurrence. */
1226d38604fSBaptiste Daroussin 	char	 id[];	/* The id= attribute without any ordinal suffix. */
1236d38604fSBaptiste Daroussin };
12461d06d6bSBaptiste Daroussin static	struct ohash	 id_unique;
12561d06d6bSBaptiste Daroussin 
1267295610fSBaptiste Daroussin static	void	 html_reset_internal(struct html *);
12761d06d6bSBaptiste Daroussin static	void	 print_byte(struct html *, char);
12861d06d6bSBaptiste Daroussin static	void	 print_endword(struct html *);
12961d06d6bSBaptiste Daroussin static	void	 print_indent(struct html *);
13061d06d6bSBaptiste Daroussin static	void	 print_word(struct html *, const char *);
13161d06d6bSBaptiste Daroussin 
13261d06d6bSBaptiste Daroussin static	void	 print_ctag(struct html *, struct tag *);
13361d06d6bSBaptiste Daroussin static	int	 print_escape(struct html *, char);
13461d06d6bSBaptiste Daroussin static	int	 print_encode(struct html *, const char *, const char *, int);
13561d06d6bSBaptiste Daroussin static	void	 print_href(struct html *, const char *, const char *, int);
13645a5aec3SBaptiste Daroussin static	void	 print_metaf(struct html *);
13761d06d6bSBaptiste Daroussin 
13861d06d6bSBaptiste Daroussin 
13961d06d6bSBaptiste Daroussin void *
html_alloc(const struct manoutput * outopts)14061d06d6bSBaptiste Daroussin html_alloc(const struct manoutput *outopts)
14161d06d6bSBaptiste Daroussin {
14261d06d6bSBaptiste Daroussin 	struct html	*h;
14361d06d6bSBaptiste Daroussin 
14461d06d6bSBaptiste Daroussin 	h = mandoc_calloc(1, sizeof(struct html));
14561d06d6bSBaptiste Daroussin 
14661d06d6bSBaptiste Daroussin 	h->tag = NULL;
1476d38604fSBaptiste Daroussin 	h->metac = h->metal = ESCAPE_FONTROMAN;
14861d06d6bSBaptiste Daroussin 	h->style = outopts->style;
1497295610fSBaptiste Daroussin 	if ((h->base_man1 = outopts->man) == NULL)
1507295610fSBaptiste Daroussin 		h->base_man2 = NULL;
1517295610fSBaptiste Daroussin 	else if ((h->base_man2 = strchr(h->base_man1, ';')) != NULL)
1527295610fSBaptiste Daroussin 		*h->base_man2++ = '\0';
15361d06d6bSBaptiste Daroussin 	h->base_includes = outopts->includes;
15461d06d6bSBaptiste Daroussin 	if (outopts->fragment)
15561d06d6bSBaptiste Daroussin 		h->oflags |= HTML_FRAGMENT;
1567295610fSBaptiste Daroussin 	if (outopts->toc)
1577295610fSBaptiste Daroussin 		h->oflags |= HTML_TOC;
15861d06d6bSBaptiste Daroussin 
1596d38604fSBaptiste Daroussin 	mandoc_ohash_init(&id_unique, 4, offsetof(struct id_entry, id));
16061d06d6bSBaptiste Daroussin 
16161d06d6bSBaptiste Daroussin 	return h;
16261d06d6bSBaptiste Daroussin }
16361d06d6bSBaptiste Daroussin 
1647295610fSBaptiste Daroussin static void
html_reset_internal(struct html * h)1657295610fSBaptiste Daroussin html_reset_internal(struct html *h)
16661d06d6bSBaptiste Daroussin {
16761d06d6bSBaptiste Daroussin 	struct tag	*tag;
1686d38604fSBaptiste Daroussin 	struct id_entry	*entry;
16961d06d6bSBaptiste Daroussin 	unsigned int	 slot;
17061d06d6bSBaptiste Daroussin 
17161d06d6bSBaptiste Daroussin 	while ((tag = h->tag) != NULL) {
17261d06d6bSBaptiste Daroussin 		h->tag = tag->next;
17361d06d6bSBaptiste Daroussin 		free(tag);
17461d06d6bSBaptiste Daroussin 	}
1756d38604fSBaptiste Daroussin 	entry = ohash_first(&id_unique, &slot);
1766d38604fSBaptiste Daroussin 	while (entry != NULL) {
1776d38604fSBaptiste Daroussin 		free(entry);
1786d38604fSBaptiste Daroussin 		entry = ohash_next(&id_unique, &slot);
17961d06d6bSBaptiste Daroussin 	}
18061d06d6bSBaptiste Daroussin 	ohash_delete(&id_unique);
18161d06d6bSBaptiste Daroussin }
18261d06d6bSBaptiste Daroussin 
18361d06d6bSBaptiste Daroussin void
html_reset(void * p)1847295610fSBaptiste Daroussin html_reset(void *p)
1857295610fSBaptiste Daroussin {
1867295610fSBaptiste Daroussin 	html_reset_internal(p);
1876d38604fSBaptiste Daroussin 	mandoc_ohash_init(&id_unique, 4, offsetof(struct id_entry, id));
1887295610fSBaptiste Daroussin }
1897295610fSBaptiste Daroussin 
1907295610fSBaptiste Daroussin void
html_free(void * p)1917295610fSBaptiste Daroussin html_free(void *p)
1927295610fSBaptiste Daroussin {
1937295610fSBaptiste Daroussin 	html_reset_internal(p);
1947295610fSBaptiste Daroussin 	free(p);
1957295610fSBaptiste Daroussin }
1967295610fSBaptiste Daroussin 
1977295610fSBaptiste Daroussin void
print_gen_head(struct html * h)19861d06d6bSBaptiste Daroussin print_gen_head(struct html *h)
19961d06d6bSBaptiste Daroussin {
20061d06d6bSBaptiste Daroussin 	struct tag	*t;
20161d06d6bSBaptiste Daroussin 
20261d06d6bSBaptiste Daroussin 	print_otag(h, TAG_META, "?", "charset", "utf-8");
2036d38604fSBaptiste Daroussin 	print_otag(h, TAG_META, "??", "name", "viewport",
2046d38604fSBaptiste Daroussin 	    "content", "width=device-width, initial-scale=1.0");
20561d06d6bSBaptiste Daroussin 	if (h->style != NULL) {
20661d06d6bSBaptiste Daroussin 		print_otag(h, TAG_LINK, "?h??", "rel", "stylesheet",
20761d06d6bSBaptiste Daroussin 		    h->style, "type", "text/css", "media", "all");
20861d06d6bSBaptiste Daroussin 		return;
20961d06d6bSBaptiste Daroussin 	}
21061d06d6bSBaptiste Daroussin 
21161d06d6bSBaptiste Daroussin 	/*
21261d06d6bSBaptiste Daroussin 	 * Print a minimal embedded style sheet.
21361d06d6bSBaptiste Daroussin 	 */
21461d06d6bSBaptiste Daroussin 
21561d06d6bSBaptiste Daroussin 	t = print_otag(h, TAG_STYLE, "");
21661d06d6bSBaptiste Daroussin 	print_text(h, "table.head, table.foot { width: 100%; }");
21761d06d6bSBaptiste Daroussin 	print_endline(h);
21861d06d6bSBaptiste Daroussin 	print_text(h, "td.head-rtitle, td.foot-os { text-align: right; }");
21961d06d6bSBaptiste Daroussin 	print_endline(h);
22061d06d6bSBaptiste Daroussin 	print_text(h, "td.head-vol { text-align: center; }");
22161d06d6bSBaptiste Daroussin 	print_endline(h);
2226d38604fSBaptiste Daroussin 	print_text(h, ".Nd, .Bf, .Op { display: inline; }");
22361d06d6bSBaptiste Daroussin 	print_endline(h);
2246d38604fSBaptiste Daroussin 	print_text(h, ".Pa, .Ad { font-style: italic; }");
22561d06d6bSBaptiste Daroussin 	print_endline(h);
2266d38604fSBaptiste Daroussin 	print_text(h, ".Ms { font-weight: bold; }");
22761d06d6bSBaptiste Daroussin 	print_endline(h);
2286d38604fSBaptiste Daroussin 	print_text(h, ".Bl-diag ");
22961d06d6bSBaptiste Daroussin 	print_byte(h, '>');
23061d06d6bSBaptiste Daroussin 	print_text(h, " dt { font-weight: bold; }");
23161d06d6bSBaptiste Daroussin 	print_endline(h);
2326d38604fSBaptiste Daroussin 	print_text(h, "code.Nm, .Fl, .Cm, .Ic, code.In, .Fd, .Fn, .Cd "
2336d38604fSBaptiste Daroussin 	    "{ font-weight: bold; font-family: inherit; }");
23461d06d6bSBaptiste Daroussin 	print_tagq(h, t);
23561d06d6bSBaptiste Daroussin }
23661d06d6bSBaptiste Daroussin 
23745a5aec3SBaptiste Daroussin int
html_setfont(struct html * h,enum mandoc_esc font)23845a5aec3SBaptiste Daroussin html_setfont(struct html *h, enum mandoc_esc font)
23961d06d6bSBaptiste Daroussin {
24045a5aec3SBaptiste Daroussin 	switch (font) {
24161d06d6bSBaptiste Daroussin 	case ESCAPE_FONTPREV:
24261d06d6bSBaptiste Daroussin 		font = h->metal;
24361d06d6bSBaptiste Daroussin 		break;
24461d06d6bSBaptiste Daroussin 	case ESCAPE_FONTITALIC:
24561d06d6bSBaptiste Daroussin 	case ESCAPE_FONTBOLD:
24661d06d6bSBaptiste Daroussin 	case ESCAPE_FONTBI:
24745a5aec3SBaptiste Daroussin 	case ESCAPE_FONTROMAN:
2486d38604fSBaptiste Daroussin 	case ESCAPE_FONTCR:
2496d38604fSBaptiste Daroussin 	case ESCAPE_FONTCB:
2506d38604fSBaptiste Daroussin 	case ESCAPE_FONTCI:
2517295610fSBaptiste Daroussin 		break;
25261d06d6bSBaptiste Daroussin 	case ESCAPE_FONT:
25345a5aec3SBaptiste Daroussin 		font = ESCAPE_FONTROMAN;
25461d06d6bSBaptiste Daroussin 		break;
25561d06d6bSBaptiste Daroussin 	default:
25645a5aec3SBaptiste Daroussin 		return 0;
25745a5aec3SBaptiste Daroussin 	}
25845a5aec3SBaptiste Daroussin 	h->metal = h->metac;
25945a5aec3SBaptiste Daroussin 	h->metac = font;
26045a5aec3SBaptiste Daroussin 	return 1;
26161d06d6bSBaptiste Daroussin }
26261d06d6bSBaptiste Daroussin 
26345a5aec3SBaptiste Daroussin static void
print_metaf(struct html * h)26445a5aec3SBaptiste Daroussin print_metaf(struct html *h)
26545a5aec3SBaptiste Daroussin {
26661d06d6bSBaptiste Daroussin 	if (h->metaf) {
26761d06d6bSBaptiste Daroussin 		print_tagq(h, h->metaf);
26861d06d6bSBaptiste Daroussin 		h->metaf = NULL;
26961d06d6bSBaptiste Daroussin 	}
27045a5aec3SBaptiste Daroussin 	switch (h->metac) {
27145a5aec3SBaptiste Daroussin 	case ESCAPE_FONTITALIC:
27261d06d6bSBaptiste Daroussin 		h->metaf = print_otag(h, TAG_I, "");
27361d06d6bSBaptiste Daroussin 		break;
27445a5aec3SBaptiste Daroussin 	case ESCAPE_FONTBOLD:
27561d06d6bSBaptiste Daroussin 		h->metaf = print_otag(h, TAG_B, "");
27661d06d6bSBaptiste Daroussin 		break;
27745a5aec3SBaptiste Daroussin 	case ESCAPE_FONTBI:
27861d06d6bSBaptiste Daroussin 		h->metaf = print_otag(h, TAG_B, "");
27961d06d6bSBaptiste Daroussin 		print_otag(h, TAG_I, "");
28061d06d6bSBaptiste Daroussin 		break;
2816d38604fSBaptiste Daroussin 	case ESCAPE_FONTCR:
2827295610fSBaptiste Daroussin 		h->metaf = print_otag(h, TAG_SPAN, "c", "Li");
2837295610fSBaptiste Daroussin 		break;
2846d38604fSBaptiste Daroussin 	case ESCAPE_FONTCB:
2856d38604fSBaptiste Daroussin 		h->metaf = print_otag(h, TAG_SPAN, "c", "Li");
2866d38604fSBaptiste Daroussin 		print_otag(h, TAG_B, "");
2876d38604fSBaptiste Daroussin 		break;
2886d38604fSBaptiste Daroussin 	case ESCAPE_FONTCI:
2896d38604fSBaptiste Daroussin 		h->metaf = print_otag(h, TAG_SPAN, "c", "Li");
2906d38604fSBaptiste Daroussin 		print_otag(h, TAG_I, "");
2916d38604fSBaptiste Daroussin 		break;
29261d06d6bSBaptiste Daroussin 	default:
29361d06d6bSBaptiste Daroussin 		break;
29461d06d6bSBaptiste Daroussin 	}
29561d06d6bSBaptiste Daroussin }
29661d06d6bSBaptiste Daroussin 
2977295610fSBaptiste Daroussin void
html_close_paragraph(struct html * h)2987295610fSBaptiste Daroussin html_close_paragraph(struct html *h)
2997295610fSBaptiste Daroussin {
3006d38604fSBaptiste Daroussin 	struct tag	*this, *next;
3016d38604fSBaptiste Daroussin 	int		 flags;
3027295610fSBaptiste Daroussin 
3036d38604fSBaptiste Daroussin 	this = h->tag;
3046d38604fSBaptiste Daroussin 	for (;;) {
3056d38604fSBaptiste Daroussin 		next = this->next;
3066d38604fSBaptiste Daroussin 		flags = htmltags[this->tag].flags;
3076d38604fSBaptiste Daroussin 		if (flags & (HTML_INPHRASE | HTML_TOPHRASE))
3086d38604fSBaptiste Daroussin 			print_ctag(h, this);
3096d38604fSBaptiste Daroussin 		if ((flags & HTML_INPHRASE) == 0)
3107295610fSBaptiste Daroussin 			break;
3116d38604fSBaptiste Daroussin 		this = next;
3127295610fSBaptiste Daroussin 	}
3137295610fSBaptiste Daroussin }
3147295610fSBaptiste Daroussin 
3157295610fSBaptiste Daroussin /*
3167295610fSBaptiste Daroussin  * ROFF_nf switches to no-fill mode, ROFF_fi to fill mode.
3177295610fSBaptiste Daroussin  * TOKEN_NONE does not switch.  The old mode is returned.
3187295610fSBaptiste Daroussin  */
3197295610fSBaptiste Daroussin enum roff_tok
html_fillmode(struct html * h,enum roff_tok want)3207295610fSBaptiste Daroussin html_fillmode(struct html *h, enum roff_tok want)
3217295610fSBaptiste Daroussin {
3227295610fSBaptiste Daroussin 	struct tag	*t;
3237295610fSBaptiste Daroussin 	enum roff_tok	 had;
3247295610fSBaptiste Daroussin 
3257295610fSBaptiste Daroussin 	for (t = h->tag; t != NULL; t = t->next)
3267295610fSBaptiste Daroussin 		if (t->tag == TAG_PRE)
3277295610fSBaptiste Daroussin 			break;
3287295610fSBaptiste Daroussin 
3297295610fSBaptiste Daroussin 	had = t == NULL ? ROFF_fi : ROFF_nf;
3307295610fSBaptiste Daroussin 
3317295610fSBaptiste Daroussin 	if (want != had) {
3327295610fSBaptiste Daroussin 		switch (want) {
3337295610fSBaptiste Daroussin 		case ROFF_fi:
3347295610fSBaptiste Daroussin 			print_tagq(h, t);
3357295610fSBaptiste Daroussin 			break;
3367295610fSBaptiste Daroussin 		case ROFF_nf:
3377295610fSBaptiste Daroussin 			html_close_paragraph(h);
3387295610fSBaptiste Daroussin 			print_otag(h, TAG_PRE, "");
3397295610fSBaptiste Daroussin 			break;
3407295610fSBaptiste Daroussin 		case TOKEN_NONE:
3417295610fSBaptiste Daroussin 			break;
3427295610fSBaptiste Daroussin 		default:
3437295610fSBaptiste Daroussin 			abort();
3447295610fSBaptiste Daroussin 		}
3457295610fSBaptiste Daroussin 	}
3467295610fSBaptiste Daroussin 	return had;
3477295610fSBaptiste Daroussin }
3487295610fSBaptiste Daroussin 
3496d38604fSBaptiste Daroussin /*
3506d38604fSBaptiste Daroussin  * Allocate a string to be used for the "id=" attribute of an HTML
3516d38604fSBaptiste Daroussin  * element and/or as a segment identifier for a URI in an <a> element.
3526d38604fSBaptiste Daroussin  * The function may fail and return NULL if the node lacks text data
3536d38604fSBaptiste Daroussin  * to create the attribute from.
3546d38604fSBaptiste Daroussin  * The caller is responsible for free(3)ing the returned string.
3556d38604fSBaptiste Daroussin  *
3566d38604fSBaptiste Daroussin  * If the "unique" argument is non-zero, the "id_unique" ohash table
3576d38604fSBaptiste Daroussin  * is used for de-duplication.  If the "unique" argument is 1,
3586d38604fSBaptiste Daroussin  * it is the first time the function is called for this tag and
3596d38604fSBaptiste Daroussin  * location, so if an ordinal suffix is needed, it is incremented.
3606d38604fSBaptiste Daroussin  * If the "unique" argument is 2, it is the second time the function
3616d38604fSBaptiste Daroussin  * is called for this tag and location, so the ordinal suffix
3626d38604fSBaptiste Daroussin  * remains unchanged.
3636d38604fSBaptiste Daroussin  */
36461d06d6bSBaptiste Daroussin char *
html_make_id(const struct roff_node * n,int unique)36561d06d6bSBaptiste Daroussin html_make_id(const struct roff_node *n, int unique)
36661d06d6bSBaptiste Daroussin {
36761d06d6bSBaptiste Daroussin 	const struct roff_node	*nch;
3686d38604fSBaptiste Daroussin 	struct id_entry		*entry;
3696d38604fSBaptiste Daroussin 	char			*buf, *cp;
3706d38604fSBaptiste Daroussin 	size_t			 len;
37161d06d6bSBaptiste Daroussin 	unsigned int		 slot;
37261d06d6bSBaptiste Daroussin 
3736d38604fSBaptiste Daroussin 	if (n->tag != NULL)
3746d38604fSBaptiste Daroussin 		buf = mandoc_strdup(n->tag);
3756d38604fSBaptiste Daroussin 	else {
3766d38604fSBaptiste Daroussin 		switch (n->tok) {
3776d38604fSBaptiste Daroussin 		case MDOC_Sh:
3786d38604fSBaptiste Daroussin 		case MDOC_Ss:
3796d38604fSBaptiste Daroussin 		case MDOC_Sx:
3806d38604fSBaptiste Daroussin 		case MAN_SH:
3816d38604fSBaptiste Daroussin 		case MAN_SS:
38261d06d6bSBaptiste Daroussin 			for (nch = n->child; nch != NULL; nch = nch->next)
38361d06d6bSBaptiste Daroussin 				if (nch->type != ROFFT_TEXT)
38461d06d6bSBaptiste Daroussin 					return NULL;
38561d06d6bSBaptiste Daroussin 			buf = NULL;
38661d06d6bSBaptiste Daroussin 			deroff(&buf, n);
38761d06d6bSBaptiste Daroussin 			if (buf == NULL)
38861d06d6bSBaptiste Daroussin 				return NULL;
3896d38604fSBaptiste Daroussin 			break;
3906d38604fSBaptiste Daroussin 		default:
3916d38604fSBaptiste Daroussin 			if (n->child == NULL || n->child->type != ROFFT_TEXT)
3926d38604fSBaptiste Daroussin 				return NULL;
3936d38604fSBaptiste Daroussin 			buf = mandoc_strdup(n->child->string);
3946d38604fSBaptiste Daroussin 			break;
3956d38604fSBaptiste Daroussin 		}
3966d38604fSBaptiste Daroussin 	}
39761d06d6bSBaptiste Daroussin 
39861d06d6bSBaptiste Daroussin 	/*
39961d06d6bSBaptiste Daroussin 	 * In ID attributes, only use ASCII characters that are
40061d06d6bSBaptiste Daroussin 	 * permitted in URL-fragment strings according to the
40161d06d6bSBaptiste Daroussin 	 * explicit list at:
40261d06d6bSBaptiste Daroussin 	 * https://url.spec.whatwg.org/#url-fragment-string
4036d38604fSBaptiste Daroussin 	 * In addition, reserve '~' for ordinal suffixes.
40461d06d6bSBaptiste Daroussin 	 */
40561d06d6bSBaptiste Daroussin 
406*c1c95addSBrooks Davis 	for (cp = buf; *cp != '\0'; cp++) {
407*c1c95addSBrooks Davis 		if (*cp == ASCII_HYPH)
408*c1c95addSBrooks Davis 			*cp = '-';
409*c1c95addSBrooks Davis 		else if (isalnum((unsigned char)*cp) == 0 &&
4106d38604fSBaptiste Daroussin 		    strchr("!$&'()*+,-./:;=?@_", *cp) == NULL)
41161d06d6bSBaptiste Daroussin 			*cp = '_';
412*c1c95addSBrooks Davis 	}
41361d06d6bSBaptiste Daroussin 
41461d06d6bSBaptiste Daroussin 	if (unique == 0)
41561d06d6bSBaptiste Daroussin 		return buf;
41661d06d6bSBaptiste Daroussin 
41761d06d6bSBaptiste Daroussin 	/* Avoid duplicate HTML id= attributes. */
41861d06d6bSBaptiste Daroussin 
41961d06d6bSBaptiste Daroussin 	slot = ohash_qlookup(&id_unique, buf);
4206d38604fSBaptiste Daroussin 	if ((entry = ohash_find(&id_unique, slot)) == NULL) {
4216d38604fSBaptiste Daroussin 		len = strlen(buf) + 1;
4226d38604fSBaptiste Daroussin 		entry = mandoc_malloc(sizeof(*entry) + len);
4236d38604fSBaptiste Daroussin 		entry->ord = 1;
4246d38604fSBaptiste Daroussin 		memcpy(entry->id, buf, len);
4256d38604fSBaptiste Daroussin 		ohash_insert(&id_unique, slot, entry);
4266d38604fSBaptiste Daroussin 	} else if (unique == 1)
4276d38604fSBaptiste Daroussin 		entry->ord++;
4286d38604fSBaptiste Daroussin 
4296d38604fSBaptiste Daroussin 	if (entry->ord > 1) {
4306d38604fSBaptiste Daroussin 		cp = buf;
4316d38604fSBaptiste Daroussin 		mandoc_asprintf(&buf, "%s~%d", cp, entry->ord);
4326d38604fSBaptiste Daroussin 		free(cp);
43361d06d6bSBaptiste Daroussin 	}
43461d06d6bSBaptiste Daroussin 	return buf;
43561d06d6bSBaptiste Daroussin }
43661d06d6bSBaptiste Daroussin 
43761d06d6bSBaptiste Daroussin static int
print_escape(struct html * h,char c)43861d06d6bSBaptiste Daroussin print_escape(struct html *h, char c)
43961d06d6bSBaptiste Daroussin {
44061d06d6bSBaptiste Daroussin 
44161d06d6bSBaptiste Daroussin 	switch (c) {
44261d06d6bSBaptiste Daroussin 	case '<':
44361d06d6bSBaptiste Daroussin 		print_word(h, "&lt;");
44461d06d6bSBaptiste Daroussin 		break;
44561d06d6bSBaptiste Daroussin 	case '>':
44661d06d6bSBaptiste Daroussin 		print_word(h, "&gt;");
44761d06d6bSBaptiste Daroussin 		break;
44861d06d6bSBaptiste Daroussin 	case '&':
44961d06d6bSBaptiste Daroussin 		print_word(h, "&amp;");
45061d06d6bSBaptiste Daroussin 		break;
45161d06d6bSBaptiste Daroussin 	case '"':
45261d06d6bSBaptiste Daroussin 		print_word(h, "&quot;");
45361d06d6bSBaptiste Daroussin 		break;
45461d06d6bSBaptiste Daroussin 	case ASCII_NBRSP:
45561d06d6bSBaptiste Daroussin 		print_word(h, "&nbsp;");
45661d06d6bSBaptiste Daroussin 		break;
45761d06d6bSBaptiste Daroussin 	case ASCII_HYPH:
45861d06d6bSBaptiste Daroussin 		print_byte(h, '-');
45961d06d6bSBaptiste Daroussin 		break;
46061d06d6bSBaptiste Daroussin 	case ASCII_BREAK:
46161d06d6bSBaptiste Daroussin 		break;
46261d06d6bSBaptiste Daroussin 	default:
46361d06d6bSBaptiste Daroussin 		return 0;
46461d06d6bSBaptiste Daroussin 	}
46561d06d6bSBaptiste Daroussin 	return 1;
46661d06d6bSBaptiste Daroussin }
46761d06d6bSBaptiste Daroussin 
46861d06d6bSBaptiste Daroussin static int
print_encode(struct html * h,const char * p,const char * pend,int norecurse)46961d06d6bSBaptiste Daroussin print_encode(struct html *h, const char *p, const char *pend, int norecurse)
47061d06d6bSBaptiste Daroussin {
47161d06d6bSBaptiste Daroussin 	char		 numbuf[16];
47261d06d6bSBaptiste Daroussin 	const char	*seq;
47361d06d6bSBaptiste Daroussin 	size_t		 sz;
47461d06d6bSBaptiste Daroussin 	int		 c, len, breakline, nospace;
47561d06d6bSBaptiste Daroussin 	enum mandoc_esc	 esc;
47661d06d6bSBaptiste Daroussin 	static const char rejs[10] = { ' ', '\\', '<', '>', '&', '"',
47761d06d6bSBaptiste Daroussin 		ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' };
47861d06d6bSBaptiste Daroussin 
47961d06d6bSBaptiste Daroussin 	if (pend == NULL)
48061d06d6bSBaptiste Daroussin 		pend = strchr(p, '\0');
48161d06d6bSBaptiste Daroussin 
48261d06d6bSBaptiste Daroussin 	breakline = 0;
48361d06d6bSBaptiste Daroussin 	nospace = 0;
48461d06d6bSBaptiste Daroussin 
48561d06d6bSBaptiste Daroussin 	while (p < pend) {
48661d06d6bSBaptiste Daroussin 		if (HTML_SKIPCHAR & h->flags && '\\' != *p) {
48761d06d6bSBaptiste Daroussin 			h->flags &= ~HTML_SKIPCHAR;
48861d06d6bSBaptiste Daroussin 			p++;
48961d06d6bSBaptiste Daroussin 			continue;
49061d06d6bSBaptiste Daroussin 		}
49161d06d6bSBaptiste Daroussin 
49261d06d6bSBaptiste Daroussin 		for (sz = strcspn(p, rejs); sz-- && p < pend; p++)
49361d06d6bSBaptiste Daroussin 			print_byte(h, *p);
49461d06d6bSBaptiste Daroussin 
49561d06d6bSBaptiste Daroussin 		if (breakline &&
49661d06d6bSBaptiste Daroussin 		    (p >= pend || *p == ' ' || *p == ASCII_NBRSP)) {
4977295610fSBaptiste Daroussin 			print_otag(h, TAG_BR, "");
49861d06d6bSBaptiste Daroussin 			breakline = 0;
49961d06d6bSBaptiste Daroussin 			while (p < pend && (*p == ' ' || *p == ASCII_NBRSP))
50061d06d6bSBaptiste Daroussin 				p++;
50161d06d6bSBaptiste Daroussin 			continue;
50261d06d6bSBaptiste Daroussin 		}
50361d06d6bSBaptiste Daroussin 
50461d06d6bSBaptiste Daroussin 		if (p >= pend)
50561d06d6bSBaptiste Daroussin 			break;
50661d06d6bSBaptiste Daroussin 
50761d06d6bSBaptiste Daroussin 		if (*p == ' ') {
50861d06d6bSBaptiste Daroussin 			print_endword(h);
50961d06d6bSBaptiste Daroussin 			p++;
51061d06d6bSBaptiste Daroussin 			continue;
51161d06d6bSBaptiste Daroussin 		}
51261d06d6bSBaptiste Daroussin 
51361d06d6bSBaptiste Daroussin 		if (print_escape(h, *p++))
51461d06d6bSBaptiste Daroussin 			continue;
51561d06d6bSBaptiste Daroussin 
51661d06d6bSBaptiste Daroussin 		esc = mandoc_escape(&p, &seq, &len);
51761d06d6bSBaptiste Daroussin 		switch (esc) {
51861d06d6bSBaptiste Daroussin 		case ESCAPE_FONT:
51961d06d6bSBaptiste Daroussin 		case ESCAPE_FONTPREV:
52061d06d6bSBaptiste Daroussin 		case ESCAPE_FONTBOLD:
52161d06d6bSBaptiste Daroussin 		case ESCAPE_FONTITALIC:
52261d06d6bSBaptiste Daroussin 		case ESCAPE_FONTBI:
52361d06d6bSBaptiste Daroussin 		case ESCAPE_FONTROMAN:
5246d38604fSBaptiste Daroussin 		case ESCAPE_FONTCR:
5256d38604fSBaptiste Daroussin 		case ESCAPE_FONTCB:
5266d38604fSBaptiste Daroussin 		case ESCAPE_FONTCI:
5277295610fSBaptiste Daroussin 			if (0 == norecurse) {
5287295610fSBaptiste Daroussin 				h->flags |= HTML_NOSPACE;
52945a5aec3SBaptiste Daroussin 				if (html_setfont(h, esc))
53045a5aec3SBaptiste Daroussin 					print_metaf(h);
5317295610fSBaptiste Daroussin 				h->flags &= ~HTML_NOSPACE;
5327295610fSBaptiste Daroussin 			}
53361d06d6bSBaptiste Daroussin 			continue;
53461d06d6bSBaptiste Daroussin 		case ESCAPE_SKIPCHAR:
53561d06d6bSBaptiste Daroussin 			h->flags |= HTML_SKIPCHAR;
53661d06d6bSBaptiste Daroussin 			continue;
5377295610fSBaptiste Daroussin 		case ESCAPE_ERROR:
5387295610fSBaptiste Daroussin 			continue;
53961d06d6bSBaptiste Daroussin 		default:
54061d06d6bSBaptiste Daroussin 			break;
54161d06d6bSBaptiste Daroussin 		}
54261d06d6bSBaptiste Daroussin 
54361d06d6bSBaptiste Daroussin 		if (h->flags & HTML_SKIPCHAR) {
54461d06d6bSBaptiste Daroussin 			h->flags &= ~HTML_SKIPCHAR;
54561d06d6bSBaptiste Daroussin 			continue;
54661d06d6bSBaptiste Daroussin 		}
54761d06d6bSBaptiste Daroussin 
54861d06d6bSBaptiste Daroussin 		switch (esc) {
54961d06d6bSBaptiste Daroussin 		case ESCAPE_UNICODE:
55061d06d6bSBaptiste Daroussin 			/* Skip past "u" header. */
55161d06d6bSBaptiste Daroussin 			c = mchars_num2uc(seq + 1, len - 1);
55261d06d6bSBaptiste Daroussin 			break;
55361d06d6bSBaptiste Daroussin 		case ESCAPE_NUMBERED:
55461d06d6bSBaptiste Daroussin 			c = mchars_num2char(seq, len);
55561d06d6bSBaptiste Daroussin 			if (c < 0)
55661d06d6bSBaptiste Daroussin 				continue;
55761d06d6bSBaptiste Daroussin 			break;
55861d06d6bSBaptiste Daroussin 		case ESCAPE_SPECIAL:
55961d06d6bSBaptiste Daroussin 			c = mchars_spec2cp(seq, len);
56061d06d6bSBaptiste Daroussin 			if (c <= 0)
56161d06d6bSBaptiste Daroussin 				continue;
56261d06d6bSBaptiste Daroussin 			break;
5637295610fSBaptiste Daroussin 		case ESCAPE_UNDEF:
5647295610fSBaptiste Daroussin 			c = *seq;
5657295610fSBaptiste Daroussin 			break;
5667295610fSBaptiste Daroussin 		case ESCAPE_DEVICE:
5677295610fSBaptiste Daroussin 			print_word(h, "html");
5687295610fSBaptiste Daroussin 			continue;
56961d06d6bSBaptiste Daroussin 		case ESCAPE_BREAK:
57061d06d6bSBaptiste Daroussin 			breakline = 1;
57161d06d6bSBaptiste Daroussin 			continue;
57261d06d6bSBaptiste Daroussin 		case ESCAPE_NOSPACE:
57361d06d6bSBaptiste Daroussin 			if ('\0' == *p)
57461d06d6bSBaptiste Daroussin 				nospace = 1;
57561d06d6bSBaptiste Daroussin 			continue;
57661d06d6bSBaptiste Daroussin 		case ESCAPE_OVERSTRIKE:
57761d06d6bSBaptiste Daroussin 			if (len == 0)
57861d06d6bSBaptiste Daroussin 				continue;
57961d06d6bSBaptiste Daroussin 			c = seq[len - 1];
58061d06d6bSBaptiste Daroussin 			break;
58161d06d6bSBaptiste Daroussin 		default:
58261d06d6bSBaptiste Daroussin 			continue;
58361d06d6bSBaptiste Daroussin 		}
58461d06d6bSBaptiste Daroussin 		if ((c < 0x20 && c != 0x09) ||
58561d06d6bSBaptiste Daroussin 		    (c > 0x7E && c < 0xA0))
58661d06d6bSBaptiste Daroussin 			c = 0xFFFD;
58761d06d6bSBaptiste Daroussin 		if (c > 0x7E) {
58861d06d6bSBaptiste Daroussin 			(void)snprintf(numbuf, sizeof(numbuf), "&#x%.4X;", c);
58961d06d6bSBaptiste Daroussin 			print_word(h, numbuf);
59061d06d6bSBaptiste Daroussin 		} else if (print_escape(h, c) == 0)
59161d06d6bSBaptiste Daroussin 			print_byte(h, c);
59261d06d6bSBaptiste Daroussin 	}
59361d06d6bSBaptiste Daroussin 
59461d06d6bSBaptiste Daroussin 	return nospace;
59561d06d6bSBaptiste Daroussin }
59661d06d6bSBaptiste Daroussin 
59761d06d6bSBaptiste Daroussin static void
print_href(struct html * h,const char * name,const char * sec,int man)59861d06d6bSBaptiste Daroussin print_href(struct html *h, const char *name, const char *sec, int man)
59961d06d6bSBaptiste Daroussin {
6007295610fSBaptiste Daroussin 	struct stat	 sb;
60161d06d6bSBaptiste Daroussin 	const char	*p, *pp;
6027295610fSBaptiste Daroussin 	char		*filename;
60361d06d6bSBaptiste Daroussin 
6047295610fSBaptiste Daroussin 	if (man) {
6057295610fSBaptiste Daroussin 		pp = h->base_man1;
6067295610fSBaptiste Daroussin 		if (h->base_man2 != NULL) {
6077295610fSBaptiste Daroussin 			mandoc_asprintf(&filename, "%s.%s", name, sec);
6087295610fSBaptiste Daroussin 			if (stat(filename, &sb) == -1)
6097295610fSBaptiste Daroussin 				pp = h->base_man2;
6107295610fSBaptiste Daroussin 			free(filename);
6117295610fSBaptiste Daroussin 		}
6127295610fSBaptiste Daroussin 	} else
6137295610fSBaptiste Daroussin 		pp = h->base_includes;
6147295610fSBaptiste Daroussin 
61561d06d6bSBaptiste Daroussin 	while ((p = strchr(pp, '%')) != NULL) {
61661d06d6bSBaptiste Daroussin 		print_encode(h, pp, p, 1);
61761d06d6bSBaptiste Daroussin 		if (man && p[1] == 'S') {
61861d06d6bSBaptiste Daroussin 			if (sec == NULL)
61961d06d6bSBaptiste Daroussin 				print_byte(h, '1');
62061d06d6bSBaptiste Daroussin 			else
62161d06d6bSBaptiste Daroussin 				print_encode(h, sec, NULL, 1);
62261d06d6bSBaptiste Daroussin 		} else if ((man && p[1] == 'N') ||
62361d06d6bSBaptiste Daroussin 		    (man == 0 && p[1] == 'I'))
62461d06d6bSBaptiste Daroussin 			print_encode(h, name, NULL, 1);
62561d06d6bSBaptiste Daroussin 		else
62661d06d6bSBaptiste Daroussin 			print_encode(h, p, p + 2, 1);
62761d06d6bSBaptiste Daroussin 		pp = p + 2;
62861d06d6bSBaptiste Daroussin 	}
62961d06d6bSBaptiste Daroussin 	if (*pp != '\0')
63061d06d6bSBaptiste Daroussin 		print_encode(h, pp, NULL, 1);
63161d06d6bSBaptiste Daroussin }
63261d06d6bSBaptiste Daroussin 
63361d06d6bSBaptiste Daroussin struct tag *
print_otag(struct html * h,enum htmltag tag,const char * fmt,...)63461d06d6bSBaptiste Daroussin print_otag(struct html *h, enum htmltag tag, const char *fmt, ...)
63561d06d6bSBaptiste Daroussin {
63661d06d6bSBaptiste Daroussin 	va_list		 ap;
63761d06d6bSBaptiste Daroussin 	struct tag	*t;
63861d06d6bSBaptiste Daroussin 	const char	*attr;
63961d06d6bSBaptiste Daroussin 	char		*arg1, *arg2;
6407295610fSBaptiste Daroussin 	int		 style_written, tflags;
64161d06d6bSBaptiste Daroussin 
64261d06d6bSBaptiste Daroussin 	tflags = htmltags[tag].flags;
64361d06d6bSBaptiste Daroussin 
6446d38604fSBaptiste Daroussin 	/* Flow content is not allowed in phrasing context. */
6456d38604fSBaptiste Daroussin 
6466d38604fSBaptiste Daroussin 	if ((tflags & HTML_INPHRASE) == 0) {
6476d38604fSBaptiste Daroussin 		for (t = h->tag; t != NULL; t = t->next) {
6486d38604fSBaptiste Daroussin 			if (t->closed)
6496d38604fSBaptiste Daroussin 				continue;
6506d38604fSBaptiste Daroussin 			assert((htmltags[t->tag].flags & HTML_TOPHRASE) == 0);
6516d38604fSBaptiste Daroussin 			break;
6526d38604fSBaptiste Daroussin 		}
6536d38604fSBaptiste Daroussin 
6546d38604fSBaptiste Daroussin 	/*
6556d38604fSBaptiste Daroussin 	 * Always wrap phrasing elements in a paragraph
6566d38604fSBaptiste Daroussin 	 * unless already contained in some flow container;
6576d38604fSBaptiste Daroussin 	 * never put them directly into a section.
6586d38604fSBaptiste Daroussin 	 */
6596d38604fSBaptiste Daroussin 
6606d38604fSBaptiste Daroussin 	} else if (tflags & HTML_TOPHRASE && h->tag->tag == TAG_SECTION)
6616d38604fSBaptiste Daroussin 		print_otag(h, TAG_P, "c", "Pp");
6626d38604fSBaptiste Daroussin 
66361d06d6bSBaptiste Daroussin 	/* Push this tag onto the stack of open scopes. */
66461d06d6bSBaptiste Daroussin 
66561d06d6bSBaptiste Daroussin 	if ((tflags & HTML_NOSTACK) == 0) {
66661d06d6bSBaptiste Daroussin 		t = mandoc_malloc(sizeof(struct tag));
66761d06d6bSBaptiste Daroussin 		t->tag = tag;
66861d06d6bSBaptiste Daroussin 		t->next = h->tag;
6697295610fSBaptiste Daroussin 		t->refcnt = 0;
6707295610fSBaptiste Daroussin 		t->closed = 0;
67161d06d6bSBaptiste Daroussin 		h->tag = t;
67261d06d6bSBaptiste Daroussin 	} else
67361d06d6bSBaptiste Daroussin 		t = NULL;
67461d06d6bSBaptiste Daroussin 
67561d06d6bSBaptiste Daroussin 	if (tflags & HTML_NLBEFORE)
67661d06d6bSBaptiste Daroussin 		print_endline(h);
67761d06d6bSBaptiste Daroussin 	if (h->col == 0)
67861d06d6bSBaptiste Daroussin 		print_indent(h);
67961d06d6bSBaptiste Daroussin 	else if ((h->flags & HTML_NOSPACE) == 0) {
68061d06d6bSBaptiste Daroussin 		if (h->flags & HTML_KEEP)
68161d06d6bSBaptiste Daroussin 			print_word(h, "&#x00A0;");
68261d06d6bSBaptiste Daroussin 		else {
68361d06d6bSBaptiste Daroussin 			if (h->flags & HTML_PREKEEP)
68461d06d6bSBaptiste Daroussin 				h->flags |= HTML_KEEP;
68561d06d6bSBaptiste Daroussin 			print_endword(h);
68661d06d6bSBaptiste Daroussin 		}
68761d06d6bSBaptiste Daroussin 	}
68861d06d6bSBaptiste Daroussin 
68961d06d6bSBaptiste Daroussin 	if ( ! (h->flags & HTML_NONOSPACE))
69061d06d6bSBaptiste Daroussin 		h->flags &= ~HTML_NOSPACE;
69161d06d6bSBaptiste Daroussin 	else
69261d06d6bSBaptiste Daroussin 		h->flags |= HTML_NOSPACE;
69361d06d6bSBaptiste Daroussin 
69461d06d6bSBaptiste Daroussin 	/* Print out the tag name and attributes. */
69561d06d6bSBaptiste Daroussin 
69661d06d6bSBaptiste Daroussin 	print_byte(h, '<');
69761d06d6bSBaptiste Daroussin 	print_word(h, htmltags[tag].name);
69861d06d6bSBaptiste Daroussin 
69961d06d6bSBaptiste Daroussin 	va_start(ap, fmt);
70061d06d6bSBaptiste Daroussin 
7017295610fSBaptiste Daroussin 	while (*fmt != '\0' && *fmt != 's') {
70261d06d6bSBaptiste Daroussin 
70361d06d6bSBaptiste Daroussin 		/* Parse attributes and arguments. */
70461d06d6bSBaptiste Daroussin 
70561d06d6bSBaptiste Daroussin 		arg1 = va_arg(ap, char *);
70661d06d6bSBaptiste Daroussin 		arg2 = NULL;
70761d06d6bSBaptiste Daroussin 		switch (*fmt++) {
70861d06d6bSBaptiste Daroussin 		case 'c':
70961d06d6bSBaptiste Daroussin 			attr = "class";
71061d06d6bSBaptiste Daroussin 			break;
71161d06d6bSBaptiste Daroussin 		case 'h':
71261d06d6bSBaptiste Daroussin 			attr = "href";
71361d06d6bSBaptiste Daroussin 			break;
71461d06d6bSBaptiste Daroussin 		case 'i':
71561d06d6bSBaptiste Daroussin 			attr = "id";
71661d06d6bSBaptiste Daroussin 			break;
717*c1c95addSBrooks Davis 		case 'r':
718*c1c95addSBrooks Davis 			attr = "role";
719*c1c95addSBrooks Davis 			break;
72061d06d6bSBaptiste Daroussin 		case '?':
72161d06d6bSBaptiste Daroussin 			attr = arg1;
72261d06d6bSBaptiste Daroussin 			arg1 = va_arg(ap, char *);
72361d06d6bSBaptiste Daroussin 			break;
72461d06d6bSBaptiste Daroussin 		default:
72561d06d6bSBaptiste Daroussin 			abort();
72661d06d6bSBaptiste Daroussin 		}
72761d06d6bSBaptiste Daroussin 		if (*fmt == 'M')
72861d06d6bSBaptiste Daroussin 			arg2 = va_arg(ap, char *);
72961d06d6bSBaptiste Daroussin 		if (arg1 == NULL)
73061d06d6bSBaptiste Daroussin 			continue;
73161d06d6bSBaptiste Daroussin 
73261d06d6bSBaptiste Daroussin 		/* Print the attributes. */
73361d06d6bSBaptiste Daroussin 
73461d06d6bSBaptiste Daroussin 		print_byte(h, ' ');
73561d06d6bSBaptiste Daroussin 		print_word(h, attr);
73661d06d6bSBaptiste Daroussin 		print_byte(h, '=');
73761d06d6bSBaptiste Daroussin 		print_byte(h, '"');
73861d06d6bSBaptiste Daroussin 		switch (*fmt) {
73961d06d6bSBaptiste Daroussin 		case 'I':
74061d06d6bSBaptiste Daroussin 			print_href(h, arg1, NULL, 0);
74161d06d6bSBaptiste Daroussin 			fmt++;
74261d06d6bSBaptiste Daroussin 			break;
74361d06d6bSBaptiste Daroussin 		case 'M':
74461d06d6bSBaptiste Daroussin 			print_href(h, arg1, arg2, 1);
74561d06d6bSBaptiste Daroussin 			fmt++;
74661d06d6bSBaptiste Daroussin 			break;
74761d06d6bSBaptiste Daroussin 		case 'R':
74861d06d6bSBaptiste Daroussin 			print_byte(h, '#');
74961d06d6bSBaptiste Daroussin 			print_encode(h, arg1, NULL, 1);
75061d06d6bSBaptiste Daroussin 			fmt++;
75161d06d6bSBaptiste Daroussin 			break;
75261d06d6bSBaptiste Daroussin 		default:
75361d06d6bSBaptiste Daroussin 			print_encode(h, arg1, NULL, 1);
7547295610fSBaptiste Daroussin 			break;
7557295610fSBaptiste Daroussin 		}
7567295610fSBaptiste Daroussin 		print_byte(h, '"');
7577295610fSBaptiste Daroussin 	}
7587295610fSBaptiste Daroussin 
7597295610fSBaptiste Daroussin 	style_written = 0;
7607295610fSBaptiste Daroussin 	while (*fmt++ == 's') {
7617295610fSBaptiste Daroussin 		arg1 = va_arg(ap, char *);
7627295610fSBaptiste Daroussin 		arg2 = va_arg(ap, char *);
7637295610fSBaptiste Daroussin 		if (arg2 == NULL)
7647295610fSBaptiste Daroussin 			continue;
7657295610fSBaptiste Daroussin 		print_byte(h, ' ');
7667295610fSBaptiste Daroussin 		if (style_written == 0) {
7677295610fSBaptiste Daroussin 			print_word(h, "style=\"");
7687295610fSBaptiste Daroussin 			style_written = 1;
7697295610fSBaptiste Daroussin 		}
77061d06d6bSBaptiste Daroussin 		print_word(h, arg1);
77161d06d6bSBaptiste Daroussin 		print_byte(h, ':');
77261d06d6bSBaptiste Daroussin 		print_byte(h, ' ');
77361d06d6bSBaptiste Daroussin 		print_word(h, arg2);
77461d06d6bSBaptiste Daroussin 		print_byte(h, ';');
77561d06d6bSBaptiste Daroussin 	}
7767295610fSBaptiste Daroussin 	if (style_written)
77761d06d6bSBaptiste Daroussin 		print_byte(h, '"');
7787295610fSBaptiste Daroussin 
77961d06d6bSBaptiste Daroussin 	va_end(ap);
78061d06d6bSBaptiste Daroussin 
78161d06d6bSBaptiste Daroussin 	/* Accommodate for "well-formed" singleton escaping. */
78261d06d6bSBaptiste Daroussin 
7836d38604fSBaptiste Daroussin 	if (htmltags[tag].flags & HTML_NOSTACK)
78461d06d6bSBaptiste Daroussin 		print_byte(h, '/');
78561d06d6bSBaptiste Daroussin 
78661d06d6bSBaptiste Daroussin 	print_byte(h, '>');
78761d06d6bSBaptiste Daroussin 
78861d06d6bSBaptiste Daroussin 	if (tflags & HTML_NLBEGIN)
78961d06d6bSBaptiste Daroussin 		print_endline(h);
79061d06d6bSBaptiste Daroussin 	else
79161d06d6bSBaptiste Daroussin 		h->flags |= HTML_NOSPACE;
79261d06d6bSBaptiste Daroussin 
79361d06d6bSBaptiste Daroussin 	if (tflags & HTML_INDENT)
79461d06d6bSBaptiste Daroussin 		h->indent++;
79561d06d6bSBaptiste Daroussin 	if (tflags & HTML_NOINDENT)
79661d06d6bSBaptiste Daroussin 		h->noindent++;
79761d06d6bSBaptiste Daroussin 
79861d06d6bSBaptiste Daroussin 	return t;
79961d06d6bSBaptiste Daroussin }
80061d06d6bSBaptiste Daroussin 
8016d38604fSBaptiste Daroussin /*
8026d38604fSBaptiste Daroussin  * Print an element with an optional "id=" attribute.
8036d38604fSBaptiste Daroussin  * If the element has phrasing content and an "id=" attribute,
8046d38604fSBaptiste Daroussin  * also add a permalink: outside if it can be in phrasing context,
8056d38604fSBaptiste Daroussin  * inside otherwise.
8066d38604fSBaptiste Daroussin  */
8076d38604fSBaptiste Daroussin struct tag *
print_otag_id(struct html * h,enum htmltag elemtype,const char * cattr,struct roff_node * n)8086d38604fSBaptiste Daroussin print_otag_id(struct html *h, enum htmltag elemtype, const char *cattr,
8096d38604fSBaptiste Daroussin     struct roff_node *n)
8106d38604fSBaptiste Daroussin {
8116d38604fSBaptiste Daroussin 	struct roff_node *nch;
8126d38604fSBaptiste Daroussin 	struct tag	*ret, *t;
8136d38604fSBaptiste Daroussin 	char		*id, *href;
8146d38604fSBaptiste Daroussin 
8156d38604fSBaptiste Daroussin 	ret = NULL;
8166d38604fSBaptiste Daroussin 	id = href = NULL;
8176d38604fSBaptiste Daroussin 	if (n->flags & NODE_ID)
8186d38604fSBaptiste Daroussin 		id = html_make_id(n, 1);
8196d38604fSBaptiste Daroussin 	if (n->flags & NODE_HREF)
8206d38604fSBaptiste Daroussin 		href = id == NULL ? html_make_id(n, 2) : id;
8216d38604fSBaptiste Daroussin 	if (href != NULL && htmltags[elemtype].flags & HTML_INPHRASE)
8226d38604fSBaptiste Daroussin 		ret = print_otag(h, TAG_A, "chR", "permalink", href);
8236d38604fSBaptiste Daroussin 	t = print_otag(h, elemtype, "ci", cattr, id);
8246d38604fSBaptiste Daroussin 	if (ret == NULL) {
8256d38604fSBaptiste Daroussin 		ret = t;
8266d38604fSBaptiste Daroussin 		if (href != NULL && (nch = n->child) != NULL) {
8276d38604fSBaptiste Daroussin 			/* man(7) is safe, it tags phrasing content only. */
8286d38604fSBaptiste Daroussin 			if (n->tok > MDOC_MAX ||
8296d38604fSBaptiste Daroussin 			    htmltags[elemtype].flags & HTML_TOPHRASE)
8306d38604fSBaptiste Daroussin 				nch = NULL;
8316d38604fSBaptiste Daroussin 			else  /* For mdoc(7), beware of nested blocks. */
8326d38604fSBaptiste Daroussin 				while (nch != NULL && nch->type == ROFFT_TEXT)
8336d38604fSBaptiste Daroussin 					nch = nch->next;
8346d38604fSBaptiste Daroussin 			if (nch == NULL)
8356d38604fSBaptiste Daroussin 				print_otag(h, TAG_A, "chR", "permalink", href);
8366d38604fSBaptiste Daroussin 		}
8376d38604fSBaptiste Daroussin 	}
8386d38604fSBaptiste Daroussin 	free(id);
8396d38604fSBaptiste Daroussin 	if (id == NULL)
8406d38604fSBaptiste Daroussin 		free(href);
8416d38604fSBaptiste Daroussin 	return ret;
8426d38604fSBaptiste Daroussin }
8436d38604fSBaptiste Daroussin 
84461d06d6bSBaptiste Daroussin static void
print_ctag(struct html * h,struct tag * tag)84561d06d6bSBaptiste Daroussin print_ctag(struct html *h, struct tag *tag)
84661d06d6bSBaptiste Daroussin {
84761d06d6bSBaptiste Daroussin 	int	 tflags;
84861d06d6bSBaptiste Daroussin 
8497295610fSBaptiste Daroussin 	if (tag->closed == 0) {
8507295610fSBaptiste Daroussin 		tag->closed = 1;
85161d06d6bSBaptiste Daroussin 		if (tag == h->metaf)
85261d06d6bSBaptiste Daroussin 			h->metaf = NULL;
85361d06d6bSBaptiste Daroussin 		if (tag == h->tblt)
85461d06d6bSBaptiste Daroussin 			h->tblt = NULL;
85561d06d6bSBaptiste Daroussin 
85661d06d6bSBaptiste Daroussin 		tflags = htmltags[tag->tag].flags;
85761d06d6bSBaptiste Daroussin 		if (tflags & HTML_INDENT)
85861d06d6bSBaptiste Daroussin 			h->indent--;
85961d06d6bSBaptiste Daroussin 		if (tflags & HTML_NOINDENT)
86061d06d6bSBaptiste Daroussin 			h->noindent--;
86161d06d6bSBaptiste Daroussin 		if (tflags & HTML_NLEND)
86261d06d6bSBaptiste Daroussin 			print_endline(h);
86361d06d6bSBaptiste Daroussin 		print_indent(h);
86461d06d6bSBaptiste Daroussin 		print_byte(h, '<');
86561d06d6bSBaptiste Daroussin 		print_byte(h, '/');
86661d06d6bSBaptiste Daroussin 		print_word(h, htmltags[tag->tag].name);
86761d06d6bSBaptiste Daroussin 		print_byte(h, '>');
86861d06d6bSBaptiste Daroussin 		if (tflags & HTML_NLAFTER)
86961d06d6bSBaptiste Daroussin 			print_endline(h);
8707295610fSBaptiste Daroussin 	}
8717295610fSBaptiste Daroussin 	if (tag->refcnt == 0) {
87261d06d6bSBaptiste Daroussin 		h->tag = tag->next;
87361d06d6bSBaptiste Daroussin 		free(tag);
87461d06d6bSBaptiste Daroussin 	}
8757295610fSBaptiste Daroussin }
87661d06d6bSBaptiste Daroussin 
87761d06d6bSBaptiste Daroussin void
print_gen_decls(struct html * h)87861d06d6bSBaptiste Daroussin print_gen_decls(struct html *h)
87961d06d6bSBaptiste Daroussin {
88061d06d6bSBaptiste Daroussin 	print_word(h, "<!DOCTYPE html>");
88161d06d6bSBaptiste Daroussin 	print_endline(h);
88261d06d6bSBaptiste Daroussin }
88361d06d6bSBaptiste Daroussin 
88461d06d6bSBaptiste Daroussin void
print_gen_comment(struct html * h,struct roff_node * n)88561d06d6bSBaptiste Daroussin print_gen_comment(struct html *h, struct roff_node *n)
88661d06d6bSBaptiste Daroussin {
88761d06d6bSBaptiste Daroussin 	int	 wantblank;
88861d06d6bSBaptiste Daroussin 
88961d06d6bSBaptiste Daroussin 	print_word(h, "<!-- This is an automatically generated file."
89061d06d6bSBaptiste Daroussin 	    "  Do not edit.");
89161d06d6bSBaptiste Daroussin 	h->indent = 1;
89261d06d6bSBaptiste Daroussin 	wantblank = 0;
89361d06d6bSBaptiste Daroussin 	while (n != NULL && n->type == ROFFT_COMMENT) {
89461d06d6bSBaptiste Daroussin 		if (strstr(n->string, "-->") == NULL &&
89561d06d6bSBaptiste Daroussin 		    (wantblank || *n->string != '\0')) {
89661d06d6bSBaptiste Daroussin 			print_endline(h);
89761d06d6bSBaptiste Daroussin 			print_indent(h);
89861d06d6bSBaptiste Daroussin 			print_word(h, n->string);
89961d06d6bSBaptiste Daroussin 			wantblank = *n->string != '\0';
90061d06d6bSBaptiste Daroussin 		}
90161d06d6bSBaptiste Daroussin 		n = n->next;
90261d06d6bSBaptiste Daroussin 	}
90361d06d6bSBaptiste Daroussin 	if (wantblank)
90461d06d6bSBaptiste Daroussin 		print_endline(h);
90561d06d6bSBaptiste Daroussin 	print_word(h, " -->");
90661d06d6bSBaptiste Daroussin 	print_endline(h);
90761d06d6bSBaptiste Daroussin 	h->indent = 0;
90861d06d6bSBaptiste Daroussin }
90961d06d6bSBaptiste Daroussin 
91061d06d6bSBaptiste Daroussin void
print_text(struct html * h,const char * word)91161d06d6bSBaptiste Daroussin print_text(struct html *h, const char *word)
91261d06d6bSBaptiste Daroussin {
9136d38604fSBaptiste Daroussin 	print_tagged_text(h, word, NULL);
9146d38604fSBaptiste Daroussin }
9156d38604fSBaptiste Daroussin 
9166d38604fSBaptiste Daroussin void
print_tagged_text(struct html * h,const char * word,struct roff_node * n)9176d38604fSBaptiste Daroussin print_tagged_text(struct html *h, const char *word, struct roff_node *n)
9186d38604fSBaptiste Daroussin {
9196d38604fSBaptiste Daroussin 	struct tag	*t;
9206d38604fSBaptiste Daroussin 	char		*href;
9216d38604fSBaptiste Daroussin 
9226d38604fSBaptiste Daroussin 	/*
9236d38604fSBaptiste Daroussin 	 * Always wrap text in a paragraph unless already contained in
9246d38604fSBaptiste Daroussin 	 * some flow container; never put it directly into a section.
9256d38604fSBaptiste Daroussin 	 */
9266d38604fSBaptiste Daroussin 
9276d38604fSBaptiste Daroussin 	if (h->tag->tag == TAG_SECTION)
9286d38604fSBaptiste Daroussin 		print_otag(h, TAG_P, "c", "Pp");
9296d38604fSBaptiste Daroussin 
9306d38604fSBaptiste Daroussin 	/* Output whitespace before this text? */
9316d38604fSBaptiste Daroussin 
93261d06d6bSBaptiste Daroussin 	if (h->col && (h->flags & HTML_NOSPACE) == 0) {
93361d06d6bSBaptiste Daroussin 		if ( ! (HTML_KEEP & h->flags)) {
93461d06d6bSBaptiste Daroussin 			if (HTML_PREKEEP & h->flags)
93561d06d6bSBaptiste Daroussin 				h->flags |= HTML_KEEP;
93661d06d6bSBaptiste Daroussin 			print_endword(h);
93761d06d6bSBaptiste Daroussin 		} else
93861d06d6bSBaptiste Daroussin 			print_word(h, "&#x00A0;");
93961d06d6bSBaptiste Daroussin 	}
94061d06d6bSBaptiste Daroussin 
9416d38604fSBaptiste Daroussin 	/*
9426d38604fSBaptiste Daroussin 	 * Optionally switch fonts, optionally write a permalink, then
9436d38604fSBaptiste Daroussin 	 * print the text, optionally surrounded by HTML whitespace.
9446d38604fSBaptiste Daroussin 	 */
9456d38604fSBaptiste Daroussin 
94645a5aec3SBaptiste Daroussin 	assert(h->metaf == NULL);
94745a5aec3SBaptiste Daroussin 	print_metaf(h);
94861d06d6bSBaptiste Daroussin 	print_indent(h);
9496d38604fSBaptiste Daroussin 
9506d38604fSBaptiste Daroussin 	if (n != NULL && (href = html_make_id(n, 2)) != NULL) {
9516d38604fSBaptiste Daroussin 		t = print_otag(h, TAG_A, "chR", "permalink", href);
9526d38604fSBaptiste Daroussin 		free(href);
9536d38604fSBaptiste Daroussin 	} else
9546d38604fSBaptiste Daroussin 		t = NULL;
9556d38604fSBaptiste Daroussin 
95661d06d6bSBaptiste Daroussin 	if ( ! print_encode(h, word, NULL, 0)) {
95761d06d6bSBaptiste Daroussin 		if ( ! (h->flags & HTML_NONOSPACE))
95861d06d6bSBaptiste Daroussin 			h->flags &= ~HTML_NOSPACE;
95961d06d6bSBaptiste Daroussin 		h->flags &= ~HTML_NONEWLINE;
96061d06d6bSBaptiste Daroussin 	} else
96161d06d6bSBaptiste Daroussin 		h->flags |= HTML_NOSPACE | HTML_NONEWLINE;
96261d06d6bSBaptiste Daroussin 
96345a5aec3SBaptiste Daroussin 	if (h->metaf != NULL) {
96461d06d6bSBaptiste Daroussin 		print_tagq(h, h->metaf);
96561d06d6bSBaptiste Daroussin 		h->metaf = NULL;
9666d38604fSBaptiste Daroussin 	} else if (t != NULL)
9676d38604fSBaptiste Daroussin 		print_tagq(h, t);
96861d06d6bSBaptiste Daroussin 
96961d06d6bSBaptiste Daroussin 	h->flags &= ~HTML_IGNDELIM;
97061d06d6bSBaptiste Daroussin }
97161d06d6bSBaptiste Daroussin 
97261d06d6bSBaptiste Daroussin void
print_tagq(struct html * h,const struct tag * until)97361d06d6bSBaptiste Daroussin print_tagq(struct html *h, const struct tag *until)
97461d06d6bSBaptiste Daroussin {
9757295610fSBaptiste Daroussin 	struct tag	*this, *next;
97661d06d6bSBaptiste Daroussin 
9777295610fSBaptiste Daroussin 	for (this = h->tag; this != NULL; this = next) {
9787295610fSBaptiste Daroussin 		next = this == until ? NULL : this->next;
9797295610fSBaptiste Daroussin 		print_ctag(h, this);
98061d06d6bSBaptiste Daroussin 	}
98161d06d6bSBaptiste Daroussin }
98261d06d6bSBaptiste Daroussin 
9837295610fSBaptiste Daroussin /*
9847295610fSBaptiste Daroussin  * Close out all open elements up to but excluding suntil.
9857295610fSBaptiste Daroussin  * Note that a paragraph just inside stays open together with it
9867295610fSBaptiste Daroussin  * because paragraphs include subsequent phrasing content.
9877295610fSBaptiste Daroussin  */
98861d06d6bSBaptiste Daroussin void
print_stagq(struct html * h,const struct tag * suntil)98961d06d6bSBaptiste Daroussin print_stagq(struct html *h, const struct tag *suntil)
99061d06d6bSBaptiste Daroussin {
9917295610fSBaptiste Daroussin 	struct tag	*this, *next;
99261d06d6bSBaptiste Daroussin 
9937295610fSBaptiste Daroussin 	for (this = h->tag; this != NULL; this = next) {
9947295610fSBaptiste Daroussin 		next = this->next;
9957295610fSBaptiste Daroussin 		if (this == suntil || (next == suntil &&
9967295610fSBaptiste Daroussin 		    (this->tag == TAG_P || this->tag == TAG_PRE)))
9977295610fSBaptiste Daroussin 			break;
9987295610fSBaptiste Daroussin 		print_ctag(h, this);
99961d06d6bSBaptiste Daroussin 	}
100061d06d6bSBaptiste Daroussin }
100161d06d6bSBaptiste Daroussin 
100261d06d6bSBaptiste Daroussin 
100361d06d6bSBaptiste Daroussin /***********************************************************************
100461d06d6bSBaptiste Daroussin  * Low level output functions.
100561d06d6bSBaptiste Daroussin  * They implement line breaking using a short static buffer.
100661d06d6bSBaptiste Daroussin  ***********************************************************************/
100761d06d6bSBaptiste Daroussin 
100861d06d6bSBaptiste Daroussin /*
100961d06d6bSBaptiste Daroussin  * Buffer one HTML output byte.
101061d06d6bSBaptiste Daroussin  * If the buffer is full, flush and deactivate it and start a new line.
101161d06d6bSBaptiste Daroussin  * If the buffer is inactive, print directly.
101261d06d6bSBaptiste Daroussin  */
101361d06d6bSBaptiste Daroussin static void
print_byte(struct html * h,char c)101461d06d6bSBaptiste Daroussin print_byte(struct html *h, char c)
101561d06d6bSBaptiste Daroussin {
101661d06d6bSBaptiste Daroussin 	if ((h->flags & HTML_BUFFER) == 0) {
101761d06d6bSBaptiste Daroussin 		putchar(c);
101861d06d6bSBaptiste Daroussin 		h->col++;
101961d06d6bSBaptiste Daroussin 		return;
102061d06d6bSBaptiste Daroussin 	}
102161d06d6bSBaptiste Daroussin 
102261d06d6bSBaptiste Daroussin 	if (h->col + h->bufcol < sizeof(h->buf)) {
102361d06d6bSBaptiste Daroussin 		h->buf[h->bufcol++] = c;
102461d06d6bSBaptiste Daroussin 		return;
102561d06d6bSBaptiste Daroussin 	}
102661d06d6bSBaptiste Daroussin 
102761d06d6bSBaptiste Daroussin 	putchar('\n');
102861d06d6bSBaptiste Daroussin 	h->col = 0;
102961d06d6bSBaptiste Daroussin 	print_indent(h);
103061d06d6bSBaptiste Daroussin 	putchar(' ');
103161d06d6bSBaptiste Daroussin 	putchar(' ');
103261d06d6bSBaptiste Daroussin 	fwrite(h->buf, h->bufcol, 1, stdout);
103361d06d6bSBaptiste Daroussin 	putchar(c);
103461d06d6bSBaptiste Daroussin 	h->col = (h->indent + 1) * 2 + h->bufcol + 1;
103561d06d6bSBaptiste Daroussin 	h->bufcol = 0;
103661d06d6bSBaptiste Daroussin 	h->flags &= ~HTML_BUFFER;
103761d06d6bSBaptiste Daroussin }
103861d06d6bSBaptiste Daroussin 
103961d06d6bSBaptiste Daroussin /*
104061d06d6bSBaptiste Daroussin  * If something was printed on the current output line, end it.
104161d06d6bSBaptiste Daroussin  * Not to be called right after print_indent().
104261d06d6bSBaptiste Daroussin  */
104361d06d6bSBaptiste Daroussin void
print_endline(struct html * h)104461d06d6bSBaptiste Daroussin print_endline(struct html *h)
104561d06d6bSBaptiste Daroussin {
104661d06d6bSBaptiste Daroussin 	if (h->col == 0)
104761d06d6bSBaptiste Daroussin 		return;
104861d06d6bSBaptiste Daroussin 
104961d06d6bSBaptiste Daroussin 	if (h->bufcol) {
105061d06d6bSBaptiste Daroussin 		putchar(' ');
105161d06d6bSBaptiste Daroussin 		fwrite(h->buf, h->bufcol, 1, stdout);
105261d06d6bSBaptiste Daroussin 		h->bufcol = 0;
105361d06d6bSBaptiste Daroussin 	}
105461d06d6bSBaptiste Daroussin 	putchar('\n');
105561d06d6bSBaptiste Daroussin 	h->col = 0;
105661d06d6bSBaptiste Daroussin 	h->flags |= HTML_NOSPACE;
105761d06d6bSBaptiste Daroussin 	h->flags &= ~HTML_BUFFER;
105861d06d6bSBaptiste Daroussin }
105961d06d6bSBaptiste Daroussin 
106061d06d6bSBaptiste Daroussin /*
106161d06d6bSBaptiste Daroussin  * Flush the HTML output buffer.
106261d06d6bSBaptiste Daroussin  * If it is inactive, activate it.
106361d06d6bSBaptiste Daroussin  */
106461d06d6bSBaptiste Daroussin static void
print_endword(struct html * h)106561d06d6bSBaptiste Daroussin print_endword(struct html *h)
106661d06d6bSBaptiste Daroussin {
106761d06d6bSBaptiste Daroussin 	if (h->noindent) {
106861d06d6bSBaptiste Daroussin 		print_byte(h, ' ');
106961d06d6bSBaptiste Daroussin 		return;
107061d06d6bSBaptiste Daroussin 	}
107161d06d6bSBaptiste Daroussin 
107261d06d6bSBaptiste Daroussin 	if ((h->flags & HTML_BUFFER) == 0) {
107361d06d6bSBaptiste Daroussin 		h->col++;
107461d06d6bSBaptiste Daroussin 		h->flags |= HTML_BUFFER;
107561d06d6bSBaptiste Daroussin 	} else if (h->bufcol) {
107661d06d6bSBaptiste Daroussin 		putchar(' ');
107761d06d6bSBaptiste Daroussin 		fwrite(h->buf, h->bufcol, 1, stdout);
107861d06d6bSBaptiste Daroussin 		h->col += h->bufcol + 1;
107961d06d6bSBaptiste Daroussin 	}
108061d06d6bSBaptiste Daroussin 	h->bufcol = 0;
108161d06d6bSBaptiste Daroussin }
108261d06d6bSBaptiste Daroussin 
108361d06d6bSBaptiste Daroussin /*
108461d06d6bSBaptiste Daroussin  * If at the beginning of a new output line,
108561d06d6bSBaptiste Daroussin  * perform indentation and mark the line as containing output.
108661d06d6bSBaptiste Daroussin  * Make sure to really produce some output right afterwards,
108761d06d6bSBaptiste Daroussin  * but do not use print_otag() for producing it.
108861d06d6bSBaptiste Daroussin  */
108961d06d6bSBaptiste Daroussin static void
print_indent(struct html * h)109061d06d6bSBaptiste Daroussin print_indent(struct html *h)
109161d06d6bSBaptiste Daroussin {
109261d06d6bSBaptiste Daroussin 	size_t	 i;
109361d06d6bSBaptiste Daroussin 
10946d38604fSBaptiste Daroussin 	if (h->col || h->noindent)
109561d06d6bSBaptiste Daroussin 		return;
109661d06d6bSBaptiste Daroussin 
109761d06d6bSBaptiste Daroussin 	h->col = h->indent * 2;
109861d06d6bSBaptiste Daroussin 	for (i = 0; i < h->col; i++)
109961d06d6bSBaptiste Daroussin 		putchar(' ');
110061d06d6bSBaptiste Daroussin }
110161d06d6bSBaptiste Daroussin 
110261d06d6bSBaptiste Daroussin /*
110361d06d6bSBaptiste Daroussin  * Print or buffer some characters
110461d06d6bSBaptiste Daroussin  * depending on the current HTML output buffer state.
110561d06d6bSBaptiste Daroussin  */
110661d06d6bSBaptiste Daroussin static void
print_word(struct html * h,const char * cp)110761d06d6bSBaptiste Daroussin print_word(struct html *h, const char *cp)
110861d06d6bSBaptiste Daroussin {
110961d06d6bSBaptiste Daroussin 	while (*cp != '\0')
111061d06d6bSBaptiste Daroussin 		print_byte(h, *cp++);
111161d06d6bSBaptiste Daroussin }
1112