/*	$Id$ */
/*
 * Copyright (c) 2012--2021 Kristaps Dzonsons <kristaps@bsd.lv>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include "config.h"

#include <assert.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "kcgi.h"
#include "kcgihtml.h"

/*
 * Maximum size of printing a signed 64-bit integer.
 */
#define	INT_MAXSZ	 22

/*
 * A type of HTML5 element.
 * Note: we don't list transitional elements, though I do note them in
 * the tag array.
 */
enum	htype {
	TAG_FLOW, /* flow (block) element */
	TAG_PHRASE,/* phrasing (inline) element */
	TAG_VOID, /* auto-closing (e.g., INPUT) */
	TAG_INSTRUCTION /* instruction */
};

/*
 * A tag describes an HTML element and its properties.
 */
struct	tag {
	enum htype	 flags; 
	const char	*name;
};

static	const uint16_t entities[KENTITY__MAX] = {
	198, /* KENTITY_AElig */
	193, /* KENTITY_Aacute */
	194, /* KENTITY_Acirc */
	192, /* KENTITY_Agrave */
	197, /* KENTITY_Aring */
	195, /* KENTITY_Atilde */
	196, /* KENTITY_Auml */
	199, /* KENTITY_Ccedil */
	8225, /* KENTITY_Dagger */
	208, /* KENTITY_ETH */
	201, /* KENTITY_Eacute */
	202, /* KENTITY_Ecirc */
	200, /* KENTITY_Egrave */
	203, /* KENTITY_Euml */
	205, /* KENTITY_Iacute */
	206, /* KENTITY_Icirc */
	204, /* KENTITY_Igrave */
	207, /* KENTITY_Iuml */
	209, /* KENTITY_Ntilde */
	338, /* KENTITY_OElig */
	211, /* KENTITY_Oacute */
	212, /* KENTITY_Ocirc */
	210, /* KENTITY_Ograve */
	216, /* KENTITY_Oslash */
	213, /* KENTITY_Otilde */
	214, /* KENTITY_Ouml */
	352, /* KENTITY_Scaron */
	222, /* KENTITY_THORN */
	218, /* KENTITY_Uacute */
	219, /* KENTITY_Ucirc */
	217, /* KENTITY_Ugrave */
	220, /* KENTITY_Uuml */
	221, /* KENTITY_Yacute */
	376, /* KENTITY_Yuml */
	225, /* KENTITY_aacute */
	226, /* KENTITY_acirc */
	180, /* KENTITY_acute */
	230, /* KENTITY_aelig */
	224, /* KENTITY_agrave */
	38, /* KENTITY_amp */
	39, /* KENTITY_apos */
	229, /* KENTITY_aring */
	227, /* KENTITY_atilde */
	228, /* KENTITY_auml */
	8222, /* KENTITY_bdquo */
	166, /* KENTITY_brvbar */
	231, /* KENTITY_ccedil */
	184, /* KENTITY_cedil */
	162, /* KENTITY_cent */
	710, /* KENTITY_circ */
	169, /* KENTITY_copy */
	164, /* KENTITY_curren */
	8224, /* KENTITY_dagger */
	176, /* KENTITY_deg */
	247, /* KENTITY_divide */
	233, /* KENTITY_eacute */
	234, /* KENTITY_ecirc */
	232, /* KENTITY_egrave */
	8195, /* KENTITY_emsp */
	8194, /* KENTITY_ensp */
	240, /* KENTITY_eth */
	235, /* KENTITY_euml */
	8364, /* KENTITY_euro */
	189, /* KENTITY_frac12 */
	188, /* KENTITY_frac14 */
	190, /* KENTITY_frac34 */
	62, /* KENTITY_gt */
	8230, /* KENTITY_hellip */
	237, /* KENTITY_iacute */
	238, /* KENTITY_icirc */
	161, /* KENTITY_iexcl */
	236, /* KENTITY_igrave */
	191, /* KENTITY_iquest */
	239, /* KENTITY_iuml */
	171, /* KENTITY_laquo */
	8220, /* KENTITY_ldquo */
	8206, /* KENTITY_lrm */
	8249, /* KENTITY_lsaquo */
	8216, /* KENTITY_lsquo */
	60, /* KENTITY_lt */
	175, /* KENTITY_macr */
	8212, /* KENTITY_mdash */
	181, /* KENTITY_micro */
	183, /* KENTITY_middot */
	160, /* KENTITY_nbsp */
	8211, /* KENTITY_ndash */
	172, /* KENTITY_not */
	241, /* KENTITY_ntilde */
	243, /* KENTITY_oacute */
	244, /* KENTITY_ocirc */
	339, /* KENTITY_oelig */
	242, /* KENTITY_ograve */
	170, /* KENTITY_ordf */
	186, /* KENTITY_ordm */
	248, /* KENTITY_oslash */
	245, /* KENTITY_otilde */
	246, /* KENTITY_ouml */
	182, /* KENTITY_para */
	8240, /* KENTITY_permil */
	177, /* KENTITY_plusmn */
	163, /* KENTITY_pound */
	34, /* KENTITY_quot */
	187, /* KENTITY_raquo */
	8221, /* KENTITY_rdquo */
	174, /* KENTITY_reg */
	8207, /* KENTITY_rlm */
	8250, /* KENTITY_rsaquo */
	8217, /* KENTITY_rsquo */
	8218, /* KENTITY_sbquo */
	353, /* KENTITY_scaron */
	167, /* KENTITY_sect */
	173, /* KENTITY_shy */
	185, /* KENTITY_sup1 */
	178, /* KENTITY_sup2 */
	179, /* KENTITY_sup3 */
	223, /* KENTITY_szlig */
	8201, /* KENTITY_thinsp */
	254, /* KENTITY_thorn */
	732, /* KENTITY_tilde */
	215, /* KENTITY_times */
	8482, /* KENTITY_trade */
	250, /* KENTITY_uacute */
	251, /* KENTITY_ucirc */
	249, /* KENTITY_ugrave */
	168, /* KENTITY_uml */
	252, /* KENTITY_uuml */
	253, /* KENTITY_yacute */
	165, /* KENTITY_yen */
	255, /* KENTITY_yuml */
	8205, /* KENTITY_zwj */
	8204, /* KENTITY_zwnj */
};

static	const struct tag tags[KELEM__MAX] = {
	{ TAG_PHRASE, "a" }, /* KELEM_A */ /* XXX: TRANS */
	{ TAG_PHRASE, "abbr" }, /* KELEM_ABBR */
	{ TAG_PHRASE, "address" }, /* KELEM_ADDRESS */
	{ TAG_VOID, "area" }, /* KELEM_AREA */
	{ TAG_FLOW, "article" }, /* KELEM_ARTICLE */
	{ TAG_FLOW, "aside" }, /* KELEM_ASIDE */
	{ TAG_FLOW, "audio" }, /* KELEM_AUDIO */ /* XXX: TRANS */
	{ TAG_PHRASE, "b" }, /* KELEM_B */
	{ TAG_VOID, "base" }, /* KELEM_BASE */
	{ TAG_PHRASE, "bdi" }, /* KELEM_BDI */
	{ TAG_PHRASE, "bdo" }, /* KELEM_BDO */
	{ TAG_FLOW, "blockquote" }, /* KELEM_BLOCKQUOTE */
	{ TAG_FLOW, "body" }, /* KELEM_BODY */
	{ TAG_VOID, "br" }, /* KELEM_BR */
	{ TAG_PHRASE, "button" }, /* KELEM_BUTTON */
	{ TAG_FLOW, "canvas" }, /* KELEM_CANVAS */ /* XXX: TRANS */
	{ TAG_FLOW, "caption" }, /* KELEM_CAPTION */
	{ TAG_PHRASE, "cite" }, /* KELEM_CITE */
	{ TAG_PHRASE, "code" }, /* KELEM_CODE */
	{ TAG_VOID, "col" }, /* KELEM_COL */
	{ TAG_PHRASE, "colgroup" }, /* KELEM_COLGROUP */
	{ TAG_PHRASE, "data" }, /* KELEM_DATA*/
	{ TAG_PHRASE, "datalist" }, /* KELEM_DATALIST */
	{ TAG_FLOW, "dd" }, /* KELEM_DD */
	{ TAG_PHRASE, "del" }, /* KELEM_DEL */ /* XXX: TRANS */
	{ TAG_FLOW, "details" }, /* KELEM_DETAILS */
	{ TAG_PHRASE, "dfn" }, /* KELEM_DFN */
	{ TAG_FLOW, "dialog" }, /* KELEM_DIALOG */
	{ TAG_FLOW, "div" }, /* KELEM_DIV */
	{ TAG_FLOW, "dl" }, /* KELEM_DL */
	{ TAG_INSTRUCTION, "!DOCTYPE html" }, /* KELEM_DOCTYPE */
	{ TAG_FLOW, "dt" }, /* KELEM_DT */
	{ TAG_PHRASE, "em" }, /* KELEM_EM */
	{ TAG_VOID, "embed" }, /* KELEM_EMBED */
	{ TAG_FLOW, "fieldset" }, /* KELEM_FIELDSET */
	{ TAG_FLOW, "figcaption" }, /* KELEM_FIGCAPTION */
	{ TAG_FLOW, "figure" }, /* KELEM_FIGURE */
	{ TAG_FLOW, "footer" }, /* KELEM_FOOTER */
	{ TAG_FLOW, "form" }, /* KELEM_FORM */
	{ TAG_PHRASE, "h1" }, /* KELEM_H1 */
	{ TAG_PHRASE, "h2" }, /* KELEM_H2 */
	{ TAG_PHRASE, "h3" }, /* KELEM_H3 */
	{ TAG_PHRASE, "h4" }, /* KELEM_H4 */
	{ TAG_PHRASE, "h5" }, /* KELEM_H5 */
	{ TAG_PHRASE, "h6" }, /* KELEM_H6 */
	{ TAG_FLOW, "head" }, /* KELEM_HEAD */
	{ TAG_FLOW, "header" }, /* KELEM_HEADER */
	{ TAG_FLOW, "hgroup" }, /* KELEM_HGROUP */
	{ TAG_VOID, "hr" }, /* KELEM_HR */
	{ TAG_FLOW, "html" }, /* KELEM_HTML */
	{ TAG_PHRASE, "i" }, /* KELEM_I */
	{ TAG_PHRASE, "iframe" }, /* KELEM_IFRAME */
	{ TAG_VOID, "img" }, /* KELEM_IMG */
	{ TAG_VOID, "input" }, /* KELEM_INPUT */
	{ TAG_PHRASE, "ins" }, /* KELEM_INS */ /* XXX: TRANS */
	{ TAG_PHRASE, "kbd" }, /* KELEM_KBD */
	{ TAG_VOID, "keygen" }, /* KELEM_KEYGEN */
	{ TAG_PHRASE, "label" }, /* KELEM_LABEL */
	{ TAG_PHRASE, "legend" }, /* KELEM_LEGEND */
	{ TAG_FLOW, "li" }, /* KELEM_LI */
	{ TAG_VOID, "link" }, /* KELEM_LINK */
	{ TAG_FLOW, "main" }, /* KELEM_MAIN */
	{ TAG_FLOW, "map" }, /* KELEM_MAP */ /* XXX: TRANS */
	{ TAG_PHRASE, "mark" }, /* KELEM_MARK */
	{ TAG_FLOW, "menu" }, /* KELEM_MENU */
	{ TAG_VOID, "meta" }, /* KELEM_META */
	{ TAG_PHRASE, "meter" }, /* KELEM_METER */
	{ TAG_FLOW, "nav" }, /* KELEM_NAV */
	{ TAG_FLOW, "noscript" }, /* KELEM_NOSCRIPT */ /* XXX: TRANS */
	{ TAG_FLOW, "object" }, /* KELEM_OBJECT */ /* XXX: TRANS */
	{ TAG_FLOW, "ol" }, /* KELEM_OL */
	{ TAG_FLOW, "optgroup" }, /* KELEM_OPTGROUP */
	{ TAG_PHRASE, "option" }, /* KELEM_OPTION */
	{ TAG_PHRASE, "output" }, /* KELEM_OUTPUT */
	{ TAG_PHRASE, "p" }, /* KELEM_P */
	{ TAG_VOID, "param" }, /* KELEM_PARAM */
	{ TAG_PHRASE, "picture" }, /* KELEM_PICTURE */ /* XXX: TRANS */
	{ TAG_PHRASE, "pre" }, /* KELEM_PRE */
	{ TAG_PHRASE, "progress" }, /* KELEM_PROGRESS */
	{ TAG_PHRASE, "q" }, /* KELEM_Q */
	{ TAG_PHRASE, "rb" }, /* KELEM_RB */
	{ TAG_PHRASE, "rp" }, /* KELEM_RP */
	{ TAG_PHRASE, "rt" }, /* KELEM_RT */
	{ TAG_PHRASE, "rtc" }, /* KELEM_RTC */
	{ TAG_PHRASE, "ruby" }, /* KELEM_RUBY */
	{ TAG_PHRASE, "s" }, /* KELEM_S */
	{ TAG_PHRASE, "samp" }, /* KELEM_SAMP */
	{ TAG_FLOW, "script" }, /* KELEM_SCRIPT */
	{ TAG_FLOW, "section" }, /* KELEM_SECTION */
	{ TAG_FLOW, "select" }, /* KELEM_SELECT */
	{ TAG_PHRASE, "small" }, /* KELEM_SMALL */
	{ TAG_VOID, "source" }, /* KELEM_SOURCE */
	{ TAG_PHRASE, "span" }, /* KELEM_SPAN */
	{ TAG_PHRASE, "strong" }, /* KELEM_STRONG */
	{ TAG_FLOW, "style" }, /* KELEM_STYLE */
	{ TAG_PHRASE, "sub" }, /* KELEM_SUB */
	{ TAG_PHRASE, "summary" }, /* KELEM_SUMMARY */
	{ TAG_PHRASE, "sup" }, /* KELEM_SUP */
	{ TAG_FLOW, "table" }, /* KELEM_TABLE */
	{ TAG_FLOW, "tbody" }, /* KELEM_TBODY */
	{ TAG_FLOW, "td" }, /* KELEM_TD */
	{ TAG_PHRASE, "textarea" }, /* KELEM_TEXTAREA */
	{ TAG_FLOW, "tfoot" }, /* KELEM_TFOOT */
	{ TAG_FLOW, "th" }, /* KELEM_TH */
	{ TAG_FLOW, "thead" }, /* KELEM_THEAD */
	{ TAG_PHRASE, "time" }, /* KELEM_TIME */
	{ TAG_PHRASE, "title" }, /* KELEM_TITLE */
	{ TAG_FLOW, "tr" }, /* KELEM_TR */
	{ TAG_VOID, "track" }, /* KELEM_TRACK */
	{ TAG_PHRASE, "u" }, /* KELEM_U */
	{ TAG_FLOW, "ul" }, /* KELEM_UL */
	{ TAG_PHRASE, "var" }, /* KELEM_VAR */
	{ TAG_FLOW, "video" }, /* KELEM_VIDEO */ /* XXX: TRANS */
	{ TAG_VOID, "wbr" }, /* KELEM_WBR */
};

static	const char *const attrs[KATTR__MAX] = {
	"accept-charset", /* KATTR_ACCEPT_CHARSET */
	"accesskey", /* KATTR_ACCESSKEY */
	"action", /* KATTR_ACTION */
	"allowfullscreen", /* KATTR_ALLOWFULLSCREEN */
	"allowpaymentrequest", /* KATTR_ALLOWPAYMENTREQUEST */
	"alt", /* KATTR_ALT */
	"async", /* KATTR_ASYNC */
	"autocomplete", /* KATTR_AUTOCOMPLETE */
	"autofocus", /* KATTR_AUTOFOCUS */
	"autoplay", /* KATTR_AUTOPLAY */
	"border", /* KATTR_BORDER */
	"challenge", /* KATTR_CHALLENGE */
	"charset", /* KATTR_CHARSET */
	"checked", /* KATTR_CHECKED */
	"cite", /* KATTR_CITE */
	"class", /* KATTR_CLASS */
	"cols", /* KATTR_COLS */
	"colspan", /* KATTR_COLSPAN */
	"content", /* KATTR_CONTENT */
	"contenteditable", /* KATTR_CONTENTEDITABLE */
	"contextmenu", /* KATTR_CONTEXTMENU */
	"controls", /* KATTR_CONTROLS */
	"coords", /* KATTR_COORDS */
	"crossorigin", /* KATTR_CROSSORIGIN */
	"data", /* KATTR_DATA */
	"datetime", /* KATTR_DATETIME */
	"default", /* KATTR_DEFAULT */
	"defer", /* KATTR_DEFER */
	"dir", /* KATTR_DIR */
	"dirname", /* KATTR_DIRNAME */
	"disabled", /* KATTR_DISABLED */
	"download", /* KATTR_DOWNLOAD */
	"draggable", /* KATTR_DRAGGABLE */
	"dropzone", /* KATTR_DROPZONE */
	"enctype", /* KATTR_ENCTYPE */
	"for", /* KATTR_FOR */
	"form", /* KATTR_FORM */
	"formaction", /* KATTR_FORMACTION */
	"formenctype", /* KATTR_FORMENCTYPE */
	"formmethod", /* KATTR_FORMMETHOD */
	"formnovalidate", /* KATTR_FORMNOVALIDATE */
	"formtarget", /* KATTR_FORMTARGET */
	"header", /* KATTR_HEADER */
	"headers", /* KATTR_HEADERS */
	"height", /* KATTR_HEIGHT */
	"hidden", /* KATTR_HIDDEN */
	"high", /* KATTR_HIGH */
	"href", /* KATTR_HREF */
	"hreflang", /* KATTR_HREFLANG */
	"http-equiv", /* KATTR_HTTP_EQUIV */
	"icon", /* KATTR_ICON */
	"id", /* KATTR_ID */
	"ismap", /* KATTR_ISMAP */
	"keytype", /* KATTR_KEYTYPE */
	"kind", /* KATTR_KIND */
	"label", /* KATTR_LABEL */
	"lang", /* KATTR_LANG */
	"language", /* KATTR_LANGUAGE */
	"list", /* KATTR_LIST */
	"longdesc", /* KATTR_LONGDESC */
	"loop", /* KATTR_LOOP */
	"low", /* KATTR_LOW */
	"manifest", /* KATTR_MANIFEST */
	"max", /* KATTR_MAX */
	"maxlength", /* KATTR_MAXLENGTH */
	"media", /* KATTR_MEDIA */
	"mediagroup", /* KATTR_MEDIAGROUP */
	"method", /* KATTR_METHOD */
	"min", /* KATTR_MIN */
	"minlength", /* KATTR_MINLENGTH */
	"multiple", /* KATTR_MULTIPLE */
	"muted", /* KATTR_MUTED */
	"name", /* KATTR_NAME */
	"nonce", /* KATTR_NONCE */
	"novalidate", /* KATTR_NOVALIDATE */
	"onabort", /* KATTR_ONABORT */
	"onafterprint", /* KATTR_ONAFTERPRINT */
	"onauxclick", /* KATTR_ONAUXCLICK */
	"onbeforeprint", /* KATTR_ONBEFOREPRINT */
	"onbeforeunload", /* KATTR_ONBEFOREUNLOAD */
	"onblur", /* KATTR_ONBLUR */
	"oncancel", /* KATTR_ONCANCEL */
	"oncanplay", /* KATTR_ONCANPLAY */
	"oncanplaythrough", /* KATTR_ONCANPLAYTHROUGH */
	"onchange", /* KATTR_ONCHANGE */
	"onclick", /* KATTR_ONCLICK */
	"onclose", /* KATTR_ONCLOSE */
	"oncontextmenu", /* KATTR_ONCONTEXTMENU */
	"oncuechange", /* KATTR_ONCUECHANGE */
	"oncut", /* KATTR_ONCUT */
	"ondblclick", /* KATTR_ONDBLCLICK */
	"ondrag", /* KATTR_ONDRAG */
	"ondragend", /* KATTR_ONDRAGEND */
	"ondragenter", /* KATTR_ONDRAGENTER */
	"ondragexit", /* KATTR_ONDRAGEXIT */
	"ondragleave", /* KATTR_ONDRAGLEAVE */
	"ondragover", /* KATTR_ONDRAGOVER */
	"ondragstart", /* KATTR_ONDRAGSTART */
	"ondrop", /* KATTR_ONDROP */
	"ondurationchange", /* KATTR_ONDURATIONCHANGE */
	"onemptied", /* KATTR_ONEMPTIED */
	"onended", /* KATTR_ONENDED */
	"onerror", /* KATTR_ONERROR */
	"onfocus", /* KATTR_ONFOCUS */
	"onhashchange", /* KATTR_ONHASHCHANGE */
	"oninput", /* KATTR_ONINPUT */
	"oninvalid", /* KATTR_ONINVALID */
	"onkeydown", /* KATTR_ONKEYDOWN */
	"onkeypress", /* KATTR_ONKEYPRESS */
	"onkeyup", /* KATTR_ONKEYUP */
	"onload", /* KATTR_ONLOAD */
	"onloadeddata", /* KATTR_ONLOADEDDATA */
	"onloadedmetadata", /* KATTR_ONLOADEDMETADATA */
	"onloadstart", /* KATTR_ONLOADSTART */
	"onmessage", /* KATTR_ONMESSAGE */
	"onmousedown", /* KATTR_ONMOUSEDOWN */
	"onmouseenter", /* KATTR_ONMOUSEENTER */
	"onmouseleave", /* KATTR_ONMOUSELEAVE */
	"onmousemove", /* KATTR_ONMOUSEMOVE */
	"onmouseout", /* KATTR_ONMOUSEOUT */
	"onmouseover", /* KATTR_ONMOUSEOVER */
	"onmouseup", /* KATTR_ONMOUSEUP */
	"onmousewheel", /* KATTR_ONMOUSEWHEEL */
	"onoffline", /* KATTR_ONOFFLINE */
	"ononline", /* KATTR_ONONLINE */
	"onpagehide", /* KATTR_ONPAGEHIDE */
	"onpageshow", /* KATTR_ONPAGESHOW */
	"onpaste", /* KATTR_ONPASTE */
	"onpause", /* KATTR_ONPAUSE */
	"onplay", /* KATTR_ONPLAY */
	"onplaying", /* KATTR_ONPLAYING */
	"onpopstate", /* KATTR_ONPOPSTATE */
	"onprogress", /* KATTR_ONPROGRESS */
	"onratechange", /* KATTR_ONRATECHANGE */
	"onreadystatechange", /* KATTR_ONREADYSTATECHANGE */
	"onreset", /* KATTR_ONRESET */
	"onresize", /* KATTR_ONRESIZE */
	"onscroll", /* KATTR_ONSCROLL */
	"onseeked", /* KATTR_ONSEEKED */
	"onseeking", /* KATTR_ONSEEKING */
	"onselect", /* KATTR_ONSELECT */
	"onshow", /* KATTR_ONSHOW */
	"onstalled", /* KATTR_ONSTALLED */
	"onstorage", /* KATTR_ONSTORAGE */
	"onsubmit", /* KATTR_ONSUBMIT */
	"onsuspend", /* KATTR_ONSUSPEND */
	"ontimeupdate", /* KATTR_ONTIMEUPDATE */
	"ontoggle", /* KATTR_ONTOGGLE */
	"onunload", /* KATTR_ONUNLOAD */
	"onvolumechange", /* KATTR_ONVOLUMECHANGE */
	"onwaiting", /* KATTR_ONWAITING */
	"onwheel", /* KATTR_ONWHEEL */
	"open", /* KATTR_OPEN */
	"optimum", /* KATTR_OPTIMUM */
	"pattern", /* KATTR_PATTERN */
	"placeholder", /* KATTR_PLACEHOLDER */
	"poster", /* KATTR_POSTER */
	"preload", /* KATTR_PRELOAD */
	"radiogroup", /* KATTR_RADIOGROUP */
	"readonly", /* KATTR_READONLY */
	"referrerpolicy", /* KATTR_REFERRERPOLICY */
	"rel", /* KATTR_REL */
	"required", /* KATTR_REQUIRED */
	"rev", /* KATTR_REV */
	"reversed", /* KATTR_REVERSED */
	"role", /* KATTR_ROLE */
	"rows", /* KATTR_ROWS */
	"rowspan", /* KATTR_ROWSPAN */
	"sandbox", /* KATTR_SANDBOX */
	"scope", /* KATTR_SCOPE */
	"seamless", /* KATTR_SEAMLESS */
	"selected", /* KATTR_SELECTED */
	"shape", /* KATTR_SHAPE */
	"size", /* KATTR_SIZE */
	"sizes", /* KATTR_SIZES */
	"span", /* KATTR_SPAN */
	"spellcheck", /* KATTR_SPELLCHECK */
	"src", /* KATTR_SRC */
	"srcdoc", /* KATTR_SRCDOC */
	"srclang", /* KATTR_SRCLANG */
	"srcset", /* KATTR_SRCSET */
	"start", /* KATTR_START */
	"step", /* KATTR_STEP */
	"style", /* KATTR_STYLE */
	"tabindex", /* KATTR_TABINDEX */
	"target", /* KATTR_TARGET */
	"title", /* KATTR_TITLE */
	"translate", /* KATTR_TRANSLATE */
	"type", /* KATTR_TYPE */
	"typemustmatch", /* KATTR_TYPEMUSTMATCH */
	"usemap", /* KATTR_USEMAP */
	"value", /* KATTR_VALUE */
	"width", /* KATTR_WIDTH */
	"wrap", /* KATTR_WRAP */
};

size_t
khtml_elemat(const struct khtmlreq *req)
{

	return req->elemsz;
}

enum kcgi_err
khtml_elem(struct khtmlreq *req, enum kelem elem)
{

	return khtml_attr(req, elem, KATTR__MAX);
}

/*
 * Open a tag---only used when pretty-printing and a noop otherwise.
 * If we're a flow tag, emit a newline (unless already omitted). 
 * Then if we're at a newline regardless of tag type, indent properly to
 * the point where we'll omit the tag name.
 */
static enum kcgi_err
khtml_flow_open(struct khtmlreq *req, enum kelem elem)
{
	size_t		 i;
	enum kcgi_err	 er;

	if ( ! (KHTML_PRETTY & req->opts))
		return(KCGI_OK);

	if (TAG_FLOW == tags[elem].flags && ! req->newln) {
		if (KCGI_OK != (er = kcgi_writer_putc(req->arg, '\n')))
			return(er);
		req->newln = 1;
	}

	if (req->newln)
		for (i = 0; i < req->elemsz; i++) 
			if (KCGI_OK != (er = 
			    kcgi_writer_puts(req->arg, "  ")))
				return(er);

	req->newln = 0;
	return(KCGI_OK);
}

/*
 * If we're pretty-printing and closing a flow or instruction tag, emit
 * a newline.
 * Otherwise do nothing.
 * Returns the usual write error code.
 */
static enum kcgi_err
khtml_flow_close(struct khtmlreq *req, enum kelem elem)
{
	enum kcgi_err	 er;

	if ( ! (KHTML_PRETTY & req->opts))
		return(KCGI_OK);

	if (TAG_FLOW == tags[elem].flags ||
	    TAG_INSTRUCTION == tags[elem].flags) {
		if (KCGI_OK != (er = kcgi_writer_putc(req->arg, '\n')))
			return(er);
		req->newln = 1;
	} else
		req->newln = 0;

	return(KCGI_OK);
}

enum kcgi_err
khtml_attrx(struct khtmlreq *req, enum kelem elem, ...)
{
	va_list		 ap;
	enum kattr	 at;
	enum kcgi_err	 er = KCGI_OK;

	if (tags[elem].flags != TAG_VOID &&
	    tags[elem].flags != TAG_INSTRUCTION &&
	    req->elemsz >= KHTML_STACK_MAX) {
		kutil_warnx(NULL, NULL, 
			"maximum html stack size exceeded");
		return KCGI_ENOMEM;
	}

	if ((er = khtml_flow_open(req, elem)) != KCGI_OK)
		return er;
	if ((er = kcgi_writer_putc(req->arg, '<')) != KCGI_OK)
		return er;
	if ((er = kcgi_writer_puts(req->arg, tags[elem].name)) != KCGI_OK)
		return er;

	va_start(ap, elem);
	while ((at = va_arg(ap, enum kattr)) != KATTR__MAX) {
		if ((er = kcgi_writer_putc(req->arg, ' ')) != KCGI_OK)
			goto out;
		if ((er = kcgi_writer_puts(req->arg, attrs[at])) != KCGI_OK)
			goto out;
		if ((er = kcgi_writer_puts(req->arg, "=\"")) != KCGI_OK)
			goto out;

		switch (va_arg(ap, enum kattrx)) {
		case KATTRX_STRING:
			er = khtml_puts(req, va_arg(ap, char *));
			break;
		case KATTRX_INT:
			er = khtml_int(req, va_arg(ap, int64_t));
			break;
		case KATTRX_DOUBLE:
			er = khtml_double(req, va_arg(ap, double));
			break;
		}
		if (er != KCGI_OK)
			goto out;
		if ((er = kcgi_writer_putc(req->arg, '"')) != KCGI_OK)
			goto out;
	}
	va_end(ap);

	if (tags[elem].flags == TAG_VOID &&
	    (er = kcgi_writer_putc(req->arg, '/')) != KCGI_OK)
		return er;
	if ((er = kcgi_writer_putc(req->arg, '>')) != KCGI_OK)
		return er;
	if ((er = khtml_flow_close(req, elem)) != KCGI_OK)
		return er;

	if (tags[elem].flags != TAG_VOID &&
	    tags[elem].flags != TAG_INSTRUCTION)
		req->elems[req->elemsz++] = elem;

	return KCGI_OK;
out:
	va_end(ap);
	return er;
}

enum kcgi_err
khtml_attr(struct khtmlreq *req, enum kelem elem, ...)
{
	va_list		 ap;
	enum kattr	 at;
	const char	*cp;
	enum kcgi_err	 er;

	if (tags[elem].flags != TAG_VOID &&
	    tags[elem].flags != TAG_INSTRUCTION &&
	    req->elemsz >= KHTML_STACK_MAX) {
		kutil_warnx(NULL, NULL, 
			"maximum html stack size exceeded");
		return KCGI_ENOMEM;
	}

	if ((er = khtml_flow_open(req, elem)) != KCGI_OK)
		return er;
	if ((er = kcgi_writer_putc(req->arg, '<')) != KCGI_OK)
		return er;
	if ((er = kcgi_writer_puts(req->arg, tags[elem].name)) != KCGI_OK)
		return er;

	va_start(ap, elem);
	while ((at = va_arg(ap, enum kattr)) != KATTR__MAX) {
		cp = va_arg(ap, char *);

		/*
		 * FIXME: shouldn't we allow situations where there's no
		 * value, like <option selected>?
		 * I don't know if this is valid XML.
		 * Meanwhile, don't let it happen.
		 */

		assert(cp != NULL);
		if ((er = kcgi_writer_putc(req->arg, ' ')) != KCGI_OK)
			goto out;
		if ((er = kcgi_writer_puts(req->arg, attrs[at])) != KCGI_OK)
			goto out;
		if ((er = kcgi_writer_puts(req->arg, "=\"")) != KCGI_OK)
			goto out;
		if ((er = khtml_puts(req, cp)) != KCGI_OK)
			goto out;
		if ((er = kcgi_writer_putc(req->arg, '"')) != KCGI_OK)
			goto out;

	}
	va_end(ap);

	if (tags[elem].flags == TAG_VOID &&
	    (er = kcgi_writer_putc(req->arg, '/')) != KCGI_OK)
		return er;
	if ((er = kcgi_writer_putc(req->arg, '>')) != KCGI_OK)
		return er;
	if ((er = khtml_flow_close(req, elem)) != KCGI_OK)
		return er;

	if (tags[elem].flags != TAG_VOID &&
	    tags[elem].flags != TAG_INSTRUCTION)
		req->elems[req->elemsz++] = elem;

	return KCGI_OK;
out:
	va_end(ap);
	return er;
}

enum kcgi_err
khtml_closeelem(struct khtmlreq *req, size_t sz)
{
	size_t		 i;
	enum kcgi_err	 er;

	if (sz == 0)
		sz = req->elemsz;
	if (sz > req->elemsz)
		sz = req->elemsz;

	for (i = 0; i < sz; i++) {
		assert(req->elemsz);
		req->elemsz--;
		er = khtml_flow_open(req, req->elems[req->elemsz]);
		if (er != KCGI_OK)
			return er;
		er = kcgi_writer_puts(req->arg, "</");
		if (er != KCGI_OK)
			return er;
		er = kcgi_writer_puts(req->arg,
			tags[req->elems[req->elemsz]].name);
		if (er != KCGI_OK)
			return er;
		er = kcgi_writer_putc(req->arg, '>');
		if (er != KCGI_OK)
			return er;
		er = khtml_flow_close(req, req->elems[req->elemsz]);
		if (er != KCGI_OK)
			return er;
	}

	return KCGI_OK;
}

enum kcgi_err
khtml_closeto(struct khtmlreq *req, size_t pos)
{

	if (pos >= req->elemsz)
		return KCGI_OK;
	return khtml_closeelem(req, req->elemsz - pos);
}

enum kcgi_err
khtml_entity(struct khtmlreq *req, enum kentity entity)
{

	assert(entity < KENTITY__MAX);
	return khtml_ncr(req, entities[entity]);
}

enum kcgi_err
khtml_ncr(struct khtmlreq *req, uint32_t ncr)
{
	char	 	 buf[INT_MAXSZ];
	enum kcgi_err	 er;

	/* 
	 * Don't check whether ncr is a valid unicode entity.
	 * Right now these only go up to 150 000 or so.
	 */

	snprintf(buf, sizeof(buf), "%" PRIx32, ncr);
	if ((er = kcgi_writer_puts(req->arg, "&#x")) != KCGI_OK)
		return er;
	if ((er = kcgi_writer_puts(req->arg, buf)) != KCGI_OK)
		return er;
	return kcgi_writer_putc(req->arg, ';');
}

enum kcgi_err
khtml_double(struct khtmlreq *req, double val)
{
	char	 buf[256];

	(void)snprintf(buf, sizeof(buf), "%g", val);
	return(khtml_puts(req, buf));
}

enum kcgi_err
khtml_int(struct khtmlreq *req, int64_t val)
{
	char	 buf[INT_MAXSZ];

	(void)snprintf(buf, sizeof(buf), "%" PRId64, val);
	return(khtml_puts(req, buf));
}

enum kcgi_err
khtml_putc(struct khtmlreq *r, char c)
{
	enum kcgi_err	 er;

	switch (c) {
	case ('>'):
		er = khtml_entity(r, KENTITY_gt);
		break;
	case ('&'):
		er = khtml_entity(r, KENTITY_amp);
		break;
	case ('<'):
		er = khtml_entity(r, KENTITY_lt);
		break;
	case ('"'):
		er = khtml_entity(r, KENTITY_quot);
		break;
	case ('\''):
		er = khtml_ncr(r, 39);
		break;
	default:
		er = kcgi_writer_putc(r->arg, c);
		break;
	}

	return(er);
}

enum kcgi_err
khtml_write(const char *cp, size_t sz, void *arg)
{
	struct khtmlreq	*r = arg;
	size_t		 i;
	enum kcgi_err	 er;

	if (cp == NULL || sz == 0)
		return KCGI_OK;

	for (i = 0; i < sz; i++) 
		if ((er = khtml_putc(r, cp[i])) != KCGI_OK)
			return er;

	return KCGI_OK;
}

enum kcgi_err
khtml_printf(struct khtmlreq *req, const char *fmt, ...)
{
	char		*buf;
	int		 len;
	va_list		 ap;
	enum kcgi_err	 er;

	if (fmt == NULL)
		return KCGI_OK;

	va_start(ap, fmt);
	len = vasprintf(&buf, fmt, ap);
	va_end(ap);

	if (len < 0)
		return KCGI_ENOMEM;

	er = khtml_write(buf, (size_t)len, req);
	free(buf);
	return er;
}

enum kcgi_err
khtml_puts(struct khtmlreq *req, const char *cp)
{

	if (cp == NULL)
		return KCGI_OK;
	return khtml_write(cp, strlen(cp), req);
}


enum kcgi_err
khtml_open(struct khtmlreq *r, struct kreq *req, int opts)
{

	memset(r, 0, sizeof(struct khtmlreq));
	if ((r->arg = kcgi_writer_get(req, 0)) == NULL)
		return KCGI_ENOMEM;

	r->opts = opts;
	return KCGI_OK;
}

enum kcgi_err
khtml_close(struct khtmlreq *r)
{
	enum kcgi_err	 er;

	er = khtml_closeelem(r, 0);
	kcgi_writer_free(r->arg);
	r->arg = NULL;
	return er;
}
