/*********************************************************************
html-tags.c: parse the html tags and attributes.
This was originally handled by the tidy library, but tidy is no longer maintained.
Bugs are accumulating, and we continue to work around those bugs,
but we can't continue along this path. Thus I wrote this html tag scanner.
I believe it is easier, in the long run, to roll our own, rather than using
tidy, or any other library.
Note that we can paste the tags directly into the edbrowse tree;
any other library, including tidy, builds its own tree of nodes,
which we must then import into the edbrowse tree of tags.
*********************************************************************/

#include "eb.h"

#include <pthread.h>
#include <signal.h>

// This makes a lot of output; maybe it should go to a file like debug css does
bool debugScanner;
bool browseMail;

// Start with the list of available tags, in alphabetical order.
// First one has to be the unknown tag. Second has to be <a>.
// whitespace bits: open nl, open para, close nl, close para.
// property bits: innerHTML,text is invisible, closing tag is insignificant.
static const struct tagInfo availableTags[] = {
	{"0unknown", "an html entity", TAGACT_UNKNOWN, 5, 1},
	{"a", "an anchor", TAGACT_A, 0, 1},
	{"abbr", "an abbreviation", TAGACT_JS, 0, 0},
	{"address", "an address block", TAGACT_NOP, 1, 0},
	{"area", "an image map area", TAGACT_AREA, 0, 4},
	{"article", "an article", TAGACT_HE, 10, 1},
	{"audio", "audio passage", TAGACT_MUSIC, 0, 0},
	{"b", "bold text", TAGACT_B, 0, 0},
	{"base", "base reference for relative URLs", TAGACT_BASE, 0, 4},
	{"bgsound", "background music", TAGACT_MUSIC, 0, 0},
	{"blockquote", "a quoted section", TAGACT_BQ, 0, 1},
	{"body", "the html body", TAGACT_BODY, 10, 5},
	{"br", "a line break", TAGACT_BR, 1, 4},
	{"button", "a button", TAGACT_INPUT, 0, 1},
	{"canvas", "a canvas", TAGACT_CANVAS, 0, 1},
	{"caption", "a caption", TAGACT_NOP, 5, 0},
	{"cdata", "xml cdata", TAGACT_CDATA, 0, 2},
	{"center", "centered text", TAGACT_P, 2, 5},
	{"cite", "a citation", TAGACT_NOP, 0, 0},
	{"code", "a block of code", TAGACT_CODE, 0, 0},
	{"comment", "a comment", TAGACT_COMMENT, 0, 2},
	{"datalist", "an input list", TAGACT_DATAL, 0, 0},
	{"dd", "a definition", TAGACT_DD, 1, 4},
	{"del", "deleted text", TAGACT_DEL, 0, 0},
	{"details", "details", TAGACT_DET, 10, 1},
	{"dfn", "definition text", TAGACT_JS, 0, 0},
	{"dir", "a directory list", TAGACT_NOP, 5, 0},
	{"div", "a divided section", TAGACT_DIV, 5, 1},
	{"dl", "a definition list", TAGACT_DL, 10, 1},
	{"doctype", "doctype", TAGACT_DOCTYPE, 0, 0},
	{"document", "a document", TAGACT_DOC, 5, 1},
	{"dt", "a term", TAGACT_DT, 2, 4},
	{"element", "an input element", TAGACT_INPUT, 0, 4},
	{"em", "emphasized text", TAGACT_EM, 0, 0},
	{"embed", "embedded html", TAGACT_MUSIC, 0, 0},
	{"fieldset", "a paragraph", TAGACT_NOP, 10, 1},
	{"figcaption", "a figure caption", TAGACT_NOP, 10, 0},
	{"figure", "a figure", TAGACT_NOP, 10, 0},
	{"fixed", "a fixed presentation", TAGACT_NOP, 1, 0},
	{"font", "a font", TAGACT_NOP, 0, 0},
	{"footer", "a footer", TAGACT_FOOTER, 2, 5},
	{"form", "a form", TAGACT_FORM, 10, 1},
	{"fragment", "a document fragment", TAGACT_FRAG, 5, 1},
	{"frame", "a frame", TAGACT_FRAME, 2, 0},
	{"frameset", "a frame set", TAGACT_JS, 0, 0},
	{"h1", "a level 1 header", TAGACT_H, 10, 1},
	{"h2", "a level 2 header", TAGACT_H, 10, 1},
	{"h3", "a level 3 header", TAGACT_H, 10, 1},
	{"h4", "a level 4 header", TAGACT_H, 10, 1},
	{"h5", "a level 5 header", TAGACT_H, 10, 1},
	{"h6", "a level 6 header", TAGACT_H, 10, 1},
	{"head", "the html header information", TAGACT_HEAD, 10, 5},
	{"header", "a header", TAGACT_HEADER, 2, 5},
	{"hr", "a horizontal line", TAGACT_HR, 5, 4},
	{"html", "html", TAGACT_HTML, 0, 0},
	{"htmlanchorelement", "an anchor element", TAGACT_A, 0, 1},
	{"i", "italicized text", TAGACT_I, 0, 0},
	{"iframe", "a frame", TAGACT_FRAME, 2, 1},
	{"image", "an image", TAGACT_IMAGE, 0, 4},
	{"img", "an image", TAGACT_IMAGE, 0, 4},
	{"input", "an input item", TAGACT_INPUT, 0, 4},
	{"ins", "inserted text", TAGACT_INS, 0, 0},
	{"kbd", "keyboard text", TAGACT_JS, 0, 0},
	{"label", "a label", TAGACT_LABEL, 0, 0},
	{"legend", "a legend", TAGACT_HE, 0, 0},
	{"li", "a list item", TAGACT_LI, 1, 5},
	{"link", "a link tag", TAGACT_LINK, 0, 4},
	{"listing", "a listing", TAGACT_PRE, 1, 0},
	{"map", "a map of images", TAGACT_NOP, 5, 0},
	{"menu", "a menu", TAGACT_NOP, 5, 0},
	{"meta", "a meta tag", TAGACT_META, 0, 4},
	{"nav", "a navigation section", TAGACT_DIV, 5, 1},
	{"noembed", "no embed section", TAGACT_NOP, 0, 2},
	{"noframes", "no frames section", TAGACT_NOP, 0, 2},
	{"noscript", "no script section", TAGACT_NOSCRIPT, 0, 2},
	{"object", "an html object", TAGACT_OBJECT, 5, 3},
	{"ol", "a numbered list", TAGACT_OL, 10, 1},
	{"optgroup", "an optiongroup", TAGACT_OPTG, 0, 0},
	{"option", "a select option", TAGACT_OPTION, 0, 0},
	{"ovb", "an overbar", TAGACT_OVB, 0, 0},
	{"p", "a paragraph", TAGACT_P, 10, 1},
	{"pre", "a preformatted section", TAGACT_PRE, 10, 0},
	{"q", "quoted text", TAGACT_JS, 0, 0},
	{"s", "strikethrough text", TAGACT_S, 0, 0},
	{"samp", "a block of sample text", TAGACT_NOP, 0, 0},
	{"script", "a script", TAGACT_SCRIPT, 0, 3},
	{"section", "an html section", TAGACT_HE, 10, 1},
	{"select", "an option list", TAGACT_SELECT, 0, 0},
	{"source", "source of audio or video", TAGACT_SOURCE, 0, 4},
	{"span", "an html span", TAGACT_SPAN, 0, 1},
	{"strike", "emphasized text", TAGACT_JS, 0, 0},
	{"strong", "emphasized text", TAGACT_STRONG, 0, 0},
	{"style", "a style tag", TAGACT_STYLE, 0, 2},
	{"sub", "a subscript", TAGACT_SUB, 0, 0},
	{"summary", "summary of details", TAGACT_SUMMARY, 10, 1},
	{"sup", "a superscript", TAGACT_SUP, 0, 0},
	{"svg", "an svg image", TAGACT_SVG, 0, 1},
	{"table", "a table", TAGACT_TABLE, 10, 1},
	{"tbody", "a table body", TAGACT_TBODY, 0, 1},
	{"td", "a table entry", TAGACT_TD, 0, 1},
	{"template", "a template", TAGACT_TEMPLATE, 0, 0},
	{"text", "a text section", TAGACT_TEXT, 0, 4},
	{"textarea", "an input text area", TAGACT_TA, 0, 0},
	{"tfoot", "a table foot", TAGACT_TFOOT, 0, 1},
	{"th", "a table heading", TAGACT_TD, 0, 1},
	{"thead", "a table head", TAGACT_THEAD, 0, 1},
	{"time", "the title", TAGACT_TIME, 0, 0},
	{"title", "the title", TAGACT_TITLE, 0, 0},
	{"tr", "a table row", TAGACT_TR, 5, 1},
	{"tt", "teletype", TAGACT_NOP, 0, 0},
	{"u", "underlined text", TAGACT_U, 0, 0},
	{"ul", "a bullet list", TAGACT_UL, 10, 1},
	{"var", "variable text", TAGACT_JS, 0, 0},
	{"video", "video passage", TAGACT_MUSIC, 0, 0},
	{"xmp", "an example", TAGACT_PRE, 1, 0},
};
static const int availableTagCount = sizeof(availableTags) / sizeof(struct tagInfo);

const struct tagInfo *name2tagInfo(const char *name)
{
// some bookmark files have thousands of tags;
// here is an efficiency for <a>
    if(name[1] == 0 && (name[0] == 'a' || name[0] == 'A'))
        return availableTags + 1;

// lower case, so far the longest tag is 17
    char namelow[20];
    int n = strlen(name) + 1;
    if(n > sizeof(namelow)) return 0;
   memcpy(namelow, name, n);
    caseShift(namelow, 'l');

// Binary search, as you would imagine. Since we already checked for <a>,
// we can put the left mark there.
    int i, l = 1, r = availableTagCount, rc;
    const struct tagInfo *ti;
    while(r - l > 1) {
        i = (l + r) / 2;
        ti = availableTags + i;
        rc = strcmp(ti->name, namelow);
        if(!rc) return ti;
        if(rc < 0)l = i; else r = i;
    }
    return 0;
}

static const char htmltag[] = "html";
static const char innerhtmltag[] = "innerhtml";
static const char headtag[] = "head";
static const char bodytag[] = "body";
static const char innerbodytag[] = "innerbody";

// report debugging information or errors
static void scannerInfo1(const char *msg, int n)
{
	if(debugScanner) printf(msg, n), nl();
}

static void scannerInfo2(const char *msg, const char *w)
{
	if(debugScanner) printf(msg, w), nl();
}

static void scannerInfo3(const char *msg, const char *w, int n)
{
	if(debugScanner) printf(msg, w, n), nl();
}

static void scannerError1(const char *msg, int n)
{
	if(debugScanner) printf(msg, n), nl();
	else if(cf->xmlMode) debugPrint(3, msg, n);
}

static void scannerError2(const char *msg, const char *w)
{
	if(debugScanner) printf(msg, w), nl();
	else if(cf->xmlMode) debugPrint(3, msg, w);
}

static void findAttributes(const char *start, const char *end);
static void setAttrFromHTML(const char *a1, const char *a2, const char *v1, const char *v2);
static char *pullAnd(const char *start, const char *end, bool intag);
static unsigned andLookup(char *entity, char *v);
static void pushState(const char *start, bool head_ok);
static void freeTag(Tag *t);

static Tag *working_t, *lasttext;
static int ln; // line number
static int start_idx;
static Tag *overnode;

// is this generated html, rather than from a web page?
// This is only used for the duration of the html scanner.
static bool htmlGenerated;

// 0 prehtml 1 prehead, 2 inhead, 3 posthead, 4 inbody, 5 postbody 6 posthtml
static uchar headbody;
// This isn't counting 13 bodies buried in the basement, it tracks <body>
// in <body>, which shouldn't happen, but it does.
static int bodycount, htmlcount;
static bool premode;

static bool atWall;
// compress whitespace
static void compress(char *s)
{
	int i, j;
	char c;
	bool space = atWall;
	for (i = j = 0; (c = s[i]); ++i) {
		if (isspaceByte(c)) {
			if (!space)
				s[j++] = ' ', space = true;
			continue;
		}
		s[j++] = c, space = false;
	}
	s[j] = 0;
}

static struct opentag {
	struct opentag *next;
	char name[MAXTAGNAME];
	char lowname[MAXTAGNAME];
	const char *start; // for innerHTML
	Tag *t;
} *stack;

static struct opentag *balance(const char *name)
{
	struct opentag *k = stack;
	while(k) {
		if(stringEqual(name, k->lowname)) return k;
		k = k->next;
	}
	return 0;
}

static const struct specialtag {
    const char *name;
    bool autoclose, nestable;
    uchar inhead;
    bool iswall;
    const char *second;
	} specialtags[] = {
    {"a", 0, 0, 2, 0, 0},
    {"area", 1, 0, 0, 0, 0},
    {"b",0,1, 0, 0, 0},
    {"base", 1, 0, 1, 0, 0},
    {"bgsound", 1, 0, 1, 0, 0},
    {"blockquote", 0, 1, 0, 0, 0},
    {"body", 0, 0, 0, 1, 0},
/*********************************************************************
The next line allows <br> in the head section, and that is a kludge,
because of the way I crank out an email in html.
I want to put a header in front, and allow their html to run,
and they might start with a head section, so I don't want to disrupt that.
Any tag I include in my email header has to be allowed in <head>,
so it doesn't push us over into <body>, whence their <head> would be invalid.
I also use <pre> and <p> so that has to be allowed.
And even <a> for the attachments.
But these only live in <head> if browseMail is true.
*********************************************************************/
    {"br", 1, 0, 2, 1, 0},
    {"center",0,1, 0, 0, 0},
    {"comment", 0, 0, 1, 0, 0},
    {"div",0,1, 0, 1, 0},
    {"dl",0,1, 0, 1, 0},
    {"dt",0,0, 0, 1, "dl"},
    {"em",0,1, 0, 0, 0},
    {"font",0,1, 0, 0, 0},
    {"form", 0, 0, 0, 1, 0},
    {"h1",0,0, 0, 1, 0},
    {"h2",0,0, 0, 1, 0},
    {"h3",0,0, 0, 1, 0},
    {"h4",0,0, 0, 1, 0},
    {"h5",0,0, 0, 1, 0},
    {"h6",0,0, 0, 1, 0},
    {"hr", 1, 0, 0, 1, 0},
    {"i",0,1, 0, 0, 0},
    {"iframe", 0, 0, 0, 1, 0},
    {"image", 1, 0, 0, 0, 0},
    {"img", 1, 0, 0, 0, 0},
// special code to allow nesting of <body> which should never happen but it does
    {innerbodytag,0,1, 0, 1, 0},
    {innerhtmltag,0,1, 0, 1, 0},
    {"input", 1, 0, 0, 0, 0},
    {"li",0,0, 0, 1, "ul,ol"},
    {"link", 1, 0, 1, 0, 0},
    {"meta", 1, 0, 1, 0, 0},
    {"noscript", 0, 0, 1, 0, 0},
    {"ol",0,1, 0, 1, 0},
    {"optgroup", 0, 0, 0, 1, 0},
    {"option", 0, 0, 0, 1, 0},
    {"p",0,0, 2, 1, "blockquote"},
    {"pre", 0, 0, 2, 0, 0},
    {"script", 0, 0, 1, 0, 0},
    {"source", 1, 0, 0, 0, 0},
// youtube has <span> in its head section. Don't know why.
    {"span",0,1, 1, 0, 0},
    {"strong",0,1, 0, 0, 0},
    {"style", 0, 0, 1, 0, 0},
    {"sub",0,1, 0, 0, 0},
    {"sup",0,1, 0, 0, 0},
    {"table",0,1, 0, 1, 0},
    {"tbody",0,0, 0, 1, "table"},
    {"td",0,0, 0, 1, "table"},
    {"tfoot",0,0, 0, 1, "table"},
    {"th",0,0, 0, 1, "table"},
    {"thead",0,0, 0, 1, "table"},
    {"title", 0, 0, 1, 1, 0},
    {"tr",0,0, 1, 0, "table"},
    {"u",0,1, 0, 0, 0},
    {"ul",0,1, 0, 1, 0},
};
static const int specialtagCount = sizeof(specialtags) / sizeof(struct specialtag);

static const struct specialtag *name2specialtag(const char *name)
{
    int i, l = -1, r = specialtagCount, rc;
    const struct specialtag *y;
    while(r - l > 1) {
        i = (l + r) / 2;
        y = specialtags + i;
        rc = strcmp(y->name, name);
        if(!rc) return y;
        if(rc < 0)l = i; else r = i;
    }
    return 0;
}

static int isAutoclose(const char *name)
{
    const struct specialtag *y = name2specialtag(name);
    return y ? y->autoclose: false;
}

static int isInhead(const char *name)
{
    const struct specialtag *y = name2specialtag(name);
    if(!y) return false;
    uchar c = y->inhead;
    if(c == 2 && !browseMail) c = 0;
    return c;
}

static int isNextclose(const char *name)
{
// In this function and others, I'll retain the list for clarity and
// documentation, but there is often a more efficient way.
//  static const char * const list[] = {"title", "option", 0};
    if(!strcmp(name, "option")) return true;
    if(!strcmp(name, "title")) return true;
    return false;
}

static int isCrossclose(const char *name)
{
//  static const char * const list[] = {"h1","h2","h3","h4","h5","h6","p",0};
    if(name[0] == 'h' && isdigit(name[1]) && !name[2]) return true;
    if(name[0] == 'p' && !name[1]) return true;
    return false;
}

static int isCrossclose2(const char *name)
{
//  static const char * const list[] = {"h1","h2","h3","h4","h5","h6","table","ul","ol","dl","hr","div","form",0};
    if(name[0] == 'h' && (isdigit(name[1]) || name[1] == 'r') && !name[2]) return true;
    if(name[1] == 'l' && !name[2] && (name[0] == 'o' || name[0] == 'u' || name[0] == 'd')) return true;
    if(!strcmp(name, "div")) return true;
    if(!strcmp(name, "form")) return true;
    if(!strcmp(name, "table")) return true;
    return false;
}

static int isTableSection(const char *name)
{
//  static const char * const list[] = {"thead","tbody","tfoot",0};
    if(name[0] != 't') return false;
    if(!name[2]) return false;
    if(!strcmp(name, "thead")) return true;
    if(!strcmp(name, "tbody")) return true;
    if(!strcmp(name, "tfoot")) return true;
    return false;
}

static int isCell(const char *name)
{
//  static const char * const list[] = {"th","td",0};
    if(name[0] == 't' && !name[2] && (name[1] == 'h' || name[1] == 'd')) return true;
    return false;
}

// space after these tags isn't significant
static bool isWall(const char *name)
{
    const struct specialtag *y = name2specialtag(name);
    return y ? y->iswall : false;
}


static int isNonest(const char *name, const struct opentag *k)
{
    const struct specialtag *y = name2specialtag(name);
    const char *s, *t;
    char watch[MAXTAGNAME];
    const struct opentag *l;
    if(!y) {
// Not on our special list, do we know this tag at all?
// Unknown tags will be nestable. Consistent with xml.
        const struct tagInfo *ti = name2tagInfo(name);
        return !!ti;
    }
    if(y->nestable) return false;

// td can be inside td, if there is table in between,
// second indicates this in-between tag
	if(!(s = y->second)) return true;
	while(*s) {
		t = strchr(s, ',');
		if(!t) t = s + strlen(s);
		strncpy(watch, s, t-s);
		watch[t - s] = 0;
		for(l = stack; l != k; l = l->next)
			if(stringEqual(l->lowname, watch)) return false;
		s = t;
		if(*s == ',') ++s;
	}
	return true;
}

// seek point for the html scanner
static const char *seek;

// generate a tag using newTag, which does most of the work
static void makeTag(const char *name, const char *lowname, bool slash, const char *mark)
{
	Tag *t, *parent;
	struct opentag *k;

	if(slash) {
		if(!(k = balance(lowname))) {
// </foo> without <foo>, may as well just throw it away.
			scannerError2("%s unbalanced", name);
			return;
		}
// now handle <i><b></i></b>
// the balancing tag is not at the top of the stack, where it should be!
// I think the best thing is to close out the other tags.
		while(stack != k) {
			scannerError2("force closure of %s", stack->name);
			makeTag(stack->name, stack->lowname, true, mark);
		}
	}

	if(!slash) {
		working_t = t = newTag(cf, name);
// xml parse comes from the xhr system, so we need the javascript objects -
// unless you have faked it out with my xml code and this is the primary frame,
// and then you don't need the js objects, and they only slow you down.
		if(cf->xmlMode && cf != &cw->f0)
			t->doorway = true;
		if((t->action == TAGACT_HTML || t->action == TAGACT_BODY) && htmlGenerated)
			goto skiplink;
		t->parent = parent = stack ? stack->t : overnode;
		if(parent) {
			if(!parent->lastchild)
				parent->firstchild = t;
			else
				parent->lastchild->sibling = t;
			parent->lastchild = t;
		}
skiplink:

		k = allocMem(sizeof(struct opentag));
		strcpy(k->name, name);
		strcpy(k->lowname, lowname);
		k->t = t;
		k->start = mark;
		k->next = stack, stack = k;

		if(cf->xmlMode) goto past_html_open_semantics;
		if(stringEqual(lowname, htmltag)) {
			headbody = 1;
			scannerInfo1("in html", 0);
			if(htmlGenerated) t->dead = true, ++cw->deadTags;
		}
		if(stringEqual(lowname, headtag)) {
			headbody = 2;
			scannerInfo1("in head", 0);
		}
		if(stringEqual(lowname, bodytag)) {
			headbody = 4, bodycount = htmlcount = 1;
			scannerInfo1("in body", 0);
			if(htmlGenerated) t->dead = true, ++cw->deadTags;
		}
		if(stringEqual(lowname, "pre")) {
			premode = true;
			scannerInfo1("pre", 0);
// Need a tag for </pre>. It's weird.
			t = newTag(cf, name);
			t->slash = t->dead = true, ++cw->deadTags;
		}
past_html_open_semantics: ;
	} else {
		if(cf->xmlMode) goto past_html_close_semantics;
		if(stringEqual(lowname, headtag)) {
			headbody = 3;
			scannerInfo1("post head", 0);
		}
		if(stringEqual(lowname, bodytag)) {
			headbody = 5, bodycount = 0;
			scannerInfo1("post body", 0);
		}
		if(stringEqual(lowname, htmltag)) {
			headbody = 6, htmlcount = 0;
			scannerInfo1("post html", 0);
		}
		if(stringEqual(lowname, "pre")) {
			premode = false;
			scannerInfo1("close pre", 0);
		}
past_html_close_semantics:

		stack = k->next;
// set up for innerHTML
		if(k->t->info->bits & TAG_INNERHTML && k->start && mark
// I don't think innerHTML is meaningful in xml
		&& allowJS && !cf->xmlMode)
			k->t->innerHTML = pullString1(k->start, mark);
		free(k);
	}
}

static void pushTag(Tag *t);
Tag *newTag(const Frame *f, const char *name)
{
	Tag *t, *t1, *t2 = 0;
	const struct tagInfo *ti = name2tagInfo(name);
	if (!ti) {
		debugPrint(4, "warning, created node %s reverts to generic", name);
		ti = availableTags;
	}

	t = (Tag *)allocZeroMem(sizeof(Tag));
	t->action = ti->action;
	t->f0 = (Frame *) f;		/* set owning frame */
	t->info = ti;
	t->seqno = cw->numTags;
	t->nodeName = cloneString(name);
	t->nodeNameU = cloneString(name);
	caseShift(t->nodeNameU, 'u');
	pushTag(t);
	if (t->action == TAGACT_SCRIPT) {
		for (t1 = cw->scriptlist; t1; t1 = t1->same)
			t2 = t1;
		if (t2)
			t2->same = t;
		else
			cw->scriptlist = t;
	}
	if (t->action == TAGACT_LINK) {
		for (t1 = cw->linklist; t1; t1 = t1->same)
			t2 = t1;
		if (t2)
			t2->same = t;
		else
			cw->linklist = t;
	}
	if (t->action == TAGACT_FRAME) {
		for (t1 = cw->framelist; t1; t1 = t1->same)
			t2 = t1;
		if (t2)
			t2->same = t;
		else
			cw->framelist = t;
	}
	if (t->action == TAGACT_INPUT || t->action == TAGACT_SELECT ||
	    t->action == TAGACT_TA) {
		for (t1 = cw->inputlist; t1; t1 = t1->same)
			t2 = t1;
		if (t2)
			t2->same = t;
		else
			cw->inputlist = t;
	}
	if (t->action == TAGACT_OPTION) {
		for (t1 = cw->optlist; t1; t1 = t1->same)
			t2 = t1;
		if (t2)
			t2->same = t;
		else
			cw->optlist = t;
	}
	return t;
}

/*********************************************************************
Delete dead tags at the end of the line before we create new ones.
This saves resources, particularly on websites that insist on updating
information every 5 seconds via innerHTML.
The prior tags are dead, but live on in our array of tags,
unless we delete them here.
Certain tags we might not want to delete, and hope they don't come up
in the block of updated information very often.
frameTag indicates <frame> above that created this frame.
Not sure how this tag could be dead but the frame still lives on and points
back to this tag. Let's not test it.
cw->htmltag cw->headtag cw->bodytag I think they are always alive.
When they appear from innerHTML or from document.write they are killed
before they are decorated,
so cw->htmltag cw->headtag cw->bodytag won't point to them.
These are especially important to delete; they appear in every innerHTML.
Link lists for efficiency: don't delete script input option link,
unless some day you want to remove them from the chain as well.
Remember that prerender turns textarea and select into input.
dead means taken out of the tree, so no worries about parent or firstchild.
There are a few controllers: table section row form select svg
but can any of these tags be dead while a subordinate tag is still alive?
I don't think so.
Now for the real bummer.
I had to change things so that tags are not dead, even after they are
displaced by innerHTML. they live on, they might be used again.
They might be put back into the tree in another location.
So this function accomplishes nothing.
I'll leave it be for a while, as a guide, in case we can some day
make it useful again, but I think we have to redesign the tag / js object
correspondence, and find a way to recognize and reclaim dead tags.
*********************************************************************/

#if 0
static void backupTags(void)
{
	static const short forbidden[] = {
	TAGACT_FRAME, TAGACT_SCRIPT, TAGACT_LINK, TAGACT_INPUT, TAGACT_OPTION, 0};
	Tag *t;
	bool delmessage = false;
	int i, n = 0;
	while(cw->numTags &&
	(t = tagList[cw->numTags-1]) && t->dead) {
		if(!delmessage) { debugPrint(4, "deleting dead tags"); delmessage = true; }
		for(i = 0; forbidden[i]; ++i)
			if(t->action == forbidden[i]) {
stop:
				debugPrint(4, "stop at %s", t->info->name);
				return;
			}
// unlikely case where html ends in </pre>, which is always a dead tag.
		if(t->action == TAGACT_PRE && t->slash
		&& !tagList[cw->numTags-2]->dead)
			goto stop;
		debugPrint(4, "backup %s", t->info->name);
		freeTag(t);
		--cw->numTags, ++n;
	}
	if(delmessage) debugPrint(4, "%d tags deleted", n);
}
#endif

// for debugging
// should this be gated on a debug variable rather than not compiled by default?
#if 0
static void printTags(void)
{
	int i;
	for(i = 0; i < cw->numTags; ++i) {
		const Tag *t = tagList[i];
		printf("%d %s%s%s\n", i, t->info->name, (t->slash ? "/" : ""), (t->dead ? "*" : ""));
	}
}
#endif

static void pushTag(Tag *t)
{
	int a = cw->allocTags;
	if (cw->numTags == a) {
		debugPrint(4, "%d tags, %d dead", a, cw->deadTags);
/* make more room */
		a = a / 2 * 3;
		cw->tags =
		    (Tag **)reallocMem(cw->tags, a * sizeof(t));
		cw->allocTags = a;
	}
	tagList[cw->numTags++] = t;
// paranoia check on the number of tags
	if (cw->numTags > MAXLINES)
		i_printfExit(MSG_LineLimit);
}

// Of course we have to free the tags when the window is done.
static void freeTag(Tag *t)
{
	char **a;
// Even if js has been turned off, if this tag was previously connected to an
// object, we should disconnect it.
	disconnectTagObject(t);

// is a child thread downloading on behalf of this tag?
	if(t->threadcreated && !t->threadjoined) {
// try to stop the download
		pthread_kill(t->loadthread, SIGINT);
// thread has to finish before we free this tag
// hopefully SIGINT will cause it to finish quickly, though that's not guaranteed
		pthread_join(t->loadthread, NULL);
		t->threadjoined = true;
	}

	nzFree(t->textval);
	nzFree(t->name);
	nzFree(t->id);
	nzFree(t->jclass);
	nzFree(t->nodeName);
	nzFree(t->nodeNameU);
	nzFree(t->value);
	cnzFree(t->rvalue);
	nzFree(t->href);
	nzFree(t->js_file);
	nzFree(t->innerHTML);
	nzFree(t->custom_h);

	a = (char **)t->attributes;
	if (a) {
		while (*a) {
			nzFree(*a);
			++a;
		}
		free(t->attributes);
	}

	a = (char **)t->atvals;
	if (a) {
		while (*a) {
			nzFree(*a);
			++a;
		}
		free(t->atvals);
	}

	free(t);
}

void freeTags(Window *w)
{
	int i, n;
	Tag *t, **e;

/* if not browsing ... */
	if (!(e = w->tags))
		return;

/* drop empty textarea buffers created by this session */
	for (t = w->inputlist; t; t = t->same) {
		if (t->action != TAGACT_INPUT)
			continue;
		if (t->itype != INP_TA)
			continue;
// 0 means no side buffer; -1 means the text is inline,
// a trick when the text can be contained on one line and doesn't need a buffer.
		if ((n = t->lic) <= 0)
			continue;
		freeEmptySideBuffer(n);
	}			// loop over tags

	for (i = 0; i < w->numTags; ++i, ++e) {
		t = *e;
		freeTag(t);
	}

	free(w->tags);
	w->tags = 0;
	w->numTags = w->allocTags = w->deadTags = 0;
	w->inputlist = w->scriptlist = w->optlist = w->linklist = 0;
	w->framelist = 0;
}

// When window first opens, reserve space for 512 tags.
void initTagArray(void)
{
	cw->numTags = 0;
	cw->allocTags = 512;
	cw->deadTags = 0;
	cw->tags =
	    (Tag **)allocMem(cw->allocTags *
					sizeof(Tag *));
}

// Now for the scanner, create edbrowse tags corresponding to the html tags.
// Returns the start position of the new tags.
int htmlScanner(const char *htmltext, Tag *above, bool isgen)
{
	int i, startpos;
	const char *lt; // les than sign
	const char *gt; // greater than sign
	const char *s, *t, *u;
	char *w;
	bool slash; // </foo>
	bool ws; // all whitespace
	bool xml = false; // parsing xml
	char qc; // quote character
	char tagname[MAXTAGNAME];
	char lowname[MAXTAGNAME];
	const struct opentag *k;
	static const char texttag[] = "text";

#if 0
	backupTags();
#endif

	startpos = cw->numTags;

	headbody = 0, bodycount = htmlcount = 0;
	stack = 0;
	atWall = false;
	lasttext = 0;
	start_idx = cw->numTags;
	if((overnode = above)) {
		Tag *u = overnode->firstchild;
		if(u)
			for(; u->sibling; u = u->sibling) ;
		overnode->lastchild = u;
	}
	htmlGenerated = isgen;
// magic code to say this is xml
    if(!strncmp(htmltext, "`~*xml}@;", 9)) {
/*********************************************************************
When we are scanning xml, the entire frame is xml, and wholly xml, and forever xml.
If javascript calls DOMParser to parse some xml, it creates a new frame inside to do it.
This is a completely different mechanism from innerHTML,
which runs within the current frame.
Thus it is fair to set xml mode in the frame, a global variable if you will,
and reference that everywhere.
*********************************************************************/
        cf->xmlMode = true;
// and some shorthand for within this function
        xml = true;
        htmltext += 9;
        scannerInfo1("xml parsing", 0);
    }

// kludge, if we have extracted the html out of an email,
// we want to recognize it as having come from email.
    if(!strncmp(htmltext, "<html>\n<pre nowspc>", 19))
        browseMail = true;

    seek = s = htmltext, ln = 1, premode = false;

// loop looking for tags
	while(*s) {
// the next literal < should begin the next tag
// text like x<y is invalid; you should be using &lt; in this case
		if(!(lt = strchr(s, '<')))
			break;
		slash = false, t = lt + 1;
		if(*t == '/') ++t, slash = true;

// <> and </> are null tags
		if(*t == '>') goto between;

// bare < just passes through
		if((!slash && *t != '!' && !isalphaByte(*t) && !memEqualCI(t, "?xml", 4)) ||
		(slash && !isalphaByte(*t))) {
			s = t + 1;
			continue;
		}

// text fragment between tags
between:
		if(lt > seek) {
// adjust line number
			for(ws = true, u = seek; u < lt; ++u) {
				if(*u == '\n') ++ln;
				if(!isspaceByte(*u)) ws = false;
			}

// Ignore whitespace that is not in the head or the body.
// We use to ignore text after body, but then:
// https://rocksolid.sybershock.com/common/grouplist.php
// This is terrible html, it has </body> with stuff after it,
// so we have to march along.
// But if this ever happens in concert with js, it's bad, because the stuff
// after </body> is under document and not under document.body
// So this is a patch and not a great solution.
			if((!ws || (headbody >= 4 && !atWall))) {
				if(!xml) pushState(seek, true);
				w = pullAnd(seek, lt, false);
				if(!premode) compress(w);
				  scannerInfo2("text{%s}", w);
				makeTag(texttag, texttag, false, 0);
				if(!ws) atWall = false, lasttext = working_t;
				working_t->textval = w;
				makeTag(texttag, texttag, true, 0);
			}
		}

		if(*t == '>') {
			scannerInfo1("null tag line %d", ln);
			s = seek = t+1;
			continue;
		}

// xml cdata section
		if(memEqualCI(t, "![cdata[", 8)) {
			if(!(u = strstr(t, "]]>"))) {
				scannerError1("open cdata at line %d, html parsing stops here", ln);
				goto stop;
			}
			scannerInfo1("cdata length %d", u - t - 8);
// adjust line number
			for(t = lt; t < u; ++t)
				if(*t == '\n') ++ln;
			seek = s = u + 3;
			t = lt + 9;
			w = pullString(t, u - t);
			makeTag("cdata", "cdata", false, 0);
			working_t->textval = w;
			makeTag("cdata", "cdata", true, 0);
			continue;
		}

// standard html comment
		if(*t == '!') {
			int hyphens = 0;
			++t;
			while(t[0] == '-' && t[1] == '-')
				t += 2, hyphens += 2;
			u = t;
//   <!------> is a self contained comment
closecomment:
			u = strchr(u, '>');
			if(!u) goto opencomment;
			for(i = 1; i <= hyphens; ++i)
				if(u[-i] != '-') break;
			if(i < hyphens) { ++u; goto closecomment; }
// this is a valid comment
			scannerInfo1("comment", 0);
// adjust line number
			for(t = lt; t < u; ++t)
				if(*t == '\n') ++ln;
			seek = s = u + 1;
// see if this is doctype
			t = lt + 2 + hyphens;
			u -= hyphens;
// for <!----> u could be less than t
			w = (u <= t ? 0 : pullString(t, u - t));
			if(w && headbody == 0 && memEqualCI(t, "doctype", 7) &&
			!isalnumByte(t[7]) && !xml) {
				scannerInfo1("doctype", 0);
				makeTag("doctype", "doctype", false, 0);
				working_t->textval = w;
				makeTag("doctype", "doctype", true, 0);
			} else {
				makeTag("comment", "comment", false, 0);
				working_t->textval = w;
				makeTag("comment", "comment", true, 0);
			}
			continue;
opencomment:
			scannerError1("open comment at line %d, html parsing stops here", ln);
			goto stop;
		}

// xml specifier - I don't really understand this.
		if(memEqualCI(t, "?xml", 4)) {
			if(!(gt = strstr(t, "?>"))) {
				scannerError1("open xml", 0);
				s = t;
				continue;
			}
			scannerInfo1("xml", 0);
			s = seek = gt + 2;
			continue;
		}

// at this point it is <tag or </tag
		i = 0;
// the standard tags do not contain hyphens, but custom tags might.
		while(isalnumByte(*t) || *t == '-' || *t == '_') {
			if(i < MAXTAGNAME - 1) tagname[i++] = *t;
			++t;
		}
		tagname[i] = 0;
// tidy converts all tags to lower case; I will do the same
		strcpy(lowname, tagname);
		caseShift(lowname, 'l');

/*********************************************************************
the next > should close the tag,
		gt = strpbrk(t, "<>");
but watch out. Look at acid3.
Apparently this crap is legal   <area alt="<'>">
You should be using &gt; but I guess when quoted like this, it flies.
And tidy is ok with it, not even a warning.
So I have to step along and watch for quotes.
But then I get this from usps.gov:
<table role="presentation" width="100%"         style="padding-bottom: 30px; max-width: 820px!important;>
Unbalanced quotes, bad html, somehow tidy knows what to do with it.
I haven't given the tidy team enough credit.
So my compromise is to take the first literal > if the quoted
string that contains the > would run past end of line.
That makes acid3 and usps.gov happy.
But some web pages are all on a line to save bits,
so also break out at >< like a new tag is starting.
Then this was found in the wild.
<a nofollow=<"" href="https://foo.bar.com">hello</a>
I watch for =< and let < slide past in this case.
I think < should completely disappear in this case, but I don't know
and so that is not implemented.
*********************************************************************/

		for(gt = t, qc = 0; *gt; ++gt) {
			if(qc) {
				if(qc == *gt) qc = 0; // unquote
				if(*gt == '>' && !u) u = gt;
				if(*gt == '\n' && u) { gt = u; break; }
				if(*gt == '>' && gt[1] == '<' && u) { gt = u; break; }
				continue;
			}
			if(*gt == '<' && gt > t && gt[-1] == '=') continue;
			if(*gt == '<' || *gt == '>') break;
			if(*gt == '"' || *gt == '\'') qc = *gt, u = 0;
		}

		if(!gt) {
			printf("open tag %s, html parsing stops here\n", tagname);
			goto stop;
		}
// adjust line number for this tag
		for(u = lt; u < gt; ++u)
			if(*u == '\n') ++ln;
		if(*(seek = gt) == '>') ++seek;
		s = seek; // ready to march on

		if(slash) {
			if(xml) goto past_html_close_semantics;
			if(stringEqual(lowname, bodytag) && bodycount > 1) {
				strcpy(tagname, innerbodytag), --bodycount;
				strcpy(lowname, innerbodytag);
			}
			if(stringEqual(lowname, htmltag) && htmlcount > 1) {
				strcpy(tagname, innerhtmltag), --htmlcount;
				strcpy(lowname, innerhtmltag);
			}
past_html_close_semantics:

// create this tag in the edbrowse world.
			scannerInfo2("</%s>", tagname);
			makeTag(tagname, lowname, true, lt);
			s = seek; // needed for </include-fragment>
#if 0
// we use to do this but then you can't paste two html documents together
			if(headbody == 6) goto stop;
#endif
			if(!xml && isWall(lowname)) {
				atWall = true;
// code here to keep trailing whitespace for document.write or innerHTML
// remember I tack </body> on the end
				if(htmlGenerated && stringEqual(lowname, bodytag))
					lasttext = 0;
				if(lasttext) trimWhite(lasttext->textval);
				lasttext = 0;
			}
			continue;
		}

// create this tag in the edbrowse world.
		scannerInfo3("<%s> line %d", tagname, ln);
		if(xml) goto past_html_open_semantics;
// has to start and end with html
		if(stringEqual(lowname, htmltag)) {
			if(headbody == 0) goto tag_ok;
			if(headbody != 4) { scannerError1("sequence", 0); continue; }
			strcpy(tagname, innerhtmltag), ++htmlcount;
			strcpy(lowname, innerhtmltag);
			goto tag_ok;
		}
		if(headbody == 0)
			makeTag(htmltag, htmltag, false, lt);
		if(stringEqual(lowname, headtag)) {
			if(headbody > 1) { scannerError1("sequence", 0); continue; }
			goto tag_ok;
		}
		if(!isInhead(lowname)) {
			if(headbody == 1) {
				if(htmlGenerated) {
					scannerInfo1("skip head section\nin head\npost head", 0);
				} else {
					scannerInfo1("initiate and terminate head", 0);
					debugPrint(3, "head section missing");
					makeTag(headtag, headtag, false, lt);
					makeTag(headtag, headtag, true, lt);
				}
			} else if(headbody == 2) {
				scannerInfo1("terminate head", 0);
				debugPrint(3, "premature end to head section");
				makeTag(headtag, headtag, true, lt);
			}
		} else pushState(lt, true);
		if(stringEqual(lowname, bodytag)) {
			if(headbody > 4) {
				scannerError1("sequence reset", 0);
				headbody = 3;
				debugPrint(3, "body after body");
				goto tag_ok;
			}
			if(headbody == 4) {
				strcpy(tagname, innerbodytag), ++bodycount;
				strcpy(lowname, innerbodytag);
				debugPrint(3, "body within body");
			}
			goto tag_ok;
		}
		if(headbody == 3) pushState(lt, false);

tag_ok:
//  see if we need to close a prior instance of this tag
		k = balance(lowname);
		if(k && isNonest(lowname, k)) {
			scannerInfo2("%s not nestable", tagname);
			makeTag(tagname, lowname, true, lt);
		}

		if(stack && isNextclose(stack->lowname)) {
			scannerInfo2("prior close %s", stack->name);
			makeTag(stack->name, stack->lowname, true, lt);
		}

		if(stack && isCrossclose2(lowname)) {
			const struct opentag *hold;
			for(k = stack; k; k = hold) {
				hold = k->next;
// special exception for <p><select><hr></select></p>
// separating options must not close the containing paragraph
				if(stringEqual(lowname, "hr") &&
				stringEqual(k->lowname, "select")) break;
// Special exception for <h2> <div>, which is improper html but happens.
				if(stringEqual(lowname, "div") &&
				k->lowname[0] == 'h') continue;
				if(isCrossclose(k->lowname)) {
					scannerInfo2("cross close %s", k->name);
					makeTag(k->name, k->lowname, true, lt);
				}
			}
		}

// This one doesn't fit into our simple table-driven crossclose pattern.
// Any of thead tbody tfoot closes the prior, unless in a lower table.
		if(stack && isTableSection(lowname)) {
			for(k = stack; k; k = k->next) {
				if(stringEqual(k->lowname, "table")) break;
				if(isTableSection(k->lowname)) {
					scannerInfo2("cross close %s", k->name);
					makeTag(k->name, k->lowname, true, lt);
					break;
				}
			}
		}

		if(stack && isCell(lowname)) {
			for(k = stack; k; k = k->next) {
				if(stringEqual(k->lowname, "table")) break;
				if(isCell(k->lowname)) {
					scannerInfo2("cross close %s", k->name);
					makeTag(k->name, k->lowname, true, lt);
					break;
				}
			}
		}

past_html_open_semantics:
		makeTag(tagname, lowname, false, seek);
		if(!xml && isWall(lowname)) {
			atWall = true;
			lasttext = 0;
		}
		i = working_t->action;
		if(!xml && (i == TAGACT_INPUT || i == TAGACT_SELECT)) {
			atWall = false;
			lasttext = 0;
		}
// The HTML5 specification says that user agents should strip the first
// newline immediately after the <pre> tag.
		if(!xml && i == TAGACT_PRE) {
			if(*s == '\r') ++s, ++seek;
			if(*s == '\n') ++s, ++seek;
		}
		findAttributes(t, gt);
		if(!xml && isAutoclose(lowname)) {
			scannerInfo2("%s autoclose", tagname);
			makeTag(tagname, lowname, true, seek);
		} else if(*gt == '>' && gt[-1] == '/') {
			scannerInfo2("%s close by slash", tagname);
			makeTag(tagname, lowname, true, seek);
			s = seek; // needed for </include-fragment>
// if we have <script /> stuf and stuff </script> what are we suppose to do?
// Well just don't do that - so I'll guard against <script src=url /> which is legit.
// However, we still have to set doorway.
			if(stringEqual(lowname, "script")) {
				working_t->doorway = true;
				working_t->scriptgen = htmlGenerated;
			}
			continue;
		}

		if(stringEqual(lowname, "script")) {
// remember the line number for error messages
			working_t->js_ln = ln;
/*********************************************************************
A script, javascript or otherwise, can contain virtually any string.
We can't expect an html scanner, like this one, to understand
the syntax of every scripting language that might ever embed.
To this end, constructs like this are invalid.
	var foo = "hello </script> world";
Such has to be written
	var foo = "hello </scr" + "ipt> world";
With this understanding, we can, and should, scan for </script
*********************************************************************/
			if(!(lt = strcasestr(seek, "</script"))) {
				scannerError1("open script at line %d, html parsing stops here", ln);
				goto stop;
			}
			if(!(gt = strpbrk(lt + 1, "<>")) || *gt == '<') {
				scannerError1("open script at line %d, html parsing stops here", ln);
				goto stop;
			}
// adjust line number
			for(u = seek; u < gt; ++u)
				if(*u == '\n') ++ln;
// remove leading whitespace from script; I think this is ok
			while(isspaceByte(*seek)) {
				if(*seek == '\n') ++working_t->js_ln;
				++seek;
			}
			   scannerInfo1("script length %d", (int)(lt - seek));
			working_t->doorway = true;
			working_t->scriptgen = htmlGenerated;
			if(lt > seek) {
// pull out the script, do not andify or change in any way.
				w = pullString(seek, lt - seek);
				working_t->textval = w;
				makeTag(texttag, texttag, false, 0);
				working_t->textval = cloneString(w);
				makeTag(texttag, texttag, true, 0);
			}
			scannerInfo1("</script>", 0);
			makeTag(tagname, lowname, true, lt);
			seek = s = gt + 1;
			continue;
		}

		if(stringEqual(lowname, "style")) {
// this is like script; leave it alone!
			if(!(lt = strcasestr(seek, "</style"))) {
				scannerError1("open style at line %d, html parsing stops here", ln);
				goto stop;
			}
			if(!(gt = strpbrk(lt + 1, "<>")) || *gt == '<') {
				scannerError1("open style at line %d, html parsing stops here", ln);
				goto stop;
			}
// adjust line number
			for(u = seek; u < gt; ++u)
				if(*u == '\n') ++ln;
			while(isspaceByte(*seek)) ++seek;
			   scannerInfo1("style length %d", (int)(lt - seek));
			if(lt > seek) {
// pull out the style, do not andify or change in any way.
				w = pullString(seek, lt - seek);
// why doesn't this fold directly into textval, like script?
				makeTag(texttag, texttag, false, 0);
				working_t->textval = w;
				makeTag(texttag, texttag, true, 0);
			}
			scannerInfo1("</style>", 0);
			makeTag(tagname, lowname, true, lt);
			seek = s = gt + 1;
			continue;
		}

		if(stringEqual(lowname, "textarea")) {
/*********************************************************************
A textarea can contain tags and these are not to be interpreted. In fact a
textarea is sometimes html code that you are suppose to embed in your web page.
With this understanding, we can, and should, scan for </textarea
*********************************************************************/
			if(!(lt = strcasestr(seek, "</textarea"))) {
				scannerError1("open textarea at line %d, html parsing stops here", ln);
				goto stop;
			}
			if(!(gt = strpbrk(lt + 1, "<>")) || *gt == '<') {
				scannerError1("open textarea at line %d, html parsing stops here", ln);
				goto stop;
			}
// adjust line number
			for(u = seek; u < gt; ++u)
				if(*u == '\n') ++ln;
			while(isspaceByte(*seek)) ++seek; // should we be doing this?
			if(lt > seek) {
// pull out the text and andify.
				w = pullAnd(seek, lt, false);
				   scannerInfo1("textarea length %d", strlen(w));
				makeTag(texttag, texttag, false, 0);
				working_t->textval = w;
				makeTag(texttag, texttag, true, 0);
			}
			scannerInfo1("</textarea>", 0);
			makeTag(tagname, lowname, true, lt);
			seek = s = gt + 1;
			continue;
		}

		if(stringEqual(lowname, "title")) {
/*********************************************************************
A title should not contain other tags.
Do not interpret < > etc, but do interpret &stuff;
With this understanding, we can, and should, scan for </title
*********************************************************************/
			if(!(lt = strcasestr(seek, "</title"))) {
				scannerError1("open title at line %d, html parsing stops here", ln);
				goto stop;
			}
			if(!(gt = strpbrk(lt + 1, "<>")) || *gt == '<') {
				scannerError1("open title at line %d, html parsing stops here", ln);
				goto stop;
			}
// adjust line number
			for(u = seek; u < gt; ++u)
				if(*u == '\n') ++ln;
			while(isspaceByte(*seek)) ++seek; // should we be doing this?
			if(lt > seek) {
// pull out the text and andify.
				w = pullAnd(seek, lt, false);
				   scannerInfo1("title length %d", strlen(w));
				makeTag(texttag, texttag, false, 0);
				working_t->textval = w;
				makeTag(texttag, texttag, true, 0);
			}
			scannerInfo1("</title>", 0);
			makeTag(tagname, lowname, true, lt);
			seek = s = gt + 1;
			continue;
		}

	}

// seek points to the last piece of the buffer, after the last tag
	if(*seek) {
		for(ws = true, u = seek; *u; ++u) {
			if(*u == '\n') ++ln;
			if(!isspaceByte(*u)) ws = false;
		}
		if(headbody < 5 && !ws) {
			if(!xml) pushState(seek, true);
			w = pullAnd(seek, seek + strlen(seek), false);
			if(!premode) compress(w), trimWhite(w);
			  scannerInfo2("text{%s}", w);
			makeTag(texttag, texttag, false, 0);
			working_t->textval = w;
			makeTag(texttag, texttag, true, 0);
		}
	}

stop:
	if(xml) goto past_html_final_semantics;

// we should have a head and a body
	pushState(s, false);

// has to start and end with html
if(headbody < 6)
		makeTag(htmltag, htmltag, true, s);

	if(htmlGenerated) {
		if(cw->numTags < start_idx + 2 ||
		!overnode ||
		(working_t = tagList[start_idx + 1])->action != TAGACT_BODY) {
			debugPrint(1, "cannot move generated html up past <html><body>");
		} else {
			Tag *u = working_t->firstchild;
			Tag *v = overnode->firstchild;
			if(!v) overnode->firstchild = u;
			else {
				while(v->sibling) v = v->sibling;
				v->sibling = u;
			}
			while(u) {
				u->parent = overnode;
				u = u->sibling;
			}
		}
	}

past_html_final_semantics:
	// this could happen if xml tags are not closed
	if(stack)
		debugPrint(1, "stack not empty after html scan");
// clear memory leak
	while(stack) {
		struct opentag *hold = stack->next;
		free(stack);
		stack = hold;
	}

// javascript could set innerHTML = blah, which induces another call to this
// function, all within the same browse routine, and that second call should
// not be treated as browsing an email.
	browseMail = false;

//	printTags();
	return startpos;
}

static void pushState(const char *start, bool head_ok)
{
	if(headbody == 0) {
		makeTag(htmltag, htmltag, false, start);
	}
	if(headbody == 1) {
		scannerInfo1("initiate head", 0);
		makeTag(headtag,headtag, false, start);
	}
	if(headbody == 2 && !head_ok) {
		scannerInfo1("terminate head", 0);
		debugPrint(3, "premature end to head section");
		makeTag(headtag,headtag, true, start);
	}
	if(headbody == 3) {
		scannerInfo1("initiate body", 0);
		makeTag(bodytag,bodytag, false, start);
	}
}

static void findAttributes(const char *start, const char *end)
{
	const char *s = start;
	char qc; // quote character
	const char *a1, *a2; // attribute name
	const char *v1, *v2; // attribute value
	Tag *t = working_t; // shorthand
	int action, j;
	const char *v;

// <script src=blah/> tidy treats the final / as close indicator
	if(*end == '>' && end[-1] == '/') --end;

	while(s < end) {
// look for a C identifier, then whitespace, then =
		if(!isalphaByte(*s)) { ++s; continue; }
		a1 = s; // start of attribute
		a2 = a1 + 1;
		while(*a2 == '_' || *a2 == '-' || isalnumByte(*a2)) ++a2;
		for(s = a2; isspaceByte(*s); ++s)  ;
		if(*s != '=' || s == end) {
// it could be an attribute with no value, but then we need whitespace
			if(s > a2 || s == end)
				setAttrFromHTML(a1, a2, a2, a2);
			continue;
		}
		for(v1 = s + 1; isspaceByte(*v1); ++v1)  ;
		qc = 0;
		if(*v1 == '"' || *v1 == '\'') qc = *v1++;
		for(v2 = v1; v2 < end; ++v2)
			if((!qc && isspaceByte(*v2)) || (qc && *v2 == qc)) break;
		setAttrFromHTML(a1, a2, v1, v2);
		if(*v2 == qc) ++v2;
		s = v2;
	}

// Certain attributes map back to members of t for html rendering.
// Remember we need to operate even without javascript.
	if(!t->attributes) return;

	action = t->action;
	if (stringInListCI(t->attributes, "onclick") >= 0)
		t->onclick = t->doorway = true;
	if (stringInListCI(t->attributes, "onchange") >= 0)
		t->onchange = t->doorway = true;
	if (stringInListCI(t->attributes, "onsubmit") >= 0)
		t->onsubmit = t->doorway = true;
	if (stringInListCI(t->attributes, "onreset") >= 0)
		t->onreset = t->doorway = true;
	if (stringInListCI(t->attributes, "onload") >= 0)
		t->onload = t->doorway = true;
	if (stringInListCI(t->attributes, "onunload") >= 0)
		t->onunload = t->doorway = true;
	if (stringInListCI(t->attributes, "checked") >= 0)
		t->checked = t->rchecked = true;
	if (stringInListCI(t->attributes, "readonly") >= 0)
		t->rdonly = true;
	if (stringInListCI(t->attributes, "disabled") >= 0)
		t->disabled = true;
	if (stringInListCI(t->attributes, "hidden") >= 0)
		t->hidden = true;
// async and defer are the same as far as edbrowse is concerned.
	if (stringInListCI(t->attributes, "async") >= 0)
		t->async = true;
	if (stringInListCI(t->attributes, "defer") >= 0)
		t->async = true;
	if (stringInListCI(t->attributes, "multiple") >= 0)
		t->multiple = true;
	if (stringInListCI(t->attributes, "required") >= 0)
		t->required = true;
	if ((j = stringInListCI(t->attributes, "name")) >= 0) {
// temporarily make another copy; some day we'll point to the value
		v = t->atvals[j];
		if (v && !*v) v = 0;
		t->name = cloneString(v);
	}
	if ((j = stringInListCI(t->attributes, "id")) >= 0) {
		v = t->atvals[j];
		if (v && !*v) v = 0;
		t->id = cloneString(v);
	}
	if ((j = stringInListCI(t->attributes, "class")) >= 0) {
		v = t->atvals[j];
		if (v && !*v) v = 0;
		t->jclass = cloneString(v);
	}
// classname is an alias for class
	if ((j = stringInListCI(t->attributes, "classname")) >= 0) {
		v = t->atvals[j];
		if (v && !*v) v = 0;
		t->jclass = cloneString(v);
	}
	if ((j = stringInListCI(t->attributes, "value")) >= 0) {
		v = t->atvals[j];
// empty value is like no value, I think, but not for <option>
// empty value has a meaning, pass no value on submit.
		if (v && !*v && action != TAGACT_OPTION) v = 0;
		t->value = cloneString(v);
		t->rvalue = cloneString(v);
	}
// Resolve href against the base, but wait a minute, what if it's <p href=blah>
// and we're not suppose to resolve it? I don't ask about the parent node,
// I just do it. Hope that's ok for now.
	if ((j = stringInListCI(t->attributes, "href")) >= 0) {
		v = t->atvals[j];
		if (v && !*v) v = 0;
		if (v) {
			v = v[0] == '#' ? cloneString(v) : resolveURL(cf->hbase, v);
			if (action == TAGACT_BASE && !cf->baseset && isURL(v)) {
				nzFree(cf->hbase);
				cf->hbase = cloneString(v);
				cf->baseset = true;
			}
// first href wins
			if(!t->href) t->href = (char*)v;
			else cnzFree(v);
		}
	}
	if ((j = stringInListCI(t->attributes, "src")) >= 0) {
		v = t->atvals[j];
		if (v && !*v) v = 0;
// first src wins
		if (v && !t->href)
			t->href = resolveURL(cf->hbase, v);
	}
	if ((j = stringInListCI(t->attributes, "action")) >= 0) {
		v = t->atvals[j];
		if (v && !*v) v = 0;
// first action wins
		if (v && !t->href)
			t->href = resolveURL(cf->hbase, v);
	}
// href=javascript:foo() is another doorway into js
	if (t->href && memEqualCI(t->href, "javascript:", 11))
		t->doorway = true;
}

// Push an attribute onto an html tag.
// Value is already allocated, name is not.
void setTagAttr(Tag *t, const char *name, char *val)
{
	int nattr = 0;		/* number of attributes */
	int i = -1;
	if (!val)
		return;
	if (t->attributes) {
		for (nattr = 0; t->attributes[nattr]; ++nattr)
			if (stringEqualCI(name, t->attributes[nattr]))
				i = nattr;
	}
	if (i >= 0) {
		cnzFree(t->atvals[i]);
		t->atvals[i] = val;
		return;
	}
/* push */
	if (!nattr) {
		t->attributes = allocMem(sizeof(char *) * 2);
		t->atvals = allocMem(sizeof(char *) * 2);
	} else {
		t->attributes =
		    reallocMem(t->attributes, sizeof(char *) * (nattr + 2));
		t->atvals = reallocMem(t->atvals, sizeof(char *) * (nattr + 2));
	}
	t->attributes[nattr] = cloneString(name);
	t->atvals[nattr] = val;
	++nattr;
	t->attributes[nattr] = 0;
	t->atvals[nattr] = 0;
}

static void setAttrFromHTML(const char *a1, const char *a2, const char *v1, const char *v2)
{
	char *w;
	char save_c;
	w = pullAnd(v1, v2, true);
// yeah this is tacky, write on top of a const, but I'll put it back.
	save_c = *a2, *(char*)a2 = 0;
	if(debugScanner && debugLevel >= 3) printf("%s=%s\n", a1, w);
	setTagAttr(working_t, a1, w);
	*(char*)a2 = save_c;
}

const char *attribVal(const Tag *t, const char *name)
{
	const char *v;
	int j;
	if (!t->attributes)
		return 0;
	j = stringInListCI(t->attributes, name);
	if (j < 0)
		return 0;
	v = t->atvals[j];
	return v;
}

bool attribPresent(const Tag *t, const char *name)
{
	int j = stringInListCI(t->attributes, name);
	return (j >= 0);
}

// make an allocated copy of the designated string,
// then decode the & fragments.
static char *pullAnd(const char *start, const char *end, bool intag)
{
	char *s, *t;
	unsigned u; // unicode
	char *u8;
	char *entity;
	int l = end - start;
	char *w = pullString(start, l);

// the assumption here is that &stuff always encodes to something smaller
// when represented as utf8.
// Example: &pi; is pretty short, but the utf8 for pi is 3 bytes, so we're good.

	for(s = t = w; *s; ++s) {
		if(*s != '&') goto putc;
		if(s[1] == '#' && (isdigitByte(s[2]) || ((s[2] == 'x' || s[2] == 'X') && isxdigit(s[3])))) {
			if(isdigitByte(s[2])) u = strtol(s+2, &s, 10);
			else u = strtol(s+3, &s, 16);
putuni:
// tidy issues a warning if no ; but tolerates it
			if(*s != ';') --s;
// nulls are not allowed, nor the low bytes as edbrowse uses them internally.
			if(u <= 4) u = ' ';
			u8 = uni2utf8(u);
			strcpy(t, u8);
			t += strlen(t);
			continue;
		}
		if(!isalphaByte(s[1])) goto putc;
// there is an identifier after &
		entity = ++s;
		for(++s; isalnumByte(*s); ++s)  ;

/*********************************************************************
The string from entity to s is the reserved word after & and before ;
As in &nbsp;
We're going to convert it into utf8 and paste it in.
*s should be a semicolon, but isn't always.
I already mentioned that &thing is bad html, should be &thing;
but it appears all over the place, so we have to tolerate it,
and encode it, just as tidy does.
This line should parse in edbrowse and every other browser,
even though two of the three semicolons are missing.
My &lang is English. &pi;r&sup2
I only encode it if thing is a recognized keyword, again, following tidy's lead.
Otherwise I leave &thing or &thing; alone.
Thus &nonsense remains &nonsense
But here is some more bad html that we are suppose to parse as intended,
not as written.
            <a href="https://test.com/?a=1&b=2&lang=en">Test</a>
should be
            <a href="https://test.com/?a=1&amp;b=2&amp;lang=en">Test</a>
This kind of bad html is everywhere too, but usually harmless,
as long as the tag attributes are not recognized html keywords.
They usually aren't, and on we go. But in this example,
the first two are not keywords, but lang is, and is thus turned into utf8.
So 90% of the time the author wrote &lang and intended &lang; and I'm
suppose to know that, but in this case he wrote &lang and meant it literally,
and even here he should have written &amp; lang but didn't, and I'm
suppose to know that as well.
My compromise here is to insist upon the ; if we are in tag attribute,
rather than plain (visible) html text.
Note that I am still parsing bad html, just in a different way.
Also, this kind of error correction shouldn't happen at all for xml.
The syntax of xml is suppose to be correct exactly as written,
just like javascript, and it's on the author to write it correctly.
(You can force xml parsing by putting `~*xml}@; at the start of your html file.)
Thus, this long block comment is here to explain the next line of code.
*********************************************************************/

		if(*s != ';' && // oops, bad html
		(intag | cf->xmlMode))
			u = 0; // never mind
		else
			u = andLookup(entity, s);
		if(u) goto putuni;
// word not recognized, leave &string in place.
		s = entity - 1;
putc:
		*t++ = *s;
	}
	*t = 0;

	return w;
}

// entity words and codes taken from
// https://www.w3schools.com/charsets/ref_html_entities_4.asp
// then sorted for binary search.
static const struct entity { unsigned int u; const char *word; } andlist[] = {
{198, "AElig"},
{193, "Aacute"},
{258, "Abreve"},
{194, "Acirc"},
{1040, "Acy"},
{120068, "Afr"},
{192, "Agrave"},
{913, "Alpha"},
{256, "Amacr"},
{10835, "And"},
{260, "Aogon"},
{120120, "Aopf"},
{8289, "ApplyFunction"},
{197, "Aring"},
{119964, "Ascr"},
{8788, "Assign"},
{195, "Atilde"},
{196, "Auml"},
{8726, "Backslash"},
{10983, "Barv"},
{1041, "Bcy"},
{8492, "Bernoullis"},
{914, "Beta"},
{120069, "Bfr"},
{120121, "Bopf"},
{728, "Breve"},
{8492, "Bscr"},
{8782, "Bumpeq"},
{1063, "CHcy"},
{262, "Cacute"},
{8914, "Cap"},
{8517, "CapitalDifferentialD"},
{8493, "Cayleys"},
{268, "Ccaron"},
{199, "Ccedil"},
{264, "Ccirc"},
{8752, "Cconint"},
{266, "Cdot"},
{184, "Cedilla"},
{183, "CenterDot"},
{8493, "Cfr"},
{935, "Chi"},
{8857, "CircleDot"},
{8854, "CircleMinus"},
{8853, "CirclePlus"},
{8855, "CircleTimes"},
{8754, "ClockwiseContourIntegral"},
{8221, "CloseCurlyDoubleQuote"},
{8217, "CloseCurlyQuote"},
{8759, "Colon"},
{10868, "Colone"},
{8801, "Congruent"},
{8751, "Conint"},
{8750, "ContourIntegral"},
{8450, "Copf"},
{8720, "Coproduct"},
{10799, "Cross"},
{119966, "Cscr"},
{8915, "Cup"},
{8781, "CupCap"},
{8517, "DD"},
{10513, "DDotrahd"},
{1026, "DJcy"},
{1029, "DScy"},
{1039, "DZcy"},
{8609, "Darr"},
{10980, "Dashv"},
{270, "Dcaron"},
{1044, "Dcy"},
{8711, "Del"},
{916, "Delta"},
{120071, "Dfr"},
{180, "DiacriticalAcute"},
{729, "DiacriticalDot"},
{733, "DiacriticalDoubleAcute"},
{96, "DiacriticalGrave"},
{732, "DiacriticalTilde"},
{8900, "Diamond"},
{8518, "DifferentialD"},
{120123, "Dopf"},
{168, "Dot"},
{8412, "DotDot"},
{8784, "DotEqual"},
{8751, "DoubleContourIntegral"},
{168, "DoubleDot"},
{8659, "DoubleDownArrow"},
{8656, "DoubleLeftArrow"},
{8660, "DoubleLeftRightArrow"},
{10980, "DoubleLeftTee"},
{10232, "DoubleLongLeftArrow"},
{10234, "DoubleLongLeftRightArrow"},
{10233, "DoubleLongRightArrow"},
{8658, "DoubleRightArrow"},
{8872, "DoubleRightTee"},
{8657, "DoubleUpArrow"},
{8661, "DoubleUpDownArrow"},
{8741, "DoubleVerticalBar"},
{8595, "DownArrow"},
{10515, "DownArrowBar"},
{8693, "DownArrowUpArrow"},
{785, "DownBreve"},
{10576, "DownLeftRightVector"},
{10590, "DownLeftTeeVector"},
{8637, "DownLeftVector"},
{10582, "DownLeftVectorBar"},
{10591, "DownRightTeeVector"},
{8641, "DownRightVector"},
{10583, "DownRightVectorBar"},
{8868, "DownTee"},
{8615, "DownTeeArrow"},
{8659, "Downarrow"},
{119967, "Dscr"},
{272, "Dstrok"},
{330, "ENG"},
{208, "ETH"},
{201, "Eacute"},
{282, "Ecaron"},
{202, "Ecirc"},
{1069, "Ecy"},
{278, "Edot"},
{120072, "Efr"},
{200, "Egrave"},
{8712, "Element"},
{274, "Emacr"},
{9723, "EmptySmallSquare"},
{9643, "EmptyVerySmallSquare"},
{280, "Eogon"},
{120124, "Eopf"},
{917, "Epsilon"},
{10869, "Equal"},
{8770, "EqualTilde"},
{8652, "Equilibrium"},
{8496, "Escr"},
{10867, "Esim"},
{919, "Eta"},
{203, "Euml"},
{8707, "Exists"},
{8519, "ExponentialE"},
{1060, "Fcy"},
{120073, "Ffr"},
{9724, "FilledSmallSquare"},
{9642, "FilledVerySmallSquare"},
{120125, "Fopf"},
{8704, "ForAll"},
{8497, "Fouriertrf"},
{8497, "Fscr"},
{1027, "GJcy"},
{62, "GT"},
{915, "Gamma"},
{988, "Gammad"},
{286, "Gbreve"},
{290, "Gcedil"},
{284, "Gcirc"},
{1043, "Gcy"},
{288, "Gdot"},
{120074, "Gfr"},
{8921, "Gg"},
{120126, "Gopf"},
{8805, "GreaterEqual"},
{8923, "GreaterEqualLess"},
{8807, "GreaterFullEqual"},
{10914, "GreaterGreater"},
{8823, "GreaterLess"},
{10878, "GreaterSlantEqual"},
{8819, "GreaterTilde"},
{119970, "Gscr"},
{8811, "Gt"},
{1066, "HARDcy"},
{711, "Hacek"},
{94, "Hat"},
{292, "Hcirc"},
{8460, "Hfr"},
{8459, "HilbertSpace"},
{8461, "Hopf"},
{9472, "HorizontalLine"},
{8459, "Hscr"},
{294, "Hstrok"},
{8782, "HumpDownHump"},
{8783, "HumpEqual"},
{1045, "IEcy"},
{306, "IJlig"},
{1025, "IOcy"},
{205, "Iacute"},
{206, "Icirc"},
{1048, "Icy"},
{304, "Idot"},
{8465, "Ifr"},
{204, "Igrave"},
{8465, "Im"},
{298, "Imacr"},
{8520, "ImaginaryI"},
{8658, "Implies"},
{8748, "Int"},
{8747, "Integral"},
{8898, "Intersection"},
{8291, "InvisibleComma"},
{8290, "InvisibleTimes"},
{302, "Iogon"},
{120128, "Iopf"},
{921, "Iota"},
{8464, "Iscr"},
{296, "Itilde"},
{1030, "Iukcy"},
{207, "Iuml"},
{308, "Jcirc"},
{1049, "Jcy"},
{120077, "Jfr"},
{120129, "Jopf"},
{119973, "Jscr"},
{1032, "Jsercy"},
{1028, "Jukcy"},
{1061, "KHcy"},
{1036, "KJcy"},
{922, "Kappa"},
{310, "Kcedil"},
{1050, "Kcy"},
{120078, "Kfr"},
{120130, "Kopf"},
{119974, "Kscr"},
{1033, "LJcy"},
{313, "Lacute"},
{923, "Lambda"},
{10218, "Lang"},
{8466, "Laplacetrf"},
{8606, "Larr"},
{317, "Lcaron"},
{315, "Lcedil"},
{1051, "Lcy"},
{10216, "LeftAngleBracket"},
{8592, "LeftArrow"},
{8676, "LeftArrowBar"},
{8646, "LeftArrowRightArrow"},
{8968, "LeftCeiling"},
{10214, "LeftDoubleBracket"},
{10593, "LeftDownTeeVector"},
{8643, "LeftDownVector"},
{10585, "LeftDownVectorBar"},
{8970, "LeftFloor"},
{8596, "LeftRightArrow"},
{10574, "LeftRightVector"},
{8867, "LeftTee"},
{8612, "LeftTeeArrow"},
{10586, "LeftTeeVector"},
{8882, "LeftTriangle"},
{10703, "LeftTriangleBar"},
{8884, "LeftTriangleEqual"},
{10577, "LeftUpDownVector"},
{10592, "LeftUpTeeVector"},
{8639, "LeftUpVector"},
{10584, "LeftUpVectorBar"},
{8636, "LeftVector"},
{10578, "LeftVectorBar"},
{8656, "Leftarrow"},
{8660, "Leftrightarrow"},
{8922, "LessEqualGreater"},
{8806, "LessFullEqual"},
{8822, "LessGreater"},
{10913, "LessLess"},
{10877, "LessSlantEqual"},
{8818, "LessTilde"},
{120079, "Lfr"},
{8920, "Ll"},
{8666, "Lleftarrow"},
{319, "Lmidot"},
{10229, "LongLeftArrow"},
{10231, "LongLeftRightArrow"},
{10230, "LongRightArrow"},
{10232, "Longleftarrow"},
{10234, "Longleftrightarrow"},
{10233, "Longrightarrow"},
{120131, "Lopf"},
{8601, "LowerLeftArrow"},
{8600, "LowerRightArrow"},
{8466, "Lscr"},
{8624, "Lsh"},
{321, "Lstrok"},
{8810, "Lt"},
{10501, "Map"},
{1052, "Mcy"},
{8287, "MediumSpace"},
{8499, "Mellintrf"},
{120080, "Mfr"},
{8723, "MinusPlus"},
{120132, "Mopf"},
{8499, "Mscr"},
{924, "Mu"},
{1034, "NJcy"},
{323, "Nacute"},
{327, "Ncaron"},
{325, "Ncedil"},
{1053, "Ncy"},
{8811, "NestedGreaterGreater"},
{8810, "NestedLessLess"},
{10, "NewLine"},
{120081, "Nfr"},
{8288, "NoBreak"},
{8469, "Nopf"},
{10988, "Not"},
{8802, "NotCongruent"},
{8813, "NotCupCap"},
{8742, "NotDoubleVerticalBar"},
{8713, "NotElement"},
{8800, "NotEqual"},
{8708, "NotExists"},
{8815, "NotGreater"},
{8817, "NotGreaterEqual"},
{8825, "NotGreaterLess"},
{8821, "NotGreaterTilde"},
{8938, "NotLeftTriangle"},
{8940, "NotLeftTriangleEqual"},
{8814, "NotLess"},
{8816, "NotLessEqual"},
{8824, "NotLessGreater"},
{8820, "NotLessTilde"},
{8832, "NotPrecedes"},
{8928, "NotPrecedesSlantEqual"},
{8716, "NotReverseElement"},
{8939, "NotRightTriangle"},
{8941, "NotRightTriangleEqual"},
{8930, "NotSquareSubsetEqual"},
{8931, "NotSquareSupersetEqual"},
{8840, "NotSubsetEqual"},
{8833, "NotSucceeds"},
{8929, "NotSucceedsSlantEqual"},
{8841, "NotSupersetEqual"},
{8769, "NotTilde"},
{8772, "NotTildeEqual"},
{8775, "NotTildeFullEqual"},
{8777, "NotTildeTilde"},
{8740, "NotVerticalBar"},
{119977, "Nscr"},
{209, "Ntilde"},
{925, "Nu"},
{338, "OElig"},
{211, "Oacute"},
{212, "Ocirc"},
{1054, "Ocy"},
{336, "Odblac"},
{120082, "Ofr"},
{210, "Ograve"},
{332, "Omacr"},
{937, "Omega"},
{927, "Omicron"},
{120134, "Oopf"},
{8220, "OpenCurlyDoubleQuote"},
{8216, "OpenCurlyQuote"},
{10836, "Or"},
{119978, "Oscr"},
{216, "Oslash"},
{213, "Otilde"},
{10807, "Otimes"},
{214, "Ouml"},
{8254, "OverBar"},
{9182, "OverBrace"},
{9140, "OverBracket"},
{9180, "OverParenthesis"},
{8706, "PartialD"},
{1055, "Pcy"},
{120083, "Pfr"},
{934, "Phi"},
{928, "Pi"},
{8460, "Poincareplane"},
{8473, "Popf"},
{10939, "Pr"},
{8826, "Precedes"},
{10927, "PrecedesEqual"},
{8828, "PrecedesSlantEqual"},
{8830, "PrecedesTilde"},
{8243, "Prime"},
{8719, "Product"},
{8759, "Proportion"},
{8733, "Proportional"},
{119979, "Pscr"},
{936, "Psi"},
{120084, "Qfr"},
{8474, "Qopf"},
{119980, "Qscr"},
{10512, "RBarr"},
{340, "Racute"},
{10219, "Rang"},
{8608, "Rarr"},
{10518, "Rarrtl"},
{344, "Rcaron"},
{342, "Rcedil"},
{1056, "Rcy"},
{8476, "Re"},
{8715, "ReverseElement"},
{8651, "ReverseEquilibrium"},
{10607, "ReverseUpEquilibrium"},
{8476, "Rfr"},
{929, "Rho"},
{10217, "RightAngleBracket"},
{8594, "RightArrow"},
{8677, "RightArrowBar"},
{8644, "RightArrowLeftArrow"},
{8969, "RightCeiling"},
{10215, "RightDoubleBracket"},
{10589, "RightDownTeeVector"},
{8642, "RightDownVector"},
{10581, "RightDownVectorBar"},
{8971, "RightFloor"},
{8866, "RightTee"},
{8614, "RightTeeArrow"},
{10587, "RightTeeVector"},
{8883, "RightTriangle"},
{10704, "RightTriangleBar"},
{8885, "RightTriangleEqual"},
{10575, "RightUpDownVector"},
{10588, "RightUpTeeVector"},
{8638, "RightUpVector"},
{10580, "RightUpVectorBar"},
{8640, "RightVector"},
{10579, "RightVectorBar"},
{8658, "Rightarrow"},
{8477, "Ropf"},
{10608, "RoundImplies"},
{8667, "Rrightarrow"},
{8475, "Rscr"},
{8625, "Rsh"},
{10740, "RuleDelayed"},
{1065, "SHCHcy"},
{1064, "SHcy"},
{1068, "SOFTcy"},
{346, "Sacute"},
{10940, "Sc"},
{352, "Scaron"},
{350, "Scedil"},
{348, "Scirc"},
{1057, "Scy"},
{120086, "Sfr"},
{8595, "ShortDownArrow"},
{8592, "ShortLeftArrow"},
{8594, "ShortRightArrow"},
{8593, "ShortUpArrow"},
{931, "Sigma"},
{8728, "SmallCircle"},
{120138, "Sopf"},
{8730, "Sqrt"},
{9633, "Square"},
{8851, "SquareIntersection"},
{8847, "SquareSubset"},
{8849, "SquareSubsetEqual"},
{8848, "SquareSuperset"},
{8850, "SquareSupersetEqual"},
{8852, "SquareUnion"},
{119982, "Sscr"},
{8902, "Star"},
{8912, "Sub"},
{8912, "Subset"},
{8838, "SubsetEqual"},
{8827, "Succeeds"},
{10928, "SucceedsEqual"},
{8829, "SucceedsSlantEqual"},
{8831, "SucceedsTilde"},
{8715, "SuchThat"},
{8721, "Sum"},
{8913, "Sup"},
{8835, "Superset"},
{8839, "SupersetEqual"},
{8913, "Supset"},
{222, "THORN"},
{8482, "TRADE"},
{1035, "TSHcy"},
{1062, "TScy"},
{9, "Tab"},
{932, "Tau"},
{356, "Tcaron"},
{354, "Tcedil"},
{1058, "Tcy"},
{120087, "Tfr"},
{8756, "Therefore"},
{920, "Theta"},
{8201, "ThinSpace"},
{8764, "Tilde"},
{8771, "TildeEqual"},
{8773, "TildeFullEqual"},
{8776, "TildeTilde"},
{120139, "Topf"},
{8411, "TripleDot"},
{119983, "Tscr"},
{358, "Tstrok"},
{218, "Uacute"},
{8607, "Uarr"},
{10569, "Uarrocir"},
{1038, "Ubrcy"},
{364, "Ubreve"},
{219, "Ucirc"},
{1059, "Ucy"},
{368, "Udblac"},
{120088, "Ufr"},
{217, "Ugrave"},
{362, "Umacr"},
{95, "UnderBar"},
{9183, "UnderBrace"},
{9141, "UnderBracket"},
{9181, "UnderParenthesis"},
{8899, "Union"},
{8846, "UnionPlus"},
{370, "Uogon"},
{120140, "Uopf"},
{8593, "UpArrow"},
{10514, "UpArrowBar"},
{8645, "UpArrowDownArrow"},
{8597, "UpDownArrow"},
{10606, "UpEquilibrium"},
{8869, "UpTee"},
{8613, "UpTeeArrow"},
{8657, "Uparrow"},
{8661, "Updownarrow"},
{8598, "UpperLeftArrow"},
{8599, "UpperRightArrow"},
{978, "Upsi"},
{933, "Upsilon"},
{366, "Uring"},
{119984, "Uscr"},
{360, "Utilde"},
{220, "Uuml"},
{8875, "VDash"},
{10987, "Vbar"},
{1042, "Vcy"},
{8873, "Vdash"},
{10982, "Vdashl"},
{8897, "Vee"},
{8214, "Verbar"},
{8214, "Vert"},
{8739, "VerticalBar"},
{10072, "VerticalSeparator"},
{8768, "VerticalTilde"},
{8202, "VeryThinSpace"},
{120089, "Vfr"},
{120141, "Vopf"},
{119985, "Vscr"},
{8874, "Vvdash"},
{372, "Wcirc"},
{8896, "Wedge"},
{120090, "Wfr"},
{120142, "Wopf"},
{119986, "Wscr"},
{120091, "Xfr"},
{926, "Xi"},
{120143, "Xopf"},
{119987, "Xscr"},
{1071, "YAcy"},
{1031, "YIcy"},
{1070, "YUcy"},
{221, "Yacute"},
{374, "Ycirc"},
{1067, "Ycy"},
{120092, "Yfr"},
{120144, "Yopf"},
{119988, "Yscr"},
{376, "Yuml"},
{1046, "ZHcy"},
{377, "Zacute"},
{381, "Zcaron"},
{1047, "Zcy"},
{379, "Zdot"},
{8203, "ZeroWidthSpace"},
{918, "Zeta"},
{8488, "Zfr"},
{8484, "Zopf"},
{119989, "Zscr"},
{225, "aacute"},
{259, "abreve"},
{8766, "ac"},
{8767, "acd"},
{226, "acirc"},
{180, "acute"},
{1072, "acy"},
{230, "aelig"},
{8289, "af"},
{120094, "afr"},
{224, "agrave"},
{8501, "alefsym"},
{8501, "aleph"},
{945, "alpha"},
{257, "amacr"},
{10815, "amalg"},
{38, "amp"},
{8743, "and"},
{10837, "andand"},
{10844, "andd"},
{10840, "andslope"},
{10842, "andv"},
{8736, "ang"},
{10660, "ange"},
{8736, "angle"},
{8737, "angmsd"},
{10664, "angmsdaa"},
{10665, "angmsdab"},
{10666, "angmsdac"},
{10667, "angmsdad"},
{10668, "angmsdae"},
{10669, "angmsdaf"},
{10670, "angmsdag"},
{10671, "angmsdah"},
{8735, "angrt"},
{8894, "angrtvb"},
{10653, "angrtvbd"},
{8738, "angsph"},
{197, "angst"},
{9084, "angzarr"},
{261, "aogon"},
{120146, "aopf"},
{8776, "ap"},
{10864, "apE"},
{10863, "apacir"},
{8778, "ape"},
{8779, "apid"},
{39, "apos"},
{8776, "approx"},
{8778, "approxeq"},
{229, "aring"},
{119990, "ascr"},
{42, "ast"},
{8776, "asymp"},
{8781, "asympeq"},
{227, "atilde"},
{228, "auml"},
{8755, "awconint"},
{10769, "awint"},
{10989, "bNot"},
{8780, "backcong"},
{1014, "backepsilon"},
{8245, "backprime"},
{8765, "backsim"},
{8909, "backsimeq"},
{8893, "barvee"},
{8965, "barwedge"},
{9141, "bbrk"},
{9142, "bbrktbrk"},
{8780, "bcong"},
{1073, "bcy"},
{8222, "bdquo"},
{8757, "because"},
{10672, "bemptyv"},
{1014, "bepsi"},
{8492, "bernou"},
{946, "beta"},
{8502, "beth"},
{8812, "between"},
{120095, "bfr"},
{8898, "bigcap"},
{9711, "bigcirc"},
{8899, "bigcup"},
{10752, "bigodot"},
{10753, "bigoplus"},
{10754, "bigotimes"},
{10758, "bigsqcup"},
{9733, "bigstar"},
{9661, "bigtriangledown"},
{9651, "bigtriangleup"},
{10756, "biguplus"},
{8897, "bigvee"},
{8896, "bigwedge"},
{10509, "bkarow"},
{10731, "blacklozenge"},
{9642, "blacksquare"},
{9652, "blacktriangle"},
{9662, "blacktriangledown"},
{9666, "blacktriangleleft"},
{9656, "blacktriangleright"},
{9251, "blank"},
{9618, "blk12"},
{9617, "blk14"},
{9619, "blk34"},
{9608, "block"},
{8976, "bnot"},
{120147, "bopf"},
{8869, "bot"},
{8869, "bottom"},
{8904, "bowtie"},
{9559, "boxDL"},
{9556, "boxDR"},
{9558, "boxDl"},
{9555, "boxDr"},
{9552, "boxH"},
{9574, "boxHD"},
{9577, "boxHU"},
{9572, "boxHd"},
{9575, "boxHu"},
{9565, "boxUL"},
{9562, "boxUR"},
{9564, "boxUl"},
{9561, "boxUr"},
{9553, "boxV"},
{9580, "boxVH"},
{9571, "boxVL"},
{9568, "boxVR"},
{9579, "boxVh"},
{9570, "boxVl"},
{9567, "boxVr"},
{10697, "boxbox"},
{9557, "boxdL"},
{9554, "boxdR"},
{9488, "boxdl"},
{9484, "boxdr"},
{9472, "boxh"},
{9573, "boxhD"},
{9576, "boxhU"},
{9516, "boxhd"},
{9524, "boxhu"},
{8863, "boxminus"},
{8862, "boxplus"},
{8864, "boxtimes"},
{9563, "boxuL"},
{9560, "boxuR"},
{9496, "boxul"},
{9492, "boxur"},
{9474, "boxv"},
{9578, "boxvH"},
{9569, "boxvL"},
{9566, "boxvR"},
{9532, "boxvh"},
{9508, "boxvl"},
{9500, "boxvr"},
{8245, "bprime"},
{728, "breve"},
{166, "brvbar"},
{119991, "bscr"},
{8271, "bsemi"},
{8765, "bsim"},
{8909, "bsime"},
{92, "bsol"},
{10693, "bsolb"},
{10184, "bsolhsub"},
{8226, "bull"},
{8226, "bullet"},
{8782, "bump"},
{10926, "bumpE"},
{8783, "bumpe"},
{8783, "bumpeq"},
{263, "cacute"},
{8745, "cap"},
{10820, "capand"},
{10825, "capbrcup"},
{10827, "capcap"},
{10823, "capcup"},
{10816, "capdot"},
{8257, "caret"},
{711, "caron"},
{10829, "ccaps"},
{269, "ccaron"},
{231, "ccedil"},
{265, "ccirc"},
{10828, "ccups"},
{10832, "ccupssm"},
{267, "cdot"},
{184, "cedil"},
{10674, "cemptyv"},
{162, "cent"},
{183, "centerdot"},
{120096, "cfr"},
{1095, "chcy"},
{10003, "check"},
{10003, "checkmark"},
{967, "chi"},
{9675, "cir"},
{10691, "cirE"},
{710, "circ"},
{8791, "circeq"},
{8634, "circlearrowleft"},
{8635, "circlearrowright"},
{174, "circledR"},
{9416, "circledS"},
{8859, "circledast"},
{8858, "circledcirc"},
{8861, "circleddash"},
{8791, "cire"},
{10768, "cirfnint"},
{10991, "cirmid"},
{10690, "cirscir"},
{9827, "clubs"},
{9827, "clubsuit"},
{58, "colon"},
{8788, "colone"},
{8788, "coloneq"},
{44, "comma"},
{64, "commat"},
{8705, "comp"},
{8728, "compfn"},
{8705, "complement"},
{8450, "complexes"},
{8773, "cong"},
{10861, "congdot"},
{8750, "conint"},
{120148, "copf"},
{8720, "coprod"},
{169, "copy"},
{8471, "copysr"},
{8629, "crarr"},
{10007, "cross"},
{119992, "cscr"},
{10959, "csub"},
{10961, "csube"},
{10960, "csup"},
{10962, "csupe"},
{8943, "ctdot"},
{10552, "cudarrl"},
{10549, "cudarrr"},
{8926, "cuepr"},
{8927, "cuesc"},
{8630, "cularr"},
{10557, "cularrp"},
{8746, "cup"},
{10824, "cupbrcap"},
{10822, "cupcap"},
{10826, "cupcup"},
{8845, "cupdot"},
{10821, "cupor"},
{8631, "curarr"},
{10556, "curarrm"},
{8926, "curlyeqprec"},
{8927, "curlyeqsucc"},
{8910, "curlyvee"},
{8911, "curlywedge"},
{164, "curren"},
{8630, "curvearrowleft"},
{8631, "curvearrowright"},
{8910, "cuvee"},
{8911, "cuwed"},
{8754, "cwconint"},
{8753, "cwint"},
{9005, "cylcty"},
{8659, "dArr"},
{10597, "dHar"},
{8224, "dagger"},
{8504, "daleth"},
{8595, "darr"},
{8208, "dash"},
{8867, "dashv"},
{10511, "dbkarow"},
{733, "dblac"},
{271, "dcaron"},
{1076, "dcy"},
{8518, "dd"},
{8225, "ddagger"},
{8650, "ddarr"},
{10871, "ddotseq"},
{176, "deg"},
{948, "delta"},
{10673, "demptyv"},
{10623, "dfisht"},
{120097, "dfr"},
{8643, "dharl"},
{8642, "dharr"},
{8900, "diam"},
{8900, "diamond"},
{9830, "diamondsuit"},
{9830, "diams"},
{168, "die"},
{989, "digamma"},
{8946, "disin"},
{247, "div"},
{247, "divide"},
{8903, "divideontimes"},
{8903, "divonx"},
{1106, "djcy"},
{8990, "dlcorn"},
{8973, "dlcrop"},
{36, "dollar"},
{120149, "dopf"},
{729, "dot"},
{8784, "doteq"},
{8785, "doteqdot"},
{8760, "dotminus"},
{8724, "dotplus"},
{8865, "dotsquare"},
{8966, "doublebarwedge"},
{8595, "downarrow"},
{8650, "downdownarrows"},
{8643, "downharpoonleft"},
{8642, "downharpoonright"},
{10512, "drbkarow"},
{8991, "drcorn"},
{8972, "drcrop"},
{119993, "dscr"},
{1109, "dscy"},
{10742, "dsol"},
{273, "dstrok"},
{8945, "dtdot"},
{9663, "dtri"},
{9662, "dtrif"},
{8693, "duarr"},
{10607, "duhar"},
{10662, "dwangle"},
{1119, "dzcy"},
{10239, "dzigrarr"},
{10871, "eDDot"},
{8785, "eDot"},
{233, "eacute"},
{10862, "easter"},
{283, "ecaron"},
{8790, "ecir"},
{234, "ecirc"},
{8789, "ecolon"},
{1101, "ecy"},
{279, "edot"},
{8519, "ee"},
{8786, "efDot"},
{120098, "efr"},
{10906, "eg"},
{232, "egrave"},
{10902, "egs"},
{10904, "egsdot"},
{10905, "el"},
{9191, "elinters"},
{8467, "ell"},
{10901, "els"},
{10903, "elsdot"},
{275, "emacr"},
{8709, "empty"},
{8709, "emptyset"},
{8709, "emptyv"},
{8195, "emsp"},
{8196, "emsp13"},
{8197, "emsp14"},
{331, "eng"},
{8194, "ensp"},
{281, "eogon"},
{120150, "eopf"},
{8917, "epar"},
{10723, "eparsl"},
{10865, "eplus"},
{949, "epsi"},
{949, "epsilon"},
{1013, "epsiv"},
{8790, "eqcirc"},
{8789, "eqcolon"},
{8770, "eqsim"},
{10902, "eqslantgtr"},
{10901, "eqslantless"},
{61, "equals"},
{8799, "equest"},
{8801, "equiv"},
{10872, "equivDD"},
{10725, "eqvparsl"},
{8787, "erDot"},
{10609, "erarr"},
{8495, "escr"},
{8784, "esdot"},
{8770, "esim"},
{951, "eta"},
{240, "eth"},
{235, "euml"},
{8364, "euro"},
{33, "excl"},
{8707, "exist"},
{8496, "expectation"},
{8519, "exponentiale"},
{8786, "fallingdotseq"},
{1092, "fcy"},
{9792, "female"},
{64259, "ffilig"},
{64256, "fflig"},
{64260, "ffllig"},
{120099, "ffr"},
{64257, "filig"},
{9837, "flat"},
{64258, "fllig"},
{9649, "fltns"},
{402, "fnof"},
{120151, "fopf"},
{8704, "forall"},
{8916, "fork"},
{10969, "forkv"},
{10765, "fpartint"},
{189, "frac12"},
{8531, "frac13"},
{188, "frac14"},
{8533, "frac15"},
{8537, "frac16"},
{8539, "frac18"},
{8532, "frac23"},
{8534, "frac25"},
{190, "frac34"},
{8535, "frac35"},
{8540, "frac38"},
{8536, "frac45"},
{8538, "frac56"},
{8541, "frac58"},
{8542, "frac78"},
{8260, "frasl"},
{8994, "frown"},
{119995, "fscr"},
{8807, "gE"},
{10892, "gEl"},
{501, "gacute"},
{947, "gamma"},
{989, "gammad"},
{10886, "gap"},
{287, "gbreve"},
{285, "gcirc"},
{1075, "gcy"},
{289, "gdot"},
{8805, "ge"},
{8923, "gel"},
{8805, "geq"},
{8807, "geqq"},
{10878, "geqslant"},
{10878, "ges"},
{10921, "gescc"},
{10880, "gesdot"},
{10882, "gesdoto"},
{10884, "gesdotol"},
{10900, "gesles"},
{120100, "gfr"},
{8811, "gg"},
{8921, "ggg"},
{8503, "gimel"},
{1107, "gjcy"},
{8823, "gl"},
{10898, "glE"},
{10917, "gla"},
{10916, "glj"},
{8809, "gnE"},
{10890, "gnap"},
{10890, "gnapprox"},
{10888, "gne"},
{10888, "gneq"},
{8809, "gneqq"},
{8935, "gnsim"},
{120152, "gopf"},
{96, "grave"},
{8458, "gscr"},
{8819, "gsim"},
{10894, "gsime"},
{10896, "gsiml"},
{62, "gt"},
{10919, "gtcc"},
{10874, "gtcir"},
{8919, "gtdot"},
{10645, "gtlPar"},
{10876, "gtquest"},
{10886, "gtrapprox"},
{10616, "gtrarr"},
{8919, "gtrdot"},
{8923, "gtreqless"},
{10892, "gtreqqless"},
{8823, "gtrless"},
{8819, "gtrsim"},
{8660, "hArr"},
{8202, "hairsp"},
{189, "half"},
{8459, "hamilt"},
{1098, "hardcy"},
{8596, "harr"},
{10568, "harrcir"},
{8621, "harrw"},
{8463, "hbar"},
{293, "hcirc"},
{9829, "hearts"},
{9829, "heartsuit"},
{8230, "hellip"},
{8889, "hercon"},
{120101, "hfr"},
{10533, "hksearow"},
{10534, "hkswarow"},
{8703, "hoarr"},
{8763, "homtht"},
{8617, "hookleftarrow"},
{8618, "hookrightarrow"},
{120153, "hopf"},
{8213, "horbar"},
{119997, "hscr"},
{8463, "hslash"},
{295, "hstrok"},
{8259, "hybull"},
{8208, "hyphen"},
{237, "iacute"},
{8291, "ic"},
{238, "icirc"},
{1080, "icy"},
{1077, "iecy"},
{161, "iexcl"},
{8660, "iff"},
{120102, "ifr"},
{236, "igrave"},
{8520, "ii"},
{10764, "iiiint"},
{8749, "iiint"},
{10716, "iinfin"},
{8489, "iiota"},
{307, "ijlig"},
{299, "imacr"},
{8465, "image"},
{8464, "imagline"},
{8465, "imagpart"},
{305, "imath"},
{8887, "imof"},
{437, "imped"},
{8712, "in"},
{8453, "incare"},
{8734, "infin"},
{10717, "infintie"},
{305, "inodot"},
{8747, "int"},
{8890, "intcal"},
{8484, "integers"},
{8890, "intercal"},
{10775, "intlarhk"},
{10812, "intprod"},
{1105, "iocy"},
{303, "iogon"},
{120154, "iopf"},
{953, "iota"},
{10812, "iprod"},
{191, "iquest"},
{119998, "iscr"},
{8712, "isin"},
{8953, "isinE"},
{8949, "isindot"},
{8948, "isins"},
{8947, "isinsv"},
{8712, "isinv"},
{8290, "it"},
{297, "itilde"},
{1110, "iukcy"},
{239, "iuml"},
{309, "jcirc"},
{1081, "jcy"},
{120103, "jfr"},
{567, "jmath"},
{120155, "jopf"},
{119999, "jscr"},
{1112, "jsercy"},
{1108, "jukcy"},
{954, "kappa"},
{1008, "kappav"},
{311, "kcedil"},
{1082, "kcy"},
{120104, "kfr"},
{312, "kgreen"},
{1093, "khcy"},
{1116, "kjcy"},
{120156, "kopf"},
{120000, "kscr"},
{8666, "lAarr"},
{8656, "lArr"},
{10523, "lAtail"},
{10510, "lBarr"},
{8806, "lE"},
{10891, "lEg"},
{10594, "lHar"},
{314, "lacute"},
{10676, "laemptyv"},
{8466, "lagran"},
{955, "lambda"},
{9001, "lang"},
{10641, "langd"},
{10216, "langle"},
{10885, "lap"},
{171, "laquo"},
{8592, "larr"},
{8676, "larrb"},
{10527, "larrbfs"},
{10525, "larrfs"},
{8617, "larrhk"},
{8619, "larrlp"},
{10553, "larrpl"},
{10611, "larrsim"},
{8610, "larrtl"},
{10923, "lat"},
{10521, "latail"},
{10925, "late"},
{10508, "lbarr"},
{10098, "lbbrk"},
{123, "lbrace"},
{91, "lbrack"},
{10635, "lbrke"},
{10639, "lbrksld"},
{10637, "lbrkslu"},
{318, "lcaron"},
{316, "lcedil"},
{8968, "lceil"},
{123, "lcub"},
{1083, "lcy"},
{10550, "ldca"},
{8220, "ldquo"},
{8222, "ldquor"},
{10599, "ldrdhar"},
{10571, "ldrushar"},
{8626, "ldsh"},
{8804, "le"},
{8592, "leftarrow"},
{8610, "leftarrowtail"},
{8637, "leftharpoondown"},
{8636, "leftharpoonup"},
{8647, "leftleftarrows"},
{8596, "leftrightarrow"},
{8646, "leftrightarrows"},
{8651, "leftrightharpoons"},
{8621, "leftrightsquigarrow"},
{8907, "leftthreetimes"},
{8922, "leg"},
{8804, "leq"},
{8806, "leqq"},
{10877, "leqslant"},
{10877, "les"},
{10920, "lescc"},
{10879, "lesdot"},
{10881, "lesdoto"},
{10883, "lesdotor"},
{10899, "lesges"},
{10885, "lessapprox"},
{8918, "lessdot"},
{8922, "lesseqgtr"},
{10891, "lesseqqgtr"},
{8822, "lessgtr"},
{8818, "lesssim"},
{10620, "lfisht"},
{8970, "lfloor"},
{120105, "lfr"},
{8822, "lg"},
{10897, "lgE"},
{8637, "lhard"},
{8636, "lharu"},
{10602, "lharul"},
{9604, "lhblk"},
{1113, "ljcy"},
{8810, "ll"},
{8647, "llarr"},
{8990, "llcorner"},
{10603, "llhard"},
{9722, "lltri"},
{320, "lmidot"},
{9136, "lmoust"},
{9136, "lmoustache"},
{8808, "lnE"},
{10889, "lnap"},
{10889, "lnapprox"},
{10887, "lne"},
{10887, "lneq"},
{8808, "lneqq"},
{8934, "lnsim"},
{10220, "loang"},
{8701, "loarr"},
{10214, "lobrk"},
{10229, "longleftarrow"},
{10231, "longleftrightarrow"},
{10236, "longmapsto"},
{10230, "longrightarrow"},
{8619, "looparrowleft"},
{8620, "looparrowright"},
{10629, "lopar"},
{120157, "lopf"},
{10797, "loplus"},
{10804, "lotimes"},
{8727, "lowast"},
{95, "lowbar"},
{9674, "loz"},
{9674, "lozenge"},
{10731, "lozf"},
{40, "lpar"},
{10643, "lparlt"},
{8646, "lrarr"},
{8991, "lrcorner"},
{8651, "lrhar"},
{10605, "lrhard"},
{8206, "lrm"},
{8895, "lrtri"},
{8249, "lsaquo"},
{120001, "lscr"},
{8624, "lsh"},
{8818, "lsim"},
{10893, "lsime"},
{10895, "lsimg"},
{91, "lsqb"},
{8216, "lsquo"},
{8218, "lsquor"},
{322, "lstrok"},
{60, "lt"},
{10918, "ltcc"},
{10873, "ltcir"},
{8918, "ltdot"},
{8907, "lthree"},
{8905, "ltimes"},
{10614, "ltlarr"},
{10875, "ltquest"},
{10646, "ltrPar"},
{9667, "ltri"},
{8884, "ltrie"},
{9666, "ltrif"},
{10570, "lurdshar"},
{10598, "luruhar"},
{8762, "mDDot"},
{175, "macr"},
{9794, "male"},
{10016, "malt"},
{10016, "maltese"},
{8614, "map"},
{8614, "mapsto"},
{8615, "mapstodown"},
{8612, "mapstoleft"},
{8613, "mapstoup"},
{9646, "marker"},
{10793, "mcomma"},
{1084, "mcy"},
{8212, "mdash"},
{8737, "measuredangle"},
{120106, "mfr"},
{8487, "mho"},
{181, "micro"},
{8739, "mid"},
{42, "midast"},
{10992, "midcir"},
{183, "middot"},
{8722, "minus"},
{8863, "minusb"},
{8760, "minusd"},
{10794, "minusdu"},
{10971, "mlcp"},
{8230, "mldr"},
{8723, "mnplus"},
{8871, "models"},
{120158, "mopf"},
{8723, "mp"},
{120002, "mscr"},
{8766, "mstpos"},
{956, "mu"},
{8888, "multimap"},
{8888, "mumap"},
{8653, "nLeftarrow"},
{8654, "nLeftrightarrow"},
{8655, "nRightarrow"},
{8879, "nVDash"},
{8878, "nVdash"},
{8711, "nabla"},
{324, "nacute"},
{8777, "nap"},
{329, "napos"},
{8777, "napprox"},
{9838, "natur"},
{9838, "natural"},
{8469, "naturals"},
{160, "nbsp"},
{10819, "ncap"},
{328, "ncaron"},
{326, "ncedil"},
{8775, "ncong"},
{10818, "ncup"},
{1085, "ncy"},
{8211, "ndash"},
{8800, "ne"},
{8663, "neArr"},
{10532, "nearhk"},
{8599, "nearr"},
{8599, "nearrow"},
{8802, "nequiv"},
{10536, "nesear"},
{8708, "nexist"},
{8708, "nexists"},
{120107, "nfr"},
{8817, "nge"},
{8817, "ngeq"},
{8821, "ngsim"},
{8815, "ngt"},
{8815, "ngtr"},
{8654, "nhArr"},
{8622, "nharr"},
{10994, "nhpar"},
{8715, "ni"},
{8956, "nis"},
{8954, "nisd"},
{8715, "niv"},
{1114, "njcy"},
{8653, "nlArr"},
{8602, "nlarr"},
{8229, "nldr"},
{8816, "nle"},
{8602, "nleftarrow"},
{8622, "nleftrightarrow"},
{8816, "nleq"},
{8814, "nless"},
{8820, "nlsim"},
{8814, "nlt"},
{8938, "nltri"},
{8940, "nltrie"},
{8740, "nmid"},
{120159, "nopf"},
{172, "not"},
{8713, "notin"},
{8713, "notinva"},
{8951, "notinvb"},
{8950, "notinvc"},
{8716, "notni"},
{8716, "notniva"},
{8958, "notnivb"},
{8957, "notnivc"},
{8742, "npar"},
{8742, "nparallel"},
{10772, "npolint"},
{8832, "npr"},
{8928, "nprcue"},
{8832, "nprec"},
{8655, "nrArr"},
{8603, "nrarr"},
{8603, "nrightarrow"},
{8939, "nrtri"},
{8941, "nrtrie"},
{8833, "nsc"},
{8929, "nsccue"},
{120003, "nscr"},
{8740, "nshortmid"},
{8742, "nshortparallel"},
{8769, "nsim"},
{8772, "nsime"},
{8772, "nsimeq"},
{8740, "nsmid"},
{8742, "nspar"},
{8930, "nsqsube"},
{8931, "nsqsupe"},
{8836, "nsub"},
{8840, "nsube"},
{8840, "nsubseteq"},
{8833, "nsucc"},
{8837, "nsup"},
{8841, "nsupe"},
{8841, "nsupseteq"},
{8825, "ntgl"},
{241, "ntilde"},
{8824, "ntlg"},
{8938, "ntriangleleft"},
{8940, "ntrianglelefteq"},
{8939, "ntriangleright"},
{8941, "ntrianglerighteq"},
{957, "nu"},
{35, "num"},
{8470, "numero"},
{8199, "numsp"},
{8877, "nvDash"},
{10500, "nvHarr"},
{8876, "nvdash"},
{10718, "nvinfin"},
{10498, "nvlArr"},
{10499, "nvrArr"},
{8662, "nwArr"},
{10531, "nwarhk"},
{8598, "nwarr"},
{8598, "nwarrow"},
{10535, "nwnear"},
{9416, "oS"},
{243, "oacute"},
{8859, "oast"},
{8858, "ocir"},
{244, "ocirc"},
{1086, "ocy"},
{8861, "odash"},
{337, "odblac"},
{10808, "odiv"},
{8857, "odot"},
{10684, "odsold"},
{339, "oelig"},
{10687, "ofcir"},
{120108, "ofr"},
{731, "ogon"},
{242, "ograve"},
{10689, "ogt"},
{10677, "ohbar"},
{937, "ohm"},
{8750, "oint"},
{8634, "olarr"},
{10686, "olcir"},
{10683, "olcross"},
{8254, "oline"},
{10688, "olt"},
{333, "omacr"},
{969, "omega"},
{959, "omicron"},
{10678, "omid"},
{8854, "ominus"},
{120160, "oopf"},
{10679, "opar"},
{10681, "operp"},
{8853, "oplus"},
{8744, "or"},
{8635, "orarr"},
{10845, "ord"},
{8500, "order"},
{8500, "orderof"},
{170, "ordf"},
{186, "ordm"},
{8886, "origof"},
{10838, "oror"},
{10839, "orslope"},
{10843, "orv"},
{8500, "oscr"},
{248, "oslash"},
{8856, "osol"},
{245, "otilde"},
{8855, "otimes"},
{10806, "otimesas"},
{246, "ouml"},
{9021, "ovbar"},
{8741, "par"},
{182, "para"},
{8741, "parallel"},
{10995, "parsim"},
{11005, "parsl"},
{8706, "part"},
{1087, "pcy"},
{37, "percnt"},
{46, "period"},
{8240, "permil"},
{8869, "perp"},
{8241, "pertenk"},
{120109, "pfr"},
{966, "phi"},
{981, "phiv"},
{8499, "phmmat"},
{9742, "phone"},
{960, "pi"},
{8916, "pitchfork"},
{982, "piv"},
{8463, "planck"},
{8462, "planckh"},
{8463, "plankv"},
{43, "plus"},
{10787, "plusacir"},
{8862, "plusb"},
{10786, "pluscir"},
{8724, "plusdo"},
{10789, "plusdu"},
{10866, "pluse"},
{177, "plusmn"},
{10790, "plussim"},
{10791, "plustwo"},
{177, "pm"},
{10773, "pointint"},
{120161, "popf"},
{163, "pound"},
{8826, "pr"},
{10931, "prE"},
{10935, "prap"},
{8828, "prcue"},
{10927, "pre"},
{8826, "prec"},
{10935, "precapprox"},
{8828, "preccurlyeq"},
{10927, "preceq"},
{10937, "precnapprox"},
{10933, "precneqq"},
{8936, "precnsim"},
{8830, "precsim"},
{8242, "prime"},
{8473, "primes"},
{10933, "prnE"},
{10937, "prnap"},
{8936, "prnsim"},
{8719, "prod"},
{9006, "profalar"},
{8978, "profline"},
{8979, "profsurf"},
{8733, "prop"},
{8733, "propto"},
{8830, "prsim"},
{8880, "prurel"},
{120005, "pscr"},
{968, "psi"},
{8200, "puncsp"},
{120110, "qfr"},
{10764, "qint"},
{120162, "qopf"},
{8279, "qprime"},
{120006, "qscr"},
{8461, "quaternions"},
{10774, "quatint"},
{63, "quest"},
{8799, "questeq"},
{34, "quot"},
{8667, "rAarr"},
{8658, "rArr"},
{10524, "rAtail"},
{10511, "rBarr"},
{10596, "rHar"},
{341, "racute"},
{8730, "radic"},
{10675, "raemptyv"},
{9002, "rang"},
{10642, "rangd"},
{10661, "range"},
{10217, "rangle"},
{187, "raquo"},
{8594, "rarr"},
{10613, "rarrap"},
{8677, "rarrb"},
{10528, "rarrbfs"},
{10547, "rarrc"},
{10526, "rarrfs"},
{8618, "rarrhk"},
{8620, "rarrlp"},
{10565, "rarrpl"},
{10612, "rarrsim"},
{8611, "rarrtl"},
{8605, "rarrw"},
{10522, "ratail"},
{8758, "ratio"},
{8474, "rationals"},
{10509, "rbarr"},
{10099, "rbbrk"},
{125, "rbrace"},
{93, "rbrack"},
{10636, "rbrke"},
{10638, "rbrksld"},
{10640, "rbrkslu"},
{345, "rcaron"},
{343, "rcedil"},
{8969, "rceil"},
{125, "rcub"},
{1088, "rcy"},
{10551, "rdca"},
{10601, "rdldhar"},
{8221, "rdquo"},
{8221, "rdquor"},
{8627, "rdsh"},
{8476, "real"},
{8475, "realine"},
{8476, "realpart"},
{8477, "reals"},
{9645, "rect"},
{174, "reg"},
{10621, "rfisht"},
{8971, "rfloor"},
{120111, "rfr"},
{8641, "rhard"},
{8640, "rharu"},
{10604, "rharul"},
{961, "rho"},
{1009, "rhov"},
{8594, "rightarrow"},
{8611, "rightarrowtail"},
{8641, "rightharpoondown"},
{8640, "rightharpoonup"},
{8644, "rightleftarrows"},
{8652, "rightleftharpoons"},
{8649, "rightrightarrows"},
{8605, "rightsquigarrow"},
{8908, "rightthreetimes"},
{730, "ring"},
{8787, "risingdotseq"},
{8644, "rlarr"},
{8652, "rlhar"},
{8207, "rlm"},
{9137, "rmoust"},
{9137, "rmoustache"},
{10990, "rnmid"},
{10221, "roang"},
{8702, "roarr"},
{10215, "robrk"},
{10630, "ropar"},
{120163, "ropf"},
{10798, "roplus"},
{10805, "rotimes"},
{41, "rpar"},
{10644, "rpargt"},
{10770, "rppolint"},
{8649, "rrarr"},
{8250, "rsaquo"},
{120007, "rscr"},
{8625, "rsh"},
{93, "rsqb"},
{8217, "rsquo"},
{8217, "rsquor"},
{8908, "rthree"},
{8906, "rtimes"},
{9657, "rtri"},
{8885, "rtrie"},
{9656, "rtrif"},
{10702, "rtriltri"},
{10600, "ruluhar"},
{8478, "rx"},
{347, "sacute"},
{8218, "sbquo"},
{8827, "sc"},
{10932, "scE"},
{10936, "scap"},
{353, "scaron"},
{8829, "sccue"},
{10928, "sce"},
{351, "scedil"},
{349, "scirc"},
{10934, "scnE"},
{10938, "scnap"},
{8937, "scnsim"},
{10771, "scpolint"},
{8831, "scsim"},
{1089, "scy"},
{8901, "sdot"},
{8865, "sdotb"},
{10854, "sdote"},
{8664, "seArr"},
{10533, "searhk"},
{8600, "searr"},
{8600, "searrow"},
{167, "sect"},
{59, "semi"},
{10537, "seswar"},
{8726, "setminus"},
{8726, "setmn"},
{10038, "sext"},
{120112, "sfr"},
{8994, "sfrown"},
{9839, "sharp"},
{1097, "shchcy"},
{1096, "shcy"},
{8739, "shortmid"},
{8741, "shortparallel"},
{173, "shy"},
{963, "sigma"},
{962, "sigmaf"},
{962, "sigmav"},
{8764, "sim"},
{10858, "simdot"},
{8771, "sime"},
{8771, "simeq"},
{10910, "simg"},
{10912, "simgE"},
{10909, "siml"},
{10911, "simlE"},
{8774, "simne"},
{10788, "simplus"},
{10610, "simrarr"},
{8592, "slarr"},
{8726, "smallsetminus"},
{10803, "smashp"},
{10724, "smeparsl"},
{8739, "smid"},
{8995, "smile"},
{10922, "smt"},
{10924, "smte"},
{1100, "softcy"},
{47, "sol"},
{10692, "solb"},
{9023, "solbar"},
{120164, "sopf"},
{9824, "spades"},
{9824, "spadesuit"},
{8741, "spar"},
{8851, "sqcap"},
{8852, "sqcup"},
{8847, "sqsub"},
{8849, "sqsube"},
{8847, "sqsubset"},
{8849, "sqsubseteq"},
{8848, "sqsup"},
{8850, "sqsupe"},
{8848, "sqsupset"},
{8850, "sqsupseteq"},
{9633, "squ"},
{9633, "square"},
{9642, "squarf"},
{9642, "squf"},
{8594, "srarr"},
{120008, "sscr"},
{8726, "ssetmn"},
{8995, "ssmile"},
{8902, "sstarf"},
{9734, "star"},
{9733, "starf"},
{1013, "straightepsilon"},
{981, "straightphi"},
{175, "strns"},
{8834, "sub"},
{10949, "subE"},
{10941, "subdot"},
{8838, "sube"},
{10947, "subedot"},
{10945, "submult"},
{10955, "subnE"},
{8842, "subne"},
{10943, "subplus"},
{10617, "subrarr"},
{8834, "subset"},
{8838, "subseteq"},
{10949, "subseteqq"},
{8842, "subsetneq"},
{10955, "subsetneqq"},
{10951, "subsim"},
{10965, "subsub"},
{10963, "subsup"},
{8827, "succ"},
{10936, "succapprox"},
{8829, "succcurlyeq"},
{10928, "succeq"},
{10938, "succnapprox"},
{10934, "succneqq"},
{8937, "succnsim"},
{8831, "succsim"},
{8721, "sum"},
{9834, "sung"},
{8835, "sup"},
{185, "sup1"},
{178, "sup2"},
{179, "sup3"},
{10950, "supE"},
{10942, "supdot"},
{10968, "supdsub"},
{8839, "supe"},
{10948, "supedot"},
{10185, "suphsol"},
{10967, "suphsub"},
{10619, "suplarr"},
{10946, "supmult"},
{10956, "supnE"},
{8843, "supne"},
{10944, "supplus"},
{8835, "supset"},
{8839, "supseteq"},
{10950, "supseteqq"},
{8843, "supsetneq"},
{10956, "supsetneqq"},
{10952, "supsim"},
{10964, "supsub"},
{10966, "supsup"},
{8665, "swArr"},
{10534, "swarhk"},
{8601, "swarr"},
{8601, "swarrow"},
{10538, "swnwar"},
{223, "szlig"},
{8982, "target"},
{964, "tau"},
{9140, "tbrk"},
{357, "tcaron"},
{355, "tcedil"},
{1090, "tcy"},
{8411, "tdot"},
{8981, "telrec"},
{120113, "tfr"},
{8756, "there4"},
{8756, "therefore"},
{952, "theta"},
{977, "thetasym"},
{977, "thetav"},
{8776, "thickapprox"},
{8764, "thicksim"},
{8201, "thinsp"},
{8776, "thkap"},
{8764, "thksim"},
{254, "thorn"},
{732, "tilde"},
{215, "times"},
{8864, "timesb"},
{10801, "timesbar"},
{10800, "timesd"},
{8749, "tint"},
{10536, "toea"},
{8868, "top"},
{9014, "topbot"},
{10993, "topcir"},
{120165, "topf"},
{10970, "topfork"},
{10537, "tosa"},
{8244, "tprime"},
{8482, "trade"},
{9653, "triangle"},
{9663, "triangledown"},
{9667, "triangleleft"},
{8884, "trianglelefteq"},
{8796, "triangleq"},
{9657, "triangleright"},
{8885, "trianglerighteq"},
{9708, "tridot"},
{8796, "trie"},
{10810, "triminus"},
{10809, "triplus"},
{10701, "trisb"},
{10811, "tritime"},
{9186, "trpezium"},
{120009, "tscr"},
{1094, "tscy"},
{1115, "tshcy"},
{359, "tstrok"},
{8812, "twixt"},
{8606, "twoheadleftarrow"},
{8608, "twoheadrightarrow"},
{8657, "uArr"},
{10595, "uHar"},
{250, "uacute"},
{8593, "uarr"},
{1118, "ubrcy"},
{365, "ubreve"},
{251, "ucirc"},
{1091, "ucy"},
{8645, "udarr"},
{369, "udblac"},
{10606, "udhar"},
{10622, "ufisht"},
{120114, "ufr"},
{249, "ugrave"},
{8639, "uharl"},
{8638, "uharr"},
{9600, "uhblk"},
{8988, "ulcorn"},
{8988, "ulcorner"},
{8975, "ulcrop"},
{9720, "ultri"},
{363, "umacr"},
{168, "uml"},
{371, "uogon"},
{120166, "uopf"},
{8593, "uparrow"},
{8597, "updownarrow"},
{8639, "upharpoonleft"},
{8638, "upharpoonright"},
{8846, "uplus"},
{965, "upsi"},
{978, "upsih"},
{965, "upsilon"},
{8648, "upuparrows"},
{8989, "urcorn"},
{8989, "urcorner"},
{8974, "urcrop"},
{367, "uring"},
{9721, "urtri"},
{120010, "uscr"},
{8944, "utdot"},
{361, "utilde"},
{9653, "utri"},
{9652, "utrif"},
{8648, "uuarr"},
{252, "uuml"},
{10663, "uwangle"},
{8661, "vArr"},
{10984, "vBar"},
{10985, "vBarv"},
{8872, "vDash"},
{10652, "vangrt"},
{1013, "varepsilon"},
{1008, "varkappa"},
{8709, "varnothing"},
{981, "varphi"},
{982, "varpi"},
{8733, "varpropto"},
{8597, "varr"},
{1009, "varrho"},
{962, "varsigma"},
{977, "vartheta"},
{8882, "vartriangleleft"},
{8883, "vartriangleright"},
{1074, "vcy"},
{8866, "vdash"},
{8744, "vee"},
{8891, "veebar"},
{8794, "veeeq"},
{8942, "vellip"},
{120115, "vfr"},
{8882, "vltri"},
{120167, "vopf"},
{8733, "vprop"},
{8883, "vrtri"},
{120011, "vscr"},
{10650, "vzigzag"},
{373, "wcirc"},
{10847, "wedbar"},
{8743, "wedge"},
{8793, "wedgeq"},
{8472, "weierp"},
{120116, "wfr"},
{120168, "wopf"},
{8472, "wp"},
{8768, "wr"},
{8768, "wreath"},
{120012, "wscr"},
{8898, "xcap"},
{9711, "xcirc"},
{8899, "xcup"},
{9661, "xdtri"},
{120117, "xfr"},
{10234, "xhArr"},
{10231, "xharr"},
{958, "xi"},
{10232, "xlArr"},
{10229, "xlarr"},
{10236, "xmap"},
{8955, "xnis"},
{10752, "xodot"},
{120169, "xopf"},
{10753, "xoplus"},
{10754, "xotime"},
{10233, "xrArr"},
{10230, "xrarr"},
{120013, "xscr"},
{10758, "xsqcup"},
{10756, "xuplus"},
{9651, "xutri"},
{8897, "xvee"},
{8896, "xwedge"},
{253, "yacute"},
{1103, "yacy"},
{375, "ycirc"},
{1099, "ycy"},
{165, "yen"},
{120118, "yfr"},
{1111, "yicy"},
{120170, "yopf"},
{120014, "yscr"},
{1102, "yucy"},
{255, "yuml"},
{378, "zacute"},
{382, "zcaron"},
{1079, "zcy"},
{380, "zdot"},
{8488, "zeetrf"},
{950, "zeta"},
{120119, "zfr"},
{1078, "zhcy"},
{8669, "zigrarr"},
{120171, "zopf"},
{120015, "zscr"},
{8205, "zwj"},
{8204, "zwnj"},
{0, 0},
};

static unsigned andLookup(char *entity, char *v)
{
	int i, l, r, d;
	int n = v - entity;
// left, right, binary search
	l = -1;
	r = sizeof(andlist) / sizeof(struct entity) - 1;
	while(r - l > 1) {
		i = (l + r) / 2;
		d = strncmp(entity, andlist[i].word, n);
		if(!d && andlist[i].word[n]) d = -1;
		if(!d) return andlist[i].u;
		if(d > 0) l = i; else r = i;
	}
	return 0; // not found
}

// Here is a general routine to traverse the tree, with a callback function.
static void traverseNode(Tag *t, struct parseContext *pc)
{
    const nodeFunction f = pc->callback;
    Tag *child, *next_child = NULL, *u;

    if (t->visited) {
        pc->malformed = true;
        debugPrint(4, "node revisit %s %d", t->info->name, t->seqno);
        return;
    }
    t->visited = true;

// open tag <foo>
    (*f) (t, true, pc);

    for (child = t->firstchild; child; child = child->sibling) {
/*********************************************************************
This will take some splaining. Suppose the html looks like:
paragraph1 script paragraph3, and script is going to append paragraph2.
When the script runs, the entire tree is built internally, the tree we are
traversing right now. Not the tree of objects, that hasn't been built yet,
but our tree of pointers in C is built. Remember that
we need our own tree within C, because all this has to run without javascript,
so we have a tree here in C and a tree of js objects,
and they absolutely have to be in sync at all times. Now the script runs,
and puts paragraph2 in place properly in the tree of objects,
because paragraph3 hasn't been placed yet,
but it puts paragraph2 after paragraph3 in the C world.
That's more than just wrong, it's inconsistent!
So we have to pretend like this is the end of the tree, like we haven't
gone any farther, like we haven't yet placed paragraph3.
Cut off the rest of the nodes, then put them back
after the script has run. It's a wild ride.

<body><p>first paragraph
<script>
var p = document.createElement("p");
p.appendChild(document.createTextNode("second paragraph"));
document.body.appendChild(p);
</script>
<p>third paragraph
</body>

We do this pointer magic only in the decorate phase,
e.g. no need to do this when rendering the page for display.
The liftup variable determines that.
*********************************************************************/

top:
        if(pc->liftup) {
            next_child = child->sibling, child->sibling = 0;
            if(next_child)
                debugPrint(5, "%s lifts up %s", child->info->name, next_child ? next_child->info->name : "empty");
        }
        traverseNode(child, pc);
        if(pc->abort) return;
// If we aren't decorating, or if we are in xml mode,
// then we can just move on. Business as usual.
        if(!pc->liftup) continue;
        if(!child->parent)
            debugPrint(3, "stepping around missing script");
// If there were no more nodes at this level, and if more have been added,
// We don't have to decorate those; they already have js objects.
// Even if the script removes itself, or prior nodes, and there is no
// next_child, then we are done.
        if(!next_child) break;
        debugPrint(5, "after sibling %s %s above",
        child->sibling ? child->sibling->info->name : "empty",
        next_child->info->name);
// This line is if the script removed itself and that was the only node
        if(!t->firstchild) {
            t->firstchild = next_child;
            child = next_child;
            goto top;
        }
// the high runner case first - the tag is still there.
// But if the script removed itself, we have to start at the beginning.
        if(child->parent)
            for(u = child; u->sibling; u = u->sibling)  ;
        else
            for(u = t->firstchild; u->sibling; u = u->sibling)  ;
        u->sibling = next_child;
// skip ahead to the next undecorated node
        child = u;
    }

// close tag </foo>
    (*f) (t, false, pc);
}

void traverseAll(int start, struct parseContext *pc)
{
	Tag *t, *u;
	int i, numtags;
	bool action = false;
	pc->malformed = false;
	pc->abort = false;
	numtags = cw->numTags;
	for (i = start; i < numtags; ++i)
		tagList[i]->visited = false;
	for (i = start; i < numtags; ++i) {
		t = tagList[i];
// for innerHTML, first couple of tags, html and body, are dead, as they are skipped.
		if(t->dead) continue;
// we have to start with a node that has no parent, or parent is outside this span
		if((u = t->parent) && u->seqno >= start && u->seqno < numtags) continue;
		if(t->visited) {
			if(!u)
				debugPrint(3,
				"%s %d already visited, perhaps removed",
				t->info->name, t->seqno);
			else
				debugPrint(3,
				"%s %d already visited, perhaps moved to %s %d",
				t->info->name, t->seqno, u->info->name, u->seqno);
			continue;
		}
		debugPrint(4, "traverse starts at %s %d", t->info->name, t->seqno);
		traverseNode(t, pc);
		action = true;
	}
	if(!action)
		debugPrint(4, "traverseAll finds no live tags to traverse");
	if(cw->numTags > numtags)
		debugPrint(3, "traverseAll added %d nodes", cw->numTags - numtags);
	if (pc->malformed)
		debugPrint(3, "malformed tree!");

/*********************************************************************
Suppose this is generated html, and it is parsed, and we leave all its
visited flags set. Then above, some of these nodes are moved into the tree
that is being created, moved into that tree by a higher script.
When decorate runs into this node, as a child in the higher tree,
it sees visited = true, and doesn't proceed.
We want it to proceed, even though it won't do anything,
even though these nodes are already decorated with js objects,
even though these nodes are already at step 2,
we want it to proceed so we don't get error messages about a malformed tree.
*********************************************************************/
	for (i = start; i < numtags; ++i)
		tagList[i]->visited = false;
}

Tag *findOpenTag(Tag *t, int action)
{
	int count = 0;
	while ((t = t->parent)) {
		if (t->action == action)
			return t;
		if (++count == 10000) {	// tree shouldn't be this deep
			debugPrint(1, "infinite loop in findOpenTag()");
			break;
		}
	}
	return 0;
}

Tag *findOpenSection(Tag *t)
{
	int count = 0;
	while ((t = t->parent)) {
		if (t->action == TAGACT_TBODY || t->action == TAGACT_THEAD ||
		    t->action == TAGACT_TFOOT)
			return t;
		if (++count == 10000) {	// tree shouldn't be this deep
			debugPrint(1, "infinite loop in findOpenTag()");
			break;
		}
	}
	return 0;
}

Tag *findOpenList(Tag *t)
{
	while ((t = t->parent))
		if (t->action == TAGACT_OL || t->action == TAGACT_UL)
			return t;
	return 0;
}

bool controlledBy(const Tag *t, const Tag *above)
{
    for(t = t->parent; t; t = t->parent)
        if(t == above) return true;
    return false;
}

/*********************************************************************
Tables are suppose to have bodies, I guess.
So <table><tr> becomes <table><tbody><tr>
Find each table and look at its children.
Note the tags between sections, where section is tHead, tBody, or tFoot.
If that span includes <tr>, then put those tags under a new tBody.
*********************************************************************/

static void insert_tbody1(Tag *s1, Tag *s2, Tag *tbl);
static bool tagBelow(Tag *t, int action);

static void insert_tbody(void)
{
	int i, end = cw->numTags;
	Tag *tbl, *s1, *s2;

	for (i = 0; i < end; ++i) {
		tbl = tagList[i];
		if (tbl->action != TAGACT_TABLE) continue;
		s1 = 0;
		do {
			s2 = (s1 ? s1->sibling : tbl->firstchild);
			while (s2 && s2->action != TAGACT_TBODY
			       && s2->action != TAGACT_THEAD
			       && s2->action != TAGACT_TFOOT)
				s2 = s2->sibling;
			insert_tbody1(s1, s2, tbl);
			s1 = s2;
		} while (s1);
	}
}

static void insert_tbody1(Tag *s1, Tag *s2, Tag *tbl)
{
	Tag *s1a = (s1 ? s1->sibling : tbl->firstchild);
	Tag *u, *uprev, *ns;	// new section

	if (s1a == s2)		// nothing between
		return;

// Look for the direct html <table><tr><th>.
// If th is anywhere else down the path, we won't find it.
	if (!s1 && s1a->action == TAGACT_TR &&
	    (u = s1a->firstchild) && stringEqual(u->info->name, "th") &&
	    (u = u->sibling) && stringEqual(u->info->name, "th")) {
		ns = newTag(cf, "thead");
		tbl->firstchild = ns;
		ns->parent = tbl;
		ns->firstchild = s1a;
		s1a->parent = ns;
		ns->sibling = s1a->sibling;
		s1a->sibling = 0;
		s1 = ns;
		s1a = s1->sibling;
	}

	for (u = s1a; u != s2; u = u->sibling)
		if (tagBelow(u, TAGACT_TR))
			break;
	if (u == s2)		// no rows below
		return;

	ns = newTag(cf, "tbody");
	for (u = s1a; u != s2; u = u->sibling)
		uprev = u, u->parent = ns;
	if (s1)
		s1->sibling = ns;
	else
		tbl->firstchild = ns;
	if (s2)
		uprev->sibling = 0, ns->sibling = s2;
	ns->firstchild = s1a;
	ns->parent = tbl;
}

static bool tagBelow(Tag *t, int action)
{
	Tag *c;

	if (t->action == action)
		return true;
	for (c = t->firstchild; c; c = c->sibling)
		if (tagBelow(c, action))
			return true;
	return false;
}

// callback functions in this file
static void prerenderNode(Tag *node, bool opentag, struct parseContext *pc);
static void jsNode(Tag *node, bool opentag, struct parseContext *pc);
static void pushAttributes(const Tag *t);
static int nopt;		/* number of options */
// None of these tags nest, so it is reasonable to talk about
// the current open tag.
static Tag *currentForm, *currentSel, *currentOpt;
static const char *optg; // option group
static Tag *currentTitle, *currentTA, *currentStyle;
static Tag *currentA, *currentAudio, *currentScript;
static char *radioCheck;
static int radio_l;

static void linkinTree(Tag *parent, Tag *child)
{
	Tag *c, *d;
	child->parent = parent;
	if (!parent->firstchild) {
		parent->firstchild = child;
	} else {
		for (c = parent->firstchild; c; c = c->sibling) d = c;
		d->sibling = child;
	}
}

static void makeButton(void)
{
	Tag *t = newTag(cf, "input");
	t->controller = currentForm;
	t->itype = INP_SUBMIT;
	t->value = emptyString;
	t->step = 1;
	linkinTree(currentForm, t);
}

void formControl(Tag *t, bool namecheck)
{
	char *myname = (t->name ? t->name : t->id);
	Tag *cform = currentForm;
	if (!cform) {
/* nodes could be created dynamically, not through html */
		cform = findOpenTag(t, TAGACT_FORM);
	}
	if (cform)
		t->controller = cform;
	if (namecheck && !myname)
		debugPrint(4, "%s does not have a name", t->info->desc);
}

const char *const inp_types[] = {
	"none",
	"reset", "button", "image", "submit",
	"hidden", "text", "file",
	"select", "textarea", "radio", "checkbox",
	0
};

/*********************************************************************
Here are some other input types that should have additional syntax checks
performed on them, but as far as this version of edbrowse is concerned,
they are equivalent to text. Just here to suppress warnings.
List taken from https://www.tutorialspoint.com/html/html_input_tag.htm
*********************************************************************/

const char *const inp_others[] = {
	"no_minor", "date", "datetime", "datetime-local",
	"month", "week", "time", "email", "range",
	"search", "tel", "url", "number", "password",
	0
};

/* helper function for input tag */
void htmlInputHelper(Tag *t)
{
	int n, len, itype;
	char *myname = (t->name ? t->name : t->id);
	const char *s = attribVal(t, "type");
	bool isbutton = stringEqual(t->info->name, "button");
	t->itype = itype = (isbutton ? INP_SUBMIT : INP_TEXT);
	if (s && *s) {
		n = stringInListCI(inp_types, s);
		if (n < 0) {
			n = stringInListCI(inp_others, s);
			if (n < 0)
				debugPrint(3, "unrecognized input type %s", s);
			else
				t->itype = itype = INP_TEXT, t->itype_minor = n;
			if (n == INP_PW)
				t->ipass = true;
		} else
			t->itype = itype = n;
	}

	s = attribVal(t, "maxlength");
	len = 0;
	if (s)
		len = stringIsNum(s);
	if (len > 0)
		t->lic = len;

// No preset value on file, for security reasons.
// <input type=file value=/etc/passwd> then submit via onload().
	if (itype == INP_FILE) {
		nzFree0(t->value);
		cnzFree0(t->rvalue);
	}

/* In this case an empty value should be "", not null */
	if (t->value == 0)
		t->value = emptyString;
	if (t->rvalue == 0)
		t->rvalue = cloneString(t->value);

	if (itype == INP_RADIO && t->checked && radioCheck && myname) {
		char namebuf[200];
		if (strlen(myname) < sizeof(namebuf) - 3) {
			if (!*radioCheck)
				stringAndChar(&radioCheck, &radio_l, '|');
			sprintf(namebuf, "|%s|", t->name);
			if (strstr(radioCheck, namebuf)) {
				debugPrint(3,
					   "multiple radio buttons have been selected");
				return;
			}
			stringAndString(&radioCheck, &radio_l, namebuf + 1);
		}
	}

	// the submit fields can have a name, but they don't have to
	formControl(t, (itype > INP_SUBMIT));
}

static void fillEmptySelect(Tag *sel)
{
	Tag *t;
	const char *z;
	int zv;
	if(sel->multiple | sel->disabled) return;
	if(sel->lic) return;
// setting size to something > 1 disables this behavior.
	if((z = attribVal(sel, "size")) && (zv = stringIsNum(z)) && zv > 1)
		return;
	for (t = cw->optlist; t; t = t->same) {
		if (t->controller != sel) continue;
		if(t->disabled ||
		(t->parent && t->parent->action == TAGACT_OPTG && t->parent->disabled))
			continue;
// this is the first option
		break;
	}
	if(!t) return; // no options possible
	t->checked = t->rchecked = true;
	sel->lic = 1; // now 1 item selected
}

static void prerenderNode(Tag *t, bool opentag, struct parseContext *pc)
{
	int itype;		/* input type */
	int j;
	int action = t->action;
	const char *a;		/* usually an attribute */

        (void) pc;
	if (t->step >= 1)
		return;
	debugPrint(6, "prend %c%s %d",
		   (opentag ? ' ' : '/'), t->info->name,
		   t->seqno);
	if (!opentag)
		t->step = 1;

	switch (action) {
		char *v;

	case TAGACT_NOSCRIPT:
// If javascript is enabled kill everything under noscript
		if (isJSAlive && !opentag)
			underKill(t);
		break;

	case TAGACT_TEXT:
		if (!opentag || !t->textval)
			break;

		if (currentScript) {
			t->deleted = true;
			break;
		}

		if (currentTitle) {
			if (!cw->htmltitle && t->textval) {
				cw->htmltitle = cloneString(t->textval);
				spaceCrunch(cw->htmltitle, true, false);
			}
			t->deleted = true;
			break;
		}

		if (currentOpt) {
			currentOpt->textval = cloneString(t->textval);
			spaceCrunch(currentOpt->textval, true, false);
			t->deleted = true;
			break;
		}

		if (currentStyle) {
			t->deleted = true;
			break;
		}

		if (currentTA) {
			currentTA->value = t->textval;
			leftClipString(currentTA->value);
			currentTA->rvalue = cloneString(currentTA->value);
			t->textval = 0;
			t->deleted = true;
			break;
		}

/* text is on the page */
		if (currentA) {
			char *s;
			for (s = t->textval; *s; ++s)
				if (isalnumByte(*s)) {
					currentA->textin = true;
					break;
				}
		}
		break;

	case TAGACT_TITLE:
		if((t->controller = findOpenTag(t, TAGACT_SVG))) {
// this is title for svg, not for the document.
// Just turn things into span.
			t->action = t->controller->action = TAGACT_SPAN;
			break;
		}
		currentTitle = (opentag ? t : 0);
		break;

	case TAGACT_SCRIPT:
		currentScript = (opentag ? t : 0);
		break;

	case TAGACT_A:
		currentA = (opentag ? t : 0);
		break;

	case TAGACT_FORM:
		if (opentag) {
			currentForm = t;
			a = attribVal(t, "method");
			if (a) {
				if (stringEqualCI(a, "post"))
					t->post = true;
				else if (!stringEqualCI(a, "get"))
					debugPrint(3,
						   "form method should be get or post");
			}
			a = attribVal(t, "enctype");
			if (a) {
				if (stringEqualCI(a, "multipart/form-data"))
					t->mime = true;
				else if (stringEqualCI(a, "text/plain"))
					t->plain = true;
				else if (!stringEqualCI(a, 
					  "application/x-www-form-urlencoded"))
					debugPrint(3,
						   "unrecognized enctype, plese use multipart/form-data or application/x-www-form-urlencoded or text/plain");
			}
			if ((a = t->href)) {
				const char *prot = getProtURL(a);
				if (prot) {
					if (stringEqualCI(prot, "mailto"))
						t->bymail = true;
					else if (stringEqualCI
						 (prot, "javascript"))
						t->javapost = true;
					else if (stringEqualCI(prot, "https") || stringEqualCI(prot, "gophers"))
						t->secure = true;
					else if (!stringEqualCI(prot, "http") &&
						 !stringEqualCI(prot, "gopher"))
						debugPrint(3,
							   "form cannot submit using protocol %s",
							   prot);
				}
			}

			nzFree(radioCheck);
			radioCheck = initString(&radio_l);
		}
		if (!opentag && currentForm) {
			if (t->ninp && !t->submitted) {
				makeButton();
				t->submitted = true;
			}
			currentForm = 0;
		}
		break;

	case TAGACT_INPUT:
		if (!opentag)
			break;
		htmlInputHelper(t);
		itype = t->itype;
		if (itype == INP_HIDDEN)
			break;
		if (currentForm) {
			++currentForm->ninp;
			if (itype == INP_SUBMIT || itype == INP_IMAGE || itype == INP_RESET || itype == INP_BUTTON)
				currentForm->submitted = true;
			if (itype > INP_HIDDEN && itype <= INP_SELECT
			    && t->onchange)
				currentForm->submitted = true;
		}
		break;

	case TAGACT_OPTG:
		if(opentag)
			optg = attribVal(t, "label");
		else
			optg = 0;
		break;

	case TAGACT_OPTION:
		if (!opentag) {
			currentOpt = 0;
			optg = 0;
// in datalist, the text becomes the value
			if(currentSel && currentSel->action == TAGACT_DATAL) {
				if(!t->value) t->value = emptyString;
				nzFree(t->textval);
				t->textval = cloneString(t->value);
			} else {
// otherwise the converse, empty value is replaced with the text
//				printf("val<%s>text<%s>\n", t->value, t->textval);
				if(!t->value)
					t->value = cloneString(t->textval);
			}
			a = attribVal(t, "label");
			if(a && *a) {
				nzFree(t->textval);
				t->textval = cloneString(a);
			}
			break;
		}
// this is open tag
		currentOpt = t;
		t->controller = currentSel;
		t->lic = 0;
		t->textval = emptyString;
		if (!currentSel) {
			debugPrint(3, "option appears outside a select statement");
			optg = 0;
			break;
		}
		t->lic = nopt++;
		if (attribPresent(t, "selected")) {
			if (currentSel->lic && !currentSel->multiple)
				debugPrint(3, "multiple options are selected");
			else {
				t->checked = t->rchecked = true;
				++currentSel->lic;
			}
		}
		if(optg && *optg) {
// borrow custom_h, opt group is like a custom header
			t->custom_h = cloneString(optg);
			optg = 0;
		}
		break;

	case TAGACT_STYLE:
		currentStyle = opentag ? t : 0;
		break;

	case TAGACT_SELECT:
	case TAGACT_DATAL:
		optg = 0;
		if (opentag) {
			currentSel = t;
			nopt = 0;
			t->itype = INP_SELECT;
			if(action == TAGACT_SELECT)
				formControl(t, true);
		} else {
			currentSel = 0;
			if(action == TAGACT_SELECT) {
// If a single select, and nothing selected, the browser
// selects the first viable option for you.
				fillEmptySelect(t);
// from here on it is called an input tag, to join the other input tags
				t->action = TAGACT_INPUT;
				t->value = displayOptions(t);
			}
		}
		break;

	case TAGACT_TA:
		if (opentag) {
			currentTA = t;
			t->itype = INP_TA;
			formControl(t, true);
		} else {
			t->action = TAGACT_INPUT;
			if (!t->value) {
/* This can only happen it no text inside, <textarea></textarea> */
/* like the other value fields, it can't be null */
				t->rvalue = t->value = emptyString;
			}
			currentTA = 0;
		}
		break;

	case TAGACT_META:
		if (opentag) {
/*********************************************************************
htmlMetaHelper is not called for document.createElement("meta")
I don't know if that matters.
Another problem which is extant in the result of a google search:
there is a meta tag with a refresh inside a noscript block.
The refresh shouldn't run at all, but it does,
and the next page has the same tags, and so on forever.
So don't do this if inside <noscript>, and js is active.
Are there other situations where we need to supress meta processing?
*********************************************************************/
			if(!(findOpenTag(t, TAGACT_NOSCRIPT) && isJSAlive))
				htmlMetaHelper(t);
		}
		break;

	case TAGACT_TBODY:
	case TAGACT_THEAD:
	case TAGACT_TFOOT:
		if (opentag)
			t->controller = findOpenTag(t, TAGACT_TABLE);
		break;

	case TAGACT_TR:
		if (opentag) {
			t->controller = findOpenSection(t);
			if (!t->controller)
				t->controller = findOpenTag(t, TAGACT_TABLE);
		}
		break;

	case TAGACT_TD:
		if (opentag)
			t->controller = findOpenTag(t, TAGACT_TR);
		break;

	case TAGACT_SPAN:
		if (!opentag)
			break;
		if (!(a = t->jclass))
			break;
		if (stringEqualCI(a, "sup"))
			action = TAGACT_SUP;
		if (stringEqualCI(a, "sub"))
			action = TAGACT_SUB;
		if (stringEqualCI(a, "ovb"))
			action = TAGACT_OVB;
		t->action = action;
		break;

	case TAGACT_OL:
/* look for start parameter for numbered list */
		if (opentag) {
			a = attribVal(t, "start");
			if (a && (j = stringIsNum(a)) >= 0)
				t->slic = j - 1;
		}
		break;

	case TAGACT_FRAME:
		if (opentag)
			break;
// If somebody wrote <frame><p>foo</frame>, those tags should be excised.
		underKill(t);
// strange situation: html page has no indication of javascript,
// but subframe runs javascript and tries to access javascript objects
// from the top frame. So this could be a doorway into js.
		t->doorway = true;
		break;

	case TAGACT_DET:
		t->contracted = true;
		a = attribVal(t, "open");
		if(a && !stringEqual(a, "false")) t->contracted = false;
		break;

	case TAGACT_MUSIC:
		if (opentag) {
			currentAudio = t;
			break;
		}
// If somebody wrote <audio><p>foo</audio>, those tags should be excised.
// However <source> tags should be kept and/or expanded. Not yet implemented.
		underKill(t);
		currentAudio = 0;
		break;

	case TAGACT_SOURCE:
		if(!currentAudio || !opentag) break;
		a = attribVal(t, "src");
		if(!a || !*a) break;
		if(attribVal(currentAudio, "src")) break; // first source wins
		v = resolveURL(cf->hbase, a);
		nzFree(currentAudio->href);
		currentAudio->href = v;
		setTagAttr(currentAudio, "src", cloneString(v));
		break;

	}			// switch
}

void prerender(int start)
{
	struct parseContext pc;

// make sure table has tbody
	insert_tbody();

	currentForm = currentSel = currentOpt = NULL;
	currentTitle = currentScript = currentTA = NULL;
currentAudio = NULL;
	currentStyle = NULL;
	optg = NULL;
	nzFree0(radioCheck);
	pc.liftup = false;
	pc.callback = prerenderNode;

	debugPrint(4, "prerender starts at %d", start);
	traverseAll(start, &pc);
	currentForm = NULL;
	nzFree0(radioCheck);
	debugPrint(4, "prerender complete");
}

static void establish_inner(Tag *t, const char *start, const char *end,
			    bool isText)
{
	const char *s = emptyString;
	const char *name = (isText ? "value" : "innerHTML");
	if (start) {
		s = start;
		if (end)
			s = pullString(start, end - start);
	}
	set_property_string_t(t, name, s);
	if (start && end)
		nzFree((char *)s);
// If this is a textarea, we haven't yet set up the innerHTML
// getter and seter
	if (isText)
		set_property_string_t(t, "innerHTML", emptyString);
}

static const char defvl[] = "defaultValue";
static const char defck[] = "defaultChecked";
static const char defsel[] = "defaultSelected";

static void formControlJS(Tag *t)
{
	int itype = t->itype;
	int isradio = (itype == INP_RADIO);
	bool isselect = (itype == INP_SELECT);
	bool ista = (itype == INP_TA);
/* this doesn't work because I convert button with no type to submit,
 * in htmlInputHelper(), I don't even know why I do that.
	bool isbutton = (itype == INP_BUTTON);
 */
	bool isbutton = stringEqual(t->info->name, "button");
	const char *whichclass = (isselect ? "HTMLSelectElement" : (ista ? "HTMLTextAreaElement" : isbutton ? "HTMLButtonElement" : "HTMLInputElement"));
	const Tag *form = t->controller;

	if (form && form->jslink)
		domLink(t, whichclass, 0, form, isradio);
	else
		domLink(t, whichclass, 0, 0, (4|isradio));
	if (!t->jslink)
		return;

	if (itype <= INP_RADIO && !isselect) {
		set_property_string_t(t, "value", t->value);
		if (itype != INP_FILE) {
/* No default value on file, for security reasons */
			set_property_string_t(t, defvl, t->value);
		}		// not file
	}

	if (itype >= INP_RADIO) {
		set_property_bool_t(t, "checked", t->checked);
		set_property_bool_t(t, defck, t->checked);
	}
}

static void optionJS(Tag *t, Tag *og)
{
	Tag *sel = t->controller;
	const char *tx = t->textval;

	if (!sel)
		return;

	if (!tx) {
		debugPrint(3, "empty option");
	} else {
		if (!t->value)
			t->value = cloneString(tx);
	}
/* no point if the controlling select doesn't have a js object */
	if (!sel->jslink)
		return;
establish_js_option(t, sel, og);
// nodeName and nodeType set in constructor
	set_property_string_t(t, "text", t->textval);
	set_property_string_t(t, "value", t->value);
	set_property_bool_t(t, "selected", t->checked);
	set_property_bool_t(t, defsel, t->checked);
	if (t->checked && !sel->multiple)
		set_property_number_t(sel, "selectedIndex", t->lic);
}

// tkae a snapshot of the nodes going up the tree
static char *upSnap(const Tag *t)
{
	char buf[24];
	char *up;
	int up_l;
	up = initString(&up_l);
	while(t) {
		sprintf(buf, "|%d", t->seqno);
		stringAndString(&up, &up_l, buf);
		t = t->parent;
	}
	return up;
}

static void jsNode(Tag *t, bool opentag, struct parseContext *pc)
{
    const struct tagInfo *ti = t->info;
    int action = t->action;
    const Tag *above;
    const char *a;
    bool linked_in;

/*********************************************************************
If js is off, and you don't decorate this tree,
then js is turned on later, and you parse and decorate a frame,
it might also decorate this tree in the wrong context.
Needless to say that's not good!
*********************************************************************/
    if (t->f0 != cf) return;

// careful! if step is already 2 we shouldn't do anything -
// except step could be 4 on a script, loaded, and we need to run it.
    if (t->step >= 2) {
        if(cf->xmlMode ||
        !(t->step <= 4 && t->action == TAGACT_SCRIPT && !opentag))
            return;
    }

    debugPrint(6, "decorate %s%s %d",
    t->info->name, (!opentag ? "/" : ""), t->seqno);

// all the js variables are on the open tag, except one or two
    if (!opentag) {
// run reindex at table close
        if (action == TAGACT_TABLE && t->jslink)
            run_function_onearg_win(cf, "rowReindex", t);

// run reindex at form close
        if (action == TAGACT_FORM && t->jslink)
            run_function_onearg_win(cf, "formReindex", t);

// close the option group
        if(action == TAGACT_OPTG) pc->currentOG = 0;

// turn template into document fragment
        if(action == TAGACT_TEMPLATE && !cf->xmlMode) {
// when I reference content, the getter does everything we need
            get_property_bool_t(t, "content");
        }

// keep the live array of frames up to date
        if(action == TAGACT_FRAME)
            run_function_bool_win(cf, "frames$rebuild");

// test to see if we should run this script right now.
// It should be part of the html, not generated - I think.
// And definitely not xml.
        if(action == TAGACT_SCRIPT && !cf->xmlMode) {
            if(t->scriptgen) {
                debugPrint(3, "script %d postponed because it is generated by another script", t->seqno);
            } else if(t->async && t->href && !isDataURI(t->href)) {
                debugPrint(3, "script %d postponed because it is deferred", t->seqno);
            } else {
// in case the script calls getComputedStyle
            loadFinishCSS();
            char *up1 = upSnap(t);
            runScriptNow(cf, t);
            char *up2 = upSnap(t);
// Did the script remove itself, or move itself to a new place,
// while we're in the middle of building the tree?!
// this doesn't catch every possible weird thing a script might do,
// but most of them.
            if(!t->parent) {
                debugPrint(3, "script %d removes itself from the tree", t->seqno);
            } else if(!stringEqual(up1, up2)) {
                debugPrint(0, "script moves itself within the tree.\n<%s><%s>\nDecoration process aborted. Results of this browse are unpredictable!",
                up1, up2);
                pc->abort = true;
            }
            nzFree(up1), nzFree(up2);
            }
        }

        t->step = 2;
        return;
    }

	switch (action) {

	case TAGACT_TEXT:
		debugPrint(5, "domText");
		establish_js_textnode(t);
// nodeName and nodeType set in constructor
		if (t->jslink) {
			const char *w = t->textval;
			set_property_string_t(t, "data", w);
			if(t->deleted && t->parent && t->parent->action == TAGACT_SCRIPT)
				set_property_bool_t(t, "eb$nomove", true);
		}
		break;

	case TAGACT_DOCTYPE:
		domLink(t, "DocType", 0, 0, 4);
		t->deleted = true;
		break;

	case TAGACT_HTML:
		domLink(t, "HTML", 0, 0, 4);
		cf->htmltag = t;
		set_property_string_win(cf, "eb$base", cf->fileName);
		break;

	case TAGACT_META:
		domLink(t, "HTMLMetaElement", "metas", 0, 4);
		break;

	case TAGACT_STYLE:
		domLink(t, "HTMLStyleElement", "styles", 0, 4);
		a = attribVal(t, "type");
		if (!a) a = emptyString;
// if type is spilldown then we don't need this line.
		set_property_string_t(t, "type", a);
		break;

	case TAGACT_COMMENT:
		domLink(t, "Comment", 0, 0, 4);
		set_property_string_t(t, "data", t->textval);
		break;

	case TAGACT_CDATA:
		nzFree(t->nodeName), nzFree(t->nodeNameU);
		t->nodeName = cloneString("cdata");
		t->nodeNameU = cloneString("CDATA");
		domLink(t, "CDATASection", 0, 0, 4);
		set_property_string_t(t, "text", t->textval);
		break;

	case TAGACT_SCRIPT:
		domLink(t, "HTMLScriptElement", "scripts", 0, 4);
		a = attribVal(t, "text");
		if (a) set_property_string_t(t, "text", a);
		else set_property_string_t(t, "text", "");
		loadScriptData(t);
		break;

	case TAGACT_FORM:
		domLink(t, "HTMLFormElement", "forms", 0, 4);
		break;

	case TAGACT_INPUT:
		formControlJS(t);
		if (t->itype == INP_TA)
			establish_inner(t, t->value, 0, true);
		break;

	case TAGACT_OPTION:
		if(t->controller) {
			optionJS(t, pc->currentOG);
// The parent child relationship has already been established,
// don't break, just return;
			pushAttributes(t);
			return;
		}
// not part of a select, just link to parent as usual
		domLink(t, "HTMLOptionElement", 0, 0, 4);
		break;

	case TAGACT_OPTG:
		domLink(t, "HTMLOptGroupElement", 0, 0, 4);
		pc->currentOG = t;
		break;

	case TAGACT_DATAL:
		domLink(t, "Datalist", 0, 0, 4);
		break;

	case TAGACT_A:
		domLink(t, "HTMLAnchorElement", "links", 0, 4);
		break;

	case TAGACT_HEAD:
		domLink(t, "HTMLHeadElement", "heads", 0, 4);
		cf->headtag = t;
		break;

	case TAGACT_BODY:
		domLink(t, "HTMLBodyElement", "bodies", 0, 4);
		cf->bodytag = t;
		break;

	case TAGACT_OL:
		domLink(t, "HTMLOListElement", 0, 0, 4);
		break;
	case TAGACT_UL:
		domLink(t, "HTMLUListElement", 0, 0, 4);
		break;
	case TAGACT_DL:
		domLink(t, "HTMLDListElement", 0, 0, 4);
		break;

	case TAGACT_LI:
		domLink(t, "HTMLLIElement", 0, 0, 4);
		break;

	case TAGACT_CANVAS:
		domLink(t, "HTMLCanvasElement", 0, 0, 4);
		break;

	case TAGACT_TABLE:
		domLink(t, "HTMLTableElement", "tables", 0, 4);
		break;

	case TAGACT_TBODY:
		if ((above = t->controller) && above->jslink)
			domLink(t, "tBody", "tBodies", above, 0);
		break;

	case TAGACT_THEAD:
		if ((above = t->controller) && above->jslink) {
			domLink(t, "tHead", 0, above, 0);
			set_property_object_t(above, "tHead", t);
		}
		break;

	case TAGACT_TFOOT:
		if ((above = t->controller) && above->jslink) {
			domLink(t, "tFoot", 0, above, 0);
			set_property_object_t(above, "tFoot", t);
		}
		break;

	case TAGACT_TR:
		if ((above = t->controller) && above->jslink)
			domLink(t, "HTMLTableRowElement", "rows", above, 0);
		break;

	case TAGACT_TD:
		if ((above = t->controller) && above->jslink)
			domLink(t, "HTMLTableCellElement", "cells", above, 0);
		break;

	case TAGACT_DIV:
		domLink(t, "HTMLDivElement", "divs", 0, 4);
		break;

	case TAGACT_LABEL:
		domLink(t, "HTMLLabelElement", "labels", 0, 4);
		break;

	case TAGACT_OBJECT:
		domLink(t, "HTMLObjectElement", "htmlobjs", 0, 4);
		break;

	case TAGACT_SPAN:
	case TAGACT_SUB:
	case TAGACT_SUP:
	case TAGACT_OVB:
		domLink(t, "HTMLSpanElement", 0, 0, 4);
		break;

	case TAGACT_AREA:
		domLink(t, "HTMLAreaElement", "links", 0, 4);
		break;

	case TAGACT_FRAME:
// about:blank means a blank frame with no sourcefile.
		if (stringEqual(t->href, "about:blank")) {
			nzFree0(t->href);
		}
		domLink(t, "HTMLFrameElement", 0, 0, 2);
		break;

	case TAGACT_IMAGE:
		domLink(t, "HTMLImageElement", "images", 0, 4);
		break;

	case TAGACT_P:
		domLink(t, "HTMLParagraphElement", "paragraphs", 0, 4);
		break;

	case TAGACT_H:
		domLink(t, "HTMLHeadingElement", 0, 0, 4);
		break;

	case TAGACT_HEADER:
		domLink(t, "Header", "headers", 0, 4);
		break;

	case TAGACT_FOOTER:
		domLink(t, "Footer", "footers", 0, 4);
		break;

	case TAGACT_TITLE:
		if (cw->htmltitle)
			set_property_string_doc(cf, "title", cw->htmltitle);
		domLink(t, "Title", 0, 0, 4);
		break;

	case TAGACT_LINK:
		domLink(t, "HTMLLinkElement", 0, 0, 4);
		loadScriptData(t);
		break;

	case TAGACT_MUSIC:
		if(!opentag) break;
		domLink(t, "HTMLAudioElement", 0, 0, 4);
		break;

	case TAGACT_TEMPLATE:
		if(opentag) {
			domLink(t, "HTMLTemplateElement", 0, 0, 4);
		}
		break;

	case TAGACT_DET:
		if(opentag) {
			domLink(t, "HTMLDetailsElement", 0, 0, 4);
		}
		break;

	case TAGACT_BASE:
		domLink(t, "HTMLBaseElement", 0, 0, 4);
		if(t->href && *t->href)
			set_property_string_win(cf, "eb$base", t->href);
		break;

	case TAGACT_TIME:
		domLink(t, "HTMLTimeElement", 0, 0, 0);
		break;

	case TAGACT_HE:
		domLink(t, "HTMLElement", 0, 0, 0);
		break;

	default:
// this could be a custom element
		domLink(t, t->nodeName, 0, pc->innerParent, 8);
		break;
	}			// switch

	if (!t->jslink)
		return;		// nothing else to do

// js tree mirrors the dom tree
	linked_in = false;

	if (t->parent && t->parent->jslink) {
		run_function_onearg_t(t->parent, "eb$apch1", t);
		linked_in = true;
	}

// doctype and html are at the top of an html document.
// for xml, anything can be at the top.
	if ((cf->xmlMode && !linked_in) || (action == TAGACT_HTML || action == TAGACT_DOCTYPE)) {
		run_function_onearg_doc(cf, "eb$apch1", t);
		linked_in = true;
	}

// innerHTML should never apply in the xml world
	if (!t->parent && pc->innerParent) {
// this is the top of innerHTML or some such.
// It is never html head or body, as those are skipped.
		run_function_onearg_t(pc->innerParent, "eb$apch1", t);
		linked_in = true;
	}

	if (!linked_in) {
		debugPrint(3, "tag %s not linked in", ti->name);
		if (action == TAGACT_TEXT)
			debugPrint(3, "text %s\n", t->textval);
	}

/* set innerHTML from the source html, if this tag supports it */
	if (ti->bits & TAG_INNERHTML)
		establish_inner(t, t->innerHTML, 0, false);

// If the tag has foo=bar as an attribute, pass this forward to javascript.
	pushAttributes(t);
}

static void pushAttributes(const Tag *t)
{
	int i;
	const char **a = t->attributes;
	const char **v = t->atvals;
	const char *u;
	char *x;
	if (!a)
		return;

	for (i = 0; a[i]; ++i) {
// html tags and attributes are case insensitive.
// That's why the entire getAttribute system drops names to lower case.
		u = v[i];
		if (!u)
			u = emptyString;
		x = cloneString(a[i]);
		if(!cf->xmlMode) caseShift(x, 'l');

		if (stringEqual(x, "style")) {	// no clue
			nzFree(x);
			continue;
		}

		run_function_twostring_t(t, "setAttribute", x, u);
// special case, classname sets the class.
// Are there others like this?
		if(stringEqual(x, "classname"))
			run_function_twostring_t(t, "setAttribute", "class", u);
		nzFree(x);
	}
}

/* decorate the tree of nodes with js objects */
void decorate(int start, Tag *above)
{
	struct parseContext pc;
	if(cf->xmlMode) {
		set_property_bool_doc(cf, "eb$xml", true);
		set_property_string_doc(cf, "dom$class", "XMLDocument");
	}
	pc.callback = jsNode;
	pc.currentOG = 0;
	pc.innerParent = above;
	pc.liftup = ! cf->xmlMode;

	debugPrint(4, "decorate starts at %d", start);
	traverseAll(start, &pc);
	debugPrint(4, "decorate complete");
}

/*********************************************************************
Consider the table
<table>
<tr><td>a</td><td>b</td><td rowspan=2>c</td><td>d</td><td>e</td></tr>
<tr><td>w</td><td>x</td><td>y</td><td>z</td></tr>
</table>
Lef to its own devices, edbrowse represents this as
a|b|c|d|e
w|x|y|z
y is below c, and only 4 cells on the second row.  That's wrong.
In a perfect world it might look like
a|b|c|d|e
w|x|c|y|z
This could be problematic if c is an entire paragraph with links and so on.
Duplicating that could screw up javascript.
Just duplicating its appearance could work but is not trivial.
Perhaps an up arrow indicating that you need to look up for this value.
a|b|c|d|e
w|x|^|y|z
You don't want to do any of this for a presentation table, only a data table.
Presentation might have a picture with rowspan=3, then
three cells to the right, each a section that javascript can access or update.
edbrowse shows this as
[picture]
paragraph 1
paragraph 2
paragraph 3
And that's exactly what you want. You don't particularly
care that the paragraphs are to the right of the picture on the screen.
Trying to convey that would be confusing.
So there are many things to consider here.
I don't know the answer, but I do know,
you have to start with an accurate representation.
If you can't represent it inside, you can't present it.
As always,we have the javascript double-edged sword.
1. js might create the entire table, including rowspan attributes, from scratch.
We can't do this from the html tags, we have to do it when the screen
is rendered, thus this machinery is called
from the top of render() in html.c.
Also, we can't change any of the tags or the tree structure.
I can't copy tags from one row down to the row below,
because running js might assume the cells are exactly as they were created.
That is, 4 cells on row 2, w, x, y, and z.
Changing that could break js in unexpected ways.
I can only muck with the display.
So, represent the location of the cells,
and the propagation of the cells by rowspan, using edbrowse variables only,
then figure out what to do about it in render().
I'm overloading 3 variables in Tag, and I know that's ugly,
but I don't feel like creating new ones just for this feature,
and these three aren't being used by <tr> or <td>, so here we go.
lic is rowspan and js_ln is colspan, (on td), and js_file is the cellstring.
What is a cellstring?
It is a comma separated list of cells, or merged cells, or inherited cells,
on that row.
If the cell is a simple <td></td>, then we don't need anything,
and we can just append a comma.
If rowspan = 7, append 1/7/seqno, the first row of 7,
and the sequence number of this tag in its row.
The next row will inherit this, in position, and will show 2/7/seqno.
The next row 3/7/seqno and so on.
If colspan=3, we append @3.
Both rowspan and colspan are possible, but very unlikely.   1/7/seqno@3
Each cellstring is constructed from the cells on that row
and the cellstring from the row above.
When you run into the first <td>, if the inherited string starts with
3/7 then we have to put in 4/7, and then move on to the new cell <td>.
This resembels the merge of two sorted lists.
It's nontrivial though.
Errors are possible, like
<tr><td></td><td rowspan=2></td></tr>
<tr><td colspan=2></td></tr>
Such should never happen in the real world, but don't be surprised if it does.
I will defer to rowspan over colspan.
Hang on, here we go.
*********************************************************************/

static void rowspan2(Tag *tr, int ri);
static void rowspan3(Tag *tr, int ri);
void rowspan(void)
{
	const Tag *table, *section;
	Tag *tr, *last_tr;
	int i, ri;

	for(i = 0; i < cw->numTags; ++i) {
		table = tagList[i];
		if(table->action != TAGACT_TABLE || table->dead || table->deleted)
			continue;

// I set last_tr here assuming rowspans and such carry through
// from head to body to foot, and are not independent in each section.
// If rather each section is independent then move this line down.
		last_tr = 0;

		ri = 0;
		for(section = table->firstchild; section; section = section->sibling) {
			if(section->action != TAGACT_THEAD && 
			section->action != TAGACT_TBODY &&
			section->action != TAGACT_TFOOT)
				continue;
//			last_tr = 0;
			for(tr = section->firstchild; tr; tr = tr->sibling) {
				if(tr->action != TAGACT_TR) continue;
// link to previous row
				tr->same = last_tr;
				last_tr = tr;
// js_file holds the descriptive string of the cells in the row
				nzFree0(tr->js_file);
				rowspan2(tr, ++ri);
			}
		}

		ri = 0;
		for(section = table->firstchild; section; section = section->sibling) {
			if(section->action != TAGACT_THEAD && 
			section->action != TAGACT_TBODY &&
			section->action != TAGACT_TFOOT)
				continue;
			for(tr = section->firstchild; tr; tr = tr->sibling) {
				if(tr->action == TAGACT_TR)
					rowspan3(tr, ++ri);
			}
		}
	}
}

static void rowspan2(Tag *tr, int ri)
{
	Tag *td, *last_td;
	char *ihs; // inherited cellstring
	char *save_ihs;
	char *ns; // new string
	int ns_l, end_l;
	int c1, c2; // column numbers
	int irl, irs; // inherited row level and span
	int ics; // inherited colspan
	int seqno = 0;
	bool needstring;
	char b[32];

// start by setting rowspan and colspan, possibly from javascript.
	for(td = tr->firstchild; td; td = td->sibling) {
		const char *v;
		if(td->action != TAGACT_TD) continue;
		td->lic = td->js_ln = 1;
// from html attributes first
			v = attribVal(td, "rowspan");
			if(v && isdigitByte(*v)) td->lic = atoi(v);
			v = attribVal(td, "colspan");
			if(v && isdigitByte(*v)) td->js_ln = atoi(v);
		if(allowJS && td->jslink) {
			int n = get_property_number_t(td, "rowspan");
			if(n > 0) td->lic = n;
			n = get_property_number_t(td, "colspan");
			if(n > 0) td->js_ln = n;
		}
		if(td->lic <= 0) td->lic = 1;
		if(td->js_ln <= 0) td->js_ln = 1;
	}

	ihs = (tr->same ? tr->same->js_file : 0);
	c1 = c2 = 1;
	ns = initString(&ns_l);
	last_td = 0;
	needstring = false;

	for(td = tr->firstchild; td; td = td->sibling) {
		if(td->action != TAGACT_TD) continue;
crack:
		irs = irl = ics = 1;
		if((save_ihs = ihs)) {
			if(isdigitByte(*ihs)) {
				irl = strtol(ihs, &ihs, 10);
				irs = strtol(ihs + 1, &ihs, 10);
				seqno = strtol(ihs + 1, &ihs, 10);
			}
			if(*ihs == '@')
				ics = strtol(ihs + 1, &ihs, 10);
			++ihs; // skip past comma
			if(!*ihs) ihs = 0; // end of string
		}

// c1 could be less than c2, if we just brought in a cell with a wide colspan
		if(c1 < c2 && irl < irs) {
// This is the error condition I talked about
// shrink colspan so this cell fits
			last_td->js_ln -= (c2-c1);
			while(ns[--ns_l] != '@')  ;
			if(last_td->js_ln == 1) strcpy(ns + ns_l, ",");
			else
				sprintf(ns + ns_l, "@%d,", last_td->js_ln);
			ns_l = strlen(ns);
			c2 = c1;
		}
		if(c1 < c2) {
			c1 += ics;
			goto crack;
		}
		if(c1 > c2) goto incorporate;
		if(irl == irs) goto incorporate;
		needstring = true;
		sprintf(b, "%d/%d/%d", irl + 1, irs, seqno);
		if(ics > 1) sprintf(b + strlen(b), "@%d", ics);
		strcat(b, ",");
		stringAndString(&ns, &ns_l, b);
		c1 += ics, c2 += ics;
		goto crack;
incorporate:
		b[0] = 0;
		if(td->lic > 1) {
			sprintf(b, "1/%d/%d", td->lic, td->seqno);
			needstring = true;
		}
		if(td->js_ln > 1) sprintf(b + strlen(b), "@%d", td->js_ln);
		strcat(b, ",");
		stringAndString(&ns, &ns_l, b);
		c2 += td->js_ln;
		ihs = save_ihs;
		last_td = td;
	}

	end_l = ns_l;
// rowspans hanging down after this row is done
	while(ihs) {
		irs = irl = ics = 1;
		if(isdigitByte(*ihs)) {
			irl = strtol(ihs, &ihs, 10);
			irs = strtol(ihs + 1, &ihs, 10);
			seqno = strtol(ihs + 1, &ihs, 10);
		}
		if(*ihs == '@')
			ics = strtol(ihs + 1, &ihs, 10);
		++ihs; // skip past comma
		if(!*ihs) ihs = 0; // end of string
c1c2:
		if(c1 < c2 && irl < irs) {
			last_td->js_ln -= (c2-c1);
			while(ns[--ns_l] != '@')  ;
			if(last_td->js_ln == 1) strcpy(ns + ns_l, ",");
			else
				sprintf(ns + ns_l, "@%d,", last_td->js_ln);
			end_l = ns_l = strlen(ns);
			c2 = c1;
		}
		if(c1 < c2) {
			c1 += ics;
			continue;
		}
		if(c1 > c2) goto addcomma;
		if(irl == irs) goto addcomma;
		needstring = true;
		sprintf(b, "%d/%d/%d", irl + 1, irs, seqno);
		if(ics > 1) sprintf(b + strlen(b), "@%d", ics);
		strcat(b, ",");
		stringAndString(&ns, &ns_l, b);
		end_l = ns_l;
		c1 += ics, c2 += ics;
		continue;
addcomma:
		stringAndChar(&ns, &ns_l, ',');
		++c2;
		goto c1c2;
	}

	if(needstring) {
		ns[end_l] = 0;
		tr->js_file = ns;
		debugPrint(3, "row %d %s", ri, ns);
		} else nzFree(ns);
}

// Simplify the cellstrings
static void rowspan3(Tag *tr, int ri)
{
	char *ihs; // inherited cellstring
	int irl, irs; // inherited row level and span
	int seqno, ics;
	char *s, *t;
	bool needstring = false;

	if(!(ihs = tr->js_file)) return;

	s = t = ihs;
	while(*s) {
		if(isdigitByte(*s)) {
			irl = strtol(s, &s, 10);
			irs = strtol(s + 1, &s, 10);
			(void)irs; // not used
			seqno = strtol(s + 1, &s, 10);
			ics = 1;
			if(*s == '@')
				ics = strtol(s + 1, &s, 10);
			if(!--irl) continue; // don't need it
			sprintf(t, "%d", seqno);
			t += strlen(t);
			if(ics > 1) {
				sprintf(t, "@%d", ics);
				t += strlen(t);
			}
			needstring = true;
			continue;
		}
		if(*s == '@') {
			while(*s != ',') *t++ = *s++;
		}
		*t++ = *s++;
	}
	*t = 0;

	if(!needstring) {
		nzFree(ihs);
		tr->js_file = 0;
	} else
		debugPrint(3, "row %d %s", ri, ihs);
}
