1 // FIXME: add classList. it is a live list and removes whitespace and duplicates when you use it.
2 // FIXME: xml namespace support???
3 // FIXME: https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML
4 // FIXME: parentElement is parentNode that skips DocumentFragment etc but will be hard to work in with my compatibility...
5 
6 // FIXME: the scriptable list is quite arbitrary
7 
8 
9 // xml entity references?!
10 
11 /++
12 	This is an html DOM implementation, started with cloning
13 	what the browser offers in Javascript, but going well beyond
14 	it in convenience.
15 
16 	If you can do it in Javascript, you can probably do it with
17 	this module, and much more.
18 
19 	---
20 	import arsd.dom;
21 
22 	void main() {
23 		auto document = new Document("<html><p>paragraph</p></html>");
24 		writeln(document.querySelector("p"));
25 		document.root.innerHTML = "<p>hey</p>";
26 		writeln(document);
27 	}
28 	---
29 
30 	BTW: this file optionally depends on `arsd.characterencodings`, to
31 	help it correctly read files from the internet. You should be able to
32 	get characterencodings.d from the same place you got this file.
33 
34 	If you want it to stand alone, just always use the `Document.parseUtf8`
35 	function or the constructor that takes a string.
36 
37 	Symbol_groups:
38 
39 	core_functionality =
40 
41 	These members provide core functionality. The members on these classes
42 	will provide most your direct interaction.
43 
44 	bonus_functionality =
45 
46 	These provide additional functionality for special use cases.
47 
48 	implementations =
49 
50 	These provide implementations of other functionality.
51 +/
52 module arsd.dom;
53 
54 // FIXME: support the css standard namespace thing in the selectors too
55 
56 version(with_arsd_jsvar)
57 	import arsd.jsvar;
58 else {
59 	enum scriptable = "arsd_jsvar_compatible";
60 }
61 
62 // this is only meant to be used at compile time, as a filter for opDispatch
63 // lists the attributes we want to allow without the use of .attr
64 bool isConvenientAttribute(string name) {
65 	static immutable list = [
66 		"name", "id", "href", "value",
67 		"checked", "selected", "type",
68 		"src", "content", "pattern",
69 		"placeholder", "required", "alt",
70 		"rel",
71 		"method", "action", "enctype"
72 	];
73 	foreach(l; list)
74 		if(name == l) return true;
75 	return false;
76 }
77 
78 
79 // FIXME: something like <ol>spam <ol> with no closing </ol> should read the second tag as the closer in garbage mode
80 // FIXME: failing to close a paragraph sometimes messes things up too
81 
82 // FIXME: it would be kinda cool to have some support for internal DTDs
83 // and maybe XPath as well, to some extent
84 /*
85 	we could do
86 	meh this sux
87 
88 	auto xpath = XPath(element);
89 
90 	     // get the first p
91 	xpath.p[0].a["href"]
92 */
93 
94 
95 /// The main document interface, including a html parser.
96 /// Group: core_functionality
97 class Document : FileResource, DomParent {
98 	inout(Document) asDocument() inout { return this; }
99 	inout(Element) asElement() inout { return null; }
100 
101 	/// Convenience method for web scraping. Requires [arsd.http2] to be
102 	/// included in the build as well as [arsd.characterencodings].
103 	static Document fromUrl()(string url, bool strictMode = false) {
104 		import arsd.http2;
105 		auto client = new HttpClient();
106 
107 		auto req = client.navigateTo(Uri(url), HttpVerb.GET);
108 		auto res = req.waitForCompletion();
109 
110 		auto document = new Document();
111 		if(strictMode) {
112 			document.parse(cast(string) res.content, true, true, res.contentTypeCharset);
113 		} else {
114 			document.parseGarbage(cast(string) res.content);
115 		}
116 
117 		return document;
118 	}
119 
120 	///.
121 	this(string data, bool caseSensitive = false, bool strict = false) {
122 		parseUtf8(data, caseSensitive, strict);
123 	}
124 
125 	/**
126 		Creates an empty document. It has *nothing* in it at all.
127 	*/
128 	this() {
129 
130 	}
131 
132 	/// This is just something I'm toying with. Right now, you use opIndex to put in css selectors.
133 	/// It returns a struct that forwards calls to all elements it holds, and returns itself so you
134 	/// can chain it.
135 	///
136 	/// Example: document["p"].innerText("hello").addClass("modified");
137 	///
138 	/// Equivalent to: foreach(e; document.getElementsBySelector("p")) { e.innerText("hello"); e.addClas("modified"); }
139 	///
140 	/// Note: always use function calls (not property syntax) and don't use toString in there for best results.
141 	///
142 	/// You can also do things like: document["p"]["b"] though tbh I'm not sure why since the selector string can do all that anyway. Maybe
143 	/// you could put in some kind of custom filter function tho.
144 	ElementCollection opIndex(string selector) {
145 		auto e = ElementCollection(this.root);
146 		return e[selector];
147 	}
148 
149 	string _contentType = "text/html; charset=utf-8";
150 
151 	/// If you're using this for some other kind of XML, you can
152 	/// set the content type here.
153 	///
154 	/// Note: this has no impact on the function of this class.
155 	/// It is only used if the document is sent via a protocol like HTTP.
156 	///
157 	/// This may be called by parse() if it recognizes the data. Otherwise,
158 	/// if you don't set it, it assumes text/html; charset=utf-8.
159 	@property string contentType(string mimeType) {
160 		_contentType = mimeType;
161 		return _contentType;
162 	}
163 
164 	/// implementing the FileResource interface, useful for sending via
165 	/// http automatically.
166 	@property string filename() const { return null; }
167 
168 	/// implementing the FileResource interface, useful for sending via
169 	/// http automatically.
170 	override @property string contentType() const {
171 		return _contentType;
172 	}
173 
174 	/// implementing the FileResource interface; it calls toString.
175 	override immutable(ubyte)[] getData() const {
176 		return cast(immutable(ubyte)[]) this.toString();
177 	}
178 
179 
180 	/// Concatenates any consecutive text nodes
181 	/*
182 	void normalize() {
183 
184 	}
185 	*/
186 
187 	/// This will set delegates for parseSaw* (note: this overwrites anything else you set, and you setting subsequently will overwrite this) that add those things to the dom tree when it sees them.
188 	/// Call this before calling parse().
189 
190 	/// Note this will also preserve the prolog and doctype from the original file, if there was one.
191 	void enableAddingSpecialTagsToDom() {
192 		parseSawComment = (string) => true;
193 		parseSawAspCode = (string) => true;
194 		parseSawPhpCode = (string) => true;
195 		parseSawQuestionInstruction = (string) => true;
196 		parseSawBangInstruction = (string) => true;
197 	}
198 
199 	/// If the parser sees a html comment, it will call this callback
200 	/// <!-- comment --> will call parseSawComment(" comment ")
201 	/// Return true if you want the node appended to the document.
202 	bool delegate(string) parseSawComment;
203 
204 	/// If the parser sees <% asp code... %>, it will call this callback.
205 	/// It will be passed "% asp code... %" or "%= asp code .. %"
206 	/// Return true if you want the node appended to the document.
207 	bool delegate(string) parseSawAspCode;
208 
209 	/// If the parser sees <?php php code... ?>, it will call this callback.
210 	/// It will be passed "?php php code... ?" or "?= asp code .. ?"
211 	/// Note: dom.d cannot identify  the other php <? code ?> short format.
212 	/// Return true if you want the node appended to the document.
213 	bool delegate(string) parseSawPhpCode;
214 
215 	/// if it sees a <?xxx> that is not php or asp
216 	/// it calls this function with the contents.
217 	/// <?SOMETHING foo> calls parseSawQuestionInstruction("?SOMETHING foo")
218 	/// Unlike the php/asp ones, this ends on the first > it sees, without requiring ?>.
219 	/// Return true if you want the node appended to the document.
220 	bool delegate(string) parseSawQuestionInstruction;
221 
222 	/// if it sees a <! that is not CDATA or comment (CDATA is handled automatically and comments call parseSawComment),
223 	/// it calls this function with the contents.
224 	/// <!SOMETHING foo> calls parseSawBangInstruction("SOMETHING foo")
225 	/// Return true if you want the node appended to the document.
226 	bool delegate(string) parseSawBangInstruction;
227 
228 	/// Given the kind of garbage you find on the Internet, try to make sense of it.
229 	/// Equivalent to document.parse(data, false, false, null);
230 	/// (Case-insensitive, non-strict, determine character encoding from the data.)
231 
232 	/// NOTE: this makes no attempt at added security.
233 	///
234 	/// It is a template so it lazily imports characterencodings.
235 	void parseGarbage()(string data) {
236 		parse(data, false, false, null);
237 	}
238 
239 	/// Parses well-formed UTF-8, case-sensitive, XML or XHTML
240 	/// Will throw exceptions on things like unclosed tags.
241 	void parseStrict(string data) {
242 		parseStream(toUtf8Stream(data), true, true);
243 	}
244 
245 	/// Parses well-formed UTF-8 in loose mode (by default). Tries to correct
246 	/// tag soup, but does NOT try to correct bad character encodings.
247 	///
248 	/// They will still throw an exception.
249 	void parseUtf8(string data, bool caseSensitive = false, bool strict = false) {
250 		parseStream(toUtf8Stream(data), caseSensitive, strict);
251 	}
252 
253 	// this is a template so we get lazy import behavior
254 	Utf8Stream handleDataEncoding()(in string rawdata, string dataEncoding, bool strict) {
255 		import arsd.characterencodings;
256 		// gotta determine the data encoding. If you know it, pass it in above to skip all this.
257 		if(dataEncoding is null) {
258 			dataEncoding = tryToDetermineEncoding(cast(const(ubyte[])) rawdata);
259 			// it can't tell... probably a random 8 bit encoding. Let's check the document itself.
260 			// Now, XML and HTML can both list encoding in the document, but we can't really parse
261 			// it here without changing a lot of code until we know the encoding. So I'm going to
262 			// do some hackish string checking.
263 			if(dataEncoding is null) {
264 				auto dataAsBytes = cast(immutable(ubyte)[]) rawdata;
265 				// first, look for an XML prolog
266 				auto idx = indexOfBytes(dataAsBytes, cast(immutable ubyte[]) "encoding=\"");
267 				if(idx != -1) {
268 					idx += "encoding=\"".length;
269 					// we're probably past the prolog if it's this far in; we might be looking at
270 					// content. Forget about it.
271 					if(idx > 100)
272 						idx = -1;
273 				}
274 				// if that fails, we're looking for Content-Type http-equiv or a meta charset (see html5)..
275 				if(idx == -1) {
276 					idx = indexOfBytes(dataAsBytes, cast(immutable ubyte[]) "charset=");
277 					if(idx != -1) {
278 						idx += "charset=".length;
279 						if(dataAsBytes[idx] == '"')
280 							idx++;
281 					}
282 				}
283 
284 				// found something in either branch...
285 				if(idx != -1) {
286 					// read till a quote or about 12 chars, whichever comes first...
287 					auto end = idx;
288 					while(end < dataAsBytes.length && dataAsBytes[end] != '"' && end - idx < 12)
289 						end++;
290 
291 					dataEncoding = cast(string) dataAsBytes[idx .. end];
292 				}
293 				// otherwise, we just don't know.
294 			}
295 		}
296 
297 		if(dataEncoding is null) {
298 			if(strict)
299 				throw new MarkupException("I couldn't figure out the encoding of this document.");
300 			else
301 			// if we really don't know by here, it means we already tried UTF-8,
302 			// looked for utf 16 and 32 byte order marks, and looked for xml or meta
303 			// tags... let's assume it's Windows-1252, since that's probably the most
304 			// common aside from utf that wouldn't be labeled.
305 
306 			dataEncoding = "Windows 1252";
307 		}
308 
309 		// and now, go ahead and convert it.
310 
311 		string data;
312 
313 		if(!strict) {
314 			// if we're in non-strict mode, we need to check
315 			// the document for mislabeling too; sometimes
316 			// web documents will say they are utf-8, but aren't
317 			// actually properly encoded. If it fails to validate,
318 			// we'll assume it's actually Windows encoding - the most
319 			// likely candidate for mislabeled garbage.
320 			dataEncoding = dataEncoding.toLower();
321 			dataEncoding = dataEncoding.replace(" ", "");
322 			dataEncoding = dataEncoding.replace("-", "");
323 			dataEncoding = dataEncoding.replace("_", "");
324 			if(dataEncoding == "utf8") {
325 				try {
326 					validate(rawdata);
327 				} catch(UTFException e) {
328 					dataEncoding = "Windows 1252";
329 				}
330 			}
331 		}
332 
333 		if(dataEncoding != "UTF-8") {
334 			if(strict)
335 				data = convertToUtf8(cast(immutable(ubyte)[]) rawdata, dataEncoding);
336 			else {
337 				try {
338 					data = convertToUtf8(cast(immutable(ubyte)[]) rawdata, dataEncoding);
339 				} catch(Exception e) {
340 					data = convertToUtf8(cast(immutable(ubyte)[]) rawdata, "Windows 1252");
341 				}
342 			}
343 		} else
344 			data = rawdata;
345 
346 		return toUtf8Stream(data);
347 	}
348 
349 	private
350 	Utf8Stream toUtf8Stream(in string rawdata) {
351 		string data = rawdata;
352 		static if(is(Utf8Stream == string))
353 			return data;
354 		else
355 			return new Utf8Stream(data);
356 	}
357 
358 	/++
359 		List of elements that can be assumed to be self-closed
360 		in this document. The default for a Document are a hard-coded
361 		list of ones appropriate for HTML. For [XmlDocument], it defaults
362 		to empty. You can modify this after construction but before parsing.
363 
364 		History:
365 			Added February 8, 2021 (included in dub release 9.2)
366 	+/
367 	string[] selfClosedElements = htmlSelfClosedElements;
368 
369 	/++
370 		List of elements that are considered inline for pretty printing.
371 		The default for a Document are hard-coded to something appropriate
372 		for HTML. For [XmlDocument], it defaults to empty. You can modify
373 		this after construction but before parsing.
374 
375 		History:
376 			Added June 21, 2021 (included in dub release 10.1)
377 	+/
378 	string[] inlineElements = htmlInlineElements;
379 
380 	/**
381 		Take XMLish data and try to make the DOM tree out of it.
382 
383 		The goal isn't to be perfect, but to just be good enough to
384 		approximate Javascript's behavior.
385 
386 		If strict, it throws on something that doesn't make sense.
387 		(Examples: mismatched tags. It doesn't validate!)
388 		If not strict, it tries to recover anyway, and only throws
389 		when something is REALLY unworkable.
390 
391 		If strict is false, it uses a magic list of tags that needn't
392 		be closed. If you are writing a document specifically for this,
393 		try to avoid such - use self closed tags at least. Easier to parse.
394 
395 		The dataEncoding argument can be used to pass a specific
396 		charset encoding for automatic conversion. If null (which is NOT
397 		the default!), it tries to determine from the data itself,
398 		using the xml prolog or meta tags, and assumes UTF-8 if unsure.
399 
400 		If this assumption is wrong, it can throw on non-ascii
401 		characters!
402 
403 
404 		Note that it previously assumed the data was encoded as UTF-8, which
405 		is why the dataEncoding argument defaults to that.
406 
407 		So it shouldn't break backward compatibility.
408 
409 		But, if you want the best behavior on wild data - figuring it out from the document
410 		instead of assuming - you'll probably want to change that argument to null.
411 
412 		This is a template so it lazily imports arsd.characterencodings, which is required
413 		to fix up data encodings.
414 
415 		If you are sure the encoding is good, try parseUtf8 or parseStrict to avoid the
416 		dependency. If it is data from the Internet though, a random website, the encoding
417 		is often a lie. This function, if dataEncoding == null, can correct for that, or
418 		you can try parseGarbage. In those cases, arsd.characterencodings is required to
419 		compile.
420 	*/
421 	void parse()(in string rawdata, bool caseSensitive = false, bool strict = false, string dataEncoding = "UTF-8") {
422 		auto data = handleDataEncoding(rawdata, dataEncoding, strict);
423 		parseStream(data, caseSensitive, strict);
424 	}
425 
426 	// note: this work best in strict mode, unless data is just a simple string wrapper
427 	void parseStream(Utf8Stream data, bool caseSensitive = false, bool strict = false) {
428 		// FIXME: this parser could be faster; it's in the top ten biggest tree times according to the profiler
429 		// of my big app.
430 
431 		assert(data !is null);
432 
433 		// go through character by character.
434 		// if you see a <, consider it a tag.
435 		// name goes until the first non tagname character
436 		// then see if it self closes or has an attribute
437 
438 		// if not in a tag, anything not a tag is a big text
439 		// node child. It ends as soon as it sees a <
440 
441 		// Whitespace in text or attributes is preserved, but not between attributes
442 
443 		// &amp; and friends are converted when I know them, left the same otherwise
444 
445 
446 		// this it should already be done correctly.. so I'm leaving it off to net a ~10% speed boost on my typical test file (really)
447 		//validate(data); // it *must* be UTF-8 for this to work correctly
448 
449 		sizediff_t pos = 0;
450 
451 		clear();
452 
453 		loose = !caseSensitive;
454 
455 		bool sawImproperNesting = false;
456 		bool paragraphHackfixRequired = false;
457 
458 		int getLineNumber(sizediff_t p) {
459 			int line = 1;
460 			foreach(c; data[0..p])
461 				if(c == '\n')
462 					line++;
463 			return line;
464 		}
465 
466 		void parseError(string message) {
467 			throw new MarkupException(format("char %d (line %d): %s", pos, getLineNumber(pos), message));
468 		}
469 
470 		bool eatWhitespace() {
471 			bool ateAny = false;
472 			while(pos < data.length && data[pos].isSimpleWhite) {
473 				pos++;
474 				ateAny = true;
475 			}
476 			return ateAny;
477 		}
478 
479 		string readTagName() {
480 			// remember to include : for namespaces
481 			// basically just keep going until >, /, or whitespace
482 			auto start = pos;
483 			while(data[pos] != '>' && data[pos] != '/' && !data[pos].isSimpleWhite)
484 			{
485 				pos++;
486 				if(pos == data.length) {
487 					if(strict)
488 						throw new Exception("tag name incomplete when file ended");
489 					else
490 						break;
491 				}
492 			}
493 
494 			if(!caseSensitive)
495 				return toLower(data[start..pos]);
496 			else
497 				return data[start..pos];
498 		}
499 
500 		string readAttributeName() {
501 			// remember to include : for namespaces
502 			// basically just keep going until >, /, or whitespace
503 			auto start = pos;
504 			while(data[pos] != '>' && data[pos] != '/'  && data[pos] != '=' && !data[pos].isSimpleWhite)
505 			{
506 				if(data[pos] == '<') {
507 					if(strict)
508 						throw new MarkupException("The character < can never appear in an attribute name. Line " ~ to!string(getLineNumber(pos)));
509 					else
510 						break; // e.g. <a href="something" <img src="poo" /></a>. The > should have been after the href, but some shitty files don't do that right and the browser handles it, so we will too, by pretending the > was indeed there
511 				}
512 				pos++;
513 				if(pos == data.length) {
514 					if(strict)
515 						throw new Exception("unterminated attribute name");
516 					else
517 						break;
518 				}
519 			}
520 
521 			if(!caseSensitive)
522 				return toLower(data[start..pos]);
523 			else
524 				return data[start..pos];
525 		}
526 
527 		string readAttributeValue() {
528 			if(pos >= data.length) {
529 				if(strict)
530 					throw new Exception("no attribute value before end of file");
531 				else
532 					return null;
533 			}
534 			switch(data[pos]) {
535 				case '\'':
536 				case '"':
537 					auto started = pos;
538 					char end = data[pos];
539 					pos++;
540 					auto start = pos;
541 					while(pos < data.length && data[pos] != end)
542 						pos++;
543 					if(strict && pos == data.length)
544 						throw new MarkupException("Unclosed attribute value, started on char " ~ to!string(started));
545 					string v = htmlEntitiesDecode(data[start..pos], strict);
546 					pos++; // skip over the end
547 				return v;
548 				default:
549 					if(strict)
550 						parseError("Attributes must be quoted");
551 					// read until whitespace or terminator (/> or >)
552 					auto start = pos;
553 					while(
554 						pos < data.length &&
555 						data[pos] != '>' &&
556 						// unquoted attributes might be urls, so gotta be careful with them and self-closed elements
557 						!(data[pos] == '/' && pos + 1 < data.length && data[pos+1] == '>') &&
558 						!data[pos].isSimpleWhite)
559 							pos++;
560 
561 					string v = htmlEntitiesDecode(data[start..pos], strict);
562 					// don't skip the end - we'll need it later
563 					return v;
564 			}
565 		}
566 
567 		TextNode readTextNode() {
568 			auto start = pos;
569 			while(pos < data.length && data[pos] != '<') {
570 				pos++;
571 			}
572 
573 			return TextNode.fromUndecodedString(this, data[start..pos]);
574 		}
575 
576 		// this is obsolete!
577 		RawSource readCDataNode() {
578 			auto start = pos;
579 			while(pos < data.length && data[pos] != '<') {
580 				pos++;
581 			}
582 
583 			return new RawSource(this, data[start..pos]);
584 		}
585 
586 
587 		struct Ele {
588 			int type; // element or closing tag or nothing
589 				/*
590 					type == 0 means regular node, self-closed (element is valid)
591 					type == 1 means closing tag (payload is the tag name, element may be valid)
592 					type == 2 means you should ignore it completely
593 					type == 3 means it is a special element that should be appended, if possible, e.g. a <!DOCTYPE> that was chosen to be kept, php code, or comment. It will be appended at the current element if inside the root, and to a special document area if not
594 					type == 4 means the document was totally empty
595 				*/
596 			Element element; // for type == 0 or type == 3
597 			string payload; // for type == 1
598 		}
599 		// recursively read a tag
600 		Ele readElement(string[] parentChain = null) {
601 			// FIXME: this is the slowest function in this module, by far, even in strict mode.
602 			// Loose mode should perform decently, but strict mode is the important one.
603 			if(!strict && parentChain is null)
604 				parentChain = [];
605 
606 			static string[] recentAutoClosedTags;
607 
608 			if(pos >= data.length)
609 			{
610 				if(strict) {
611 					throw new MarkupException("Gone over the input (is there no root element or did it never close?), chain: " ~ to!string(parentChain));
612 				} else {
613 					if(parentChain.length)
614 						return Ele(1, null, parentChain[0]); // in loose mode, we just assume the document has ended
615 					else
616 						return Ele(4); // signal emptiness upstream
617 				}
618 			}
619 
620 			if(data[pos] != '<') {
621 				return Ele(0, readTextNode(), null);
622 			}
623 
624 			enforce(data[pos] == '<');
625 			pos++;
626 			if(pos == data.length) {
627 				if(strict)
628 					throw new MarkupException("Found trailing < at end of file");
629 				// if not strict, we'll just skip the switch
630 			} else
631 			switch(data[pos]) {
632 				// I don't care about these, so I just want to skip them
633 				case '!': // might be a comment, a doctype, or a special instruction
634 					pos++;
635 
636 						// FIXME: we should store these in the tree too
637 						// though I like having it stripped out tbh.
638 
639 					if(pos == data.length) {
640 						if(strict)
641 							throw new MarkupException("<! opened at end of file");
642 					} else if(data[pos] == '-' && (pos + 1 < data.length) && data[pos+1] == '-') {
643 						// comment
644 						pos += 2;
645 
646 						// FIXME: technically, a comment is anything
647 						// between -- and -- inside a <!> block.
648 						// so in <!-- test -- lol> , the " lol" is NOT a comment
649 						// and should probably be handled differently in here, but for now
650 						// I'll just keep running until --> since that's the common way
651 
652 						auto commentStart = pos;
653 						while(pos+3 < data.length && data[pos..pos+3] != "-->")
654 							pos++;
655 
656 						auto end = commentStart;
657 
658 						if(pos + 3 >= data.length) {
659 							if(strict)
660 								throw new MarkupException("unclosed comment");
661 							end = data.length;
662 							pos = data.length;
663 						} else {
664 							end = pos;
665 							assert(data[pos] == '-');
666 							pos++;
667 							assert(data[pos] == '-');
668 							pos++;
669 							assert(data[pos] == '>');
670 							pos++;
671 						}
672 
673 						if(parseSawComment !is null)
674 							if(parseSawComment(data[commentStart .. end])) {
675 								return Ele(3, new HtmlComment(this, data[commentStart .. end]), null);
676 							}
677 					} else if(pos + 7 <= data.length && data[pos..pos + 7] == "[CDATA[") {
678 						pos += 7;
679 
680 						auto cdataStart = pos;
681 
682 						ptrdiff_t end = -1;
683 						typeof(end) cdataEnd;
684 
685 						if(pos < data.length) {
686 							// cdata isn't allowed to nest, so this should be generally ok, as long as it is found
687 							end = data[pos .. $].indexOf("]]>");
688 						}
689 
690 						if(end == -1) {
691 							if(strict)
692 								throw new MarkupException("Unclosed CDATA section");
693 							end = pos;
694 							cdataEnd = pos;
695 						} else {
696 							cdataEnd = pos + end;
697 							pos = cdataEnd + 3;
698 						}
699 
700 						return Ele(0, new TextNode(this, data[cdataStart .. cdataEnd]), null);
701 					} else {
702 						auto start = pos;
703 						while(pos < data.length && data[pos] != '>')
704 							pos++;
705 
706 						auto bangEnds = pos;
707 						if(pos == data.length) {
708 							if(strict)
709 								throw new MarkupException("unclosed processing instruction (<!xxx>)");
710 						} else pos++; // skipping the >
711 
712 						if(parseSawBangInstruction !is null)
713 							if(parseSawBangInstruction(data[start .. bangEnds])) {
714 								// FIXME: these should be able to modify the parser state,
715 								// doing things like adding entities, somehow.
716 
717 								return Ele(3, new BangInstruction(this, data[start .. bangEnds]), null);
718 							}
719 					}
720 
721 					/*
722 					if(pos < data.length && data[pos] == '>')
723 						pos++; // skip the >
724 					else
725 						assert(!strict);
726 					*/
727 				break;
728 				case '%':
729 				case '?':
730 					/*
731 						Here's what we want to support:
732 
733 						<% asp code %>
734 						<%= asp code %>
735 						<?php php code ?>
736 						<?= php code ?>
737 
738 						The contents don't really matter, just if it opens with
739 						one of the above for, it ends on the two char terminator.
740 
741 						<?something>
742 							this is NOT php code
743 							because I've seen this in the wild: <?EM-dummyText>
744 
745 							This could be php with shorttags which would be cut off
746 							prematurely because if(a >) - that > counts as the close
747 							of the tag, but since dom.d can't tell the difference
748 							between that and the <?EM> real world example, it will
749 							not try to look for the ?> ending.
750 
751 						The difference between this and the asp/php stuff is that it
752 						ends on >, not ?>. ONLY <?php or <?= ends on ?>. The rest end
753 						on >.
754 					*/
755 
756 					char end = data[pos];
757 					auto started = pos;
758 					bool isAsp = end == '%';
759 					int currentIndex = 0;
760 					bool isPhp = false;
761 					bool isEqualTag = false;
762 					int phpCount = 0;
763 
764 				    more:
765 					pos++; // skip the start
766 					if(pos == data.length) {
767 						if(strict)
768 							throw new MarkupException("Unclosed <"~end~" by end of file");
769 					} else {
770 						currentIndex++;
771 						if(currentIndex == 1 && data[pos] == '=') {
772 							if(!isAsp)
773 								isPhp = true;
774 							isEqualTag = true;
775 							goto more;
776 						}
777 						if(currentIndex == 1 && data[pos] == 'p')
778 							phpCount++;
779 						if(currentIndex == 2 && data[pos] == 'h')
780 							phpCount++;
781 						if(currentIndex == 3 && data[pos] == 'p' && phpCount == 2)
782 							isPhp = true;
783 
784 						if(data[pos] == '>') {
785 							if((isAsp || isPhp) && data[pos - 1] != end)
786 								goto more;
787 							// otherwise we're done
788 						} else
789 							goto more;
790 					}
791 
792 					//writefln("%s: %s", isAsp ? "ASP" : isPhp ? "PHP" : "<? ", data[started .. pos]);
793 					auto code = data[started .. pos];
794 
795 
796 					assert((pos < data.length && data[pos] == '>') || (!strict && pos == data.length));
797 					if(pos < data.length)
798 						pos++; // get past the >
799 
800 					if(isAsp && parseSawAspCode !is null) {
801 						if(parseSawAspCode(code)) {
802 							return Ele(3, new AspCode(this, code), null);
803 						}
804 					} else if(isPhp && parseSawPhpCode !is null) {
805 						if(parseSawPhpCode(code)) {
806 							return Ele(3, new PhpCode(this, code), null);
807 						}
808 					} else if(!isAsp && !isPhp && parseSawQuestionInstruction !is null) {
809 						if(parseSawQuestionInstruction(code)) {
810 							return Ele(3, new QuestionInstruction(this, code), null);
811 						}
812 					}
813 				break;
814 				case '/': // closing an element
815 					pos++; // skip the start
816 					auto p = pos;
817 					while(pos < data.length && data[pos] != '>')
818 						pos++;
819 					//writefln("</%s>", data[p..pos]);
820 					if(pos == data.length && data[pos-1] != '>') {
821 						if(strict)
822 							throw new MarkupException("File ended before closing tag had a required >");
823 						else
824 							data ~= ">"; // just hack it in
825 					}
826 					pos++; // skip the '>'
827 
828 					string tname = data[p..pos-1];
829 					if(!caseSensitive)
830 						tname = tname.toLower();
831 
832 				return Ele(1, null, tname); // closing tag reports itself here
833 				case ' ': // assume it isn't a real element...
834 					if(strict) {
835 						parseError("bad markup - improperly placed <");
836 						assert(0); // parseError always throws
837 					} else
838 						return Ele(0, TextNode.fromUndecodedString(this, "<"), null);
839 				default:
840 
841 					if(!strict) {
842 						// what about something that kinda looks like a tag, but isn't?
843 						auto nextTag = data[pos .. $].indexOf("<");
844 						auto closeTag = data[pos .. $].indexOf(">");
845 						if(closeTag != -1 && nextTag != -1)
846 							if(nextTag < closeTag) {
847 								// since attribute names cannot possibly have a < in them, we'll look for an equal since it might be an attribute value... and even in garbage mode, it'd have to be a quoted one realistically
848 
849 								auto equal = data[pos .. $].indexOf("=\"");
850 								if(equal != -1 && equal < closeTag) {
851 									// this MIGHT be ok, soldier on
852 								} else {
853 									// definitely no good, this must be a (horribly distorted) text node
854 									pos++; // skip the < we're on - don't want text node to end prematurely
855 									auto node = readTextNode();
856 									node.contents = "<" ~ node.contents; // put this back
857 									return Ele(0, node, null);
858 								}
859 							}
860 					}
861 
862 					string tagName = readTagName();
863 					string[string] attributes;
864 
865 					Ele addTag(bool selfClosed) {
866 						if(selfClosed)
867 							pos++;
868 						else {
869 							if(!strict)
870 								if(tagName.isInArray(selfClosedElements))
871 									// these are de-facto self closed
872 									selfClosed = true;
873 						}
874 
875 						import std.algorithm.comparison;
876 
877 						if(strict) {
878 						enforce(data[pos] == '>', format("got %s when expecting > (possible missing attribute name)\nContext:\n%s", data[pos], data[max(0, pos - 100) .. min(data.length, pos + 100)]));
879 						} else {
880 							// if we got here, it's probably because a slash was in an
881 							// unquoted attribute - don't trust the selfClosed value
882 							if(!selfClosed)
883 								selfClosed = tagName.isInArray(selfClosedElements);
884 
885 							while(pos < data.length && data[pos] != '>')
886 								pos++;
887 
888 							if(pos >= data.length) {
889 								// the tag never closed
890 								assert(data.length != 0);
891 								pos = data.length - 1; // rewinding so it hits the end at the bottom..
892 							}
893 						}
894 
895 						auto whereThisTagStarted = pos; // for better error messages
896 
897 						pos++;
898 
899 						auto e = createElement(tagName);
900 						e.attributes = attributes;
901 						version(dom_node_indexes) {
902 							if(e.dataset.nodeIndex.length == 0)
903 								e.dataset.nodeIndex = to!string(&(e.attributes));
904 						}
905 						e.selfClosed = selfClosed;
906 						e.parseAttributes();
907 
908 
909 						// HACK to handle script and style as a raw data section as it is in HTML browsers
910 						if(tagName == "script" || tagName == "style") {
911 							if(!selfClosed) {
912 								string closer = "</" ~ tagName ~ ">";
913 								ptrdiff_t ending;
914 								if(pos >= data.length)
915 									ending = -1;
916 								else
917 									ending = indexOf(data[pos..$], closer);
918 
919 								ending = indexOf(data[pos..$], closer, 0, (loose ? CaseSensitive.no : CaseSensitive.yes));
920 								/*
921 								if(loose && ending == -1 && pos < data.length)
922 									ending = indexOf(data[pos..$], closer.toUpper());
923 								*/
924 								if(ending == -1) {
925 									if(strict)
926 										throw new Exception("tag " ~ tagName ~ " never closed");
927 									else {
928 										// let's call it totally empty and do the rest of the file as text. doing it as html could still result in some weird stuff like if(a<4) being read as <4 being a tag so it comes out if(a<4></4> and other weirdness) It is either a closed script tag or the rest of the file is forfeit.
929 										if(pos < data.length) {
930 											e = new TextNode(this, data[pos .. $]);
931 											pos = data.length;
932 										}
933 									}
934 								} else {
935 									ending += pos;
936 									e.innerRawSource = data[pos..ending];
937 									pos = ending + closer.length;
938 								}
939 							}
940 							return Ele(0, e, null);
941 						}
942 
943 						bool closed = selfClosed;
944 
945 						void considerHtmlParagraphHack(Element n) {
946 							assert(!strict);
947 							if(e.tagName == "p" && e.tagName == n.tagName) {
948 								// html lets you write <p> para 1 <p> para 1
949 								// but in the dom tree, they should be siblings, not children.
950 								paragraphHackfixRequired = true;
951 							}
952 						}
953 
954 						//writef("<%s>", tagName);
955 						while(!closed) {
956 							Ele n;
957 							if(strict)
958 								n = readElement();
959 							else
960 								n = readElement(parentChain ~ tagName);
961 
962 							if(n.type == 4) return n; // the document is empty
963 
964 							if(n.type == 3 && n.element !is null) {
965 								// special node, append if possible
966 								if(e !is null)
967 									e.appendChild(n.element);
968 								else
969 									piecesBeforeRoot ~= n.element;
970 							} else if(n.type == 0) {
971 								if(!strict)
972 									considerHtmlParagraphHack(n.element);
973 								e.appendChild(n.element);
974 							} else if(n.type == 1) {
975 								bool found = false;
976 								if(n.payload != tagName) {
977 									if(strict)
978 										parseError(format("mismatched tag: </%s> != <%s> (opened on line %d)", n.payload, tagName, getLineNumber(whereThisTagStarted)));
979 									else {
980 										sawImproperNesting = true;
981 										// this is so we don't drop several levels of awful markup
982 										if(n.element) {
983 											if(!strict)
984 												considerHtmlParagraphHack(n.element);
985 											e.appendChild(n.element);
986 											n.element = null;
987 										}
988 
989 										// is the element open somewhere up the chain?
990 										foreach(i, parent; parentChain)
991 											if(parent == n.payload) {
992 												recentAutoClosedTags ~= tagName;
993 												// just rotating it so we don't inadvertently break stuff with vile crap
994 												if(recentAutoClosedTags.length > 4)
995 													recentAutoClosedTags = recentAutoClosedTags[1 .. $];
996 
997 												n.element = e;
998 												return n;
999 											}
1000 
1001 										// if not, this is a text node; we can't fix it up...
1002 
1003 										// If it's already in the tree somewhere, assume it is closed by algorithm
1004 										// and we shouldn't output it - odds are the user just flipped a couple tags
1005 										foreach(ele; e.tree) {
1006 											if(ele.tagName == n.payload) {
1007 												found = true;
1008 												break;
1009 											}
1010 										}
1011 
1012 										foreach(ele; recentAutoClosedTags) {
1013 											if(ele == n.payload) {
1014 												found = true;
1015 												break;
1016 											}
1017 										}
1018 
1019 										if(!found) // if not found in the tree though, it's probably just text
1020 										e.appendChild(TextNode.fromUndecodedString(this, "</"~n.payload~">"));
1021 									}
1022 								} else {
1023 									if(n.element) {
1024 										if(!strict)
1025 											considerHtmlParagraphHack(n.element);
1026 										e.appendChild(n.element);
1027 									}
1028 								}
1029 
1030 								if(n.payload == tagName) // in strict mode, this is always true
1031 									closed = true;
1032 							} else { /*throw new Exception("wtf " ~ tagName);*/ }
1033 						}
1034 						//writef("</%s>\n", tagName);
1035 						return Ele(0, e, null);
1036 					}
1037 
1038 					// if a tag was opened but not closed by end of file, we can arrive here
1039 					if(!strict && pos >= data.length)
1040 						return addTag(false);
1041 					//else if(strict) assert(0); // should be caught before
1042 
1043 					switch(data[pos]) {
1044 						default: assert(0);
1045 						case '/': // self closing tag
1046 							return addTag(true);
1047 						case '>':
1048 							return addTag(false);
1049 						case ' ':
1050 						case '\t':
1051 						case '\n':
1052 						case '\r':
1053 							// there might be attributes...
1054 							moreAttributes:
1055 							eatWhitespace();
1056 
1057 							// same deal as above the switch....
1058 							if(!strict && pos >= data.length)
1059 								return addTag(false);
1060 
1061 							if(strict && pos >= data.length)
1062 								throw new MarkupException("tag open, didn't find > before end of file");
1063 
1064 							switch(data[pos]) {
1065 								case '/': // self closing tag
1066 									return addTag(true);
1067 								case '>': // closed tag; open -- we now read the contents
1068 									return addTag(false);
1069 								default: // it is an attribute
1070 									string attrName = readAttributeName();
1071 									string attrValue = attrName;
1072 
1073 									bool ateAny = eatWhitespace();
1074 									if(strict && ateAny)
1075 										throw new MarkupException("inappropriate whitespace after attribute name");
1076 
1077 									if(pos >= data.length) {
1078 										if(strict)
1079 											assert(0, "this should have thrown in readAttributeName");
1080 										else {
1081 											data ~= ">";
1082 											goto blankValue;
1083 										}
1084 									}
1085 									if(data[pos] == '=') {
1086 										pos++;
1087 
1088 										ateAny = eatWhitespace();
1089 										// the spec actually allows this!
1090 										//if(strict && ateAny)
1091 											//throw new MarkupException("inappropriate whitespace after attribute equals");
1092 
1093 										attrValue = readAttributeValue();
1094 
1095 										eatWhitespace();
1096 									}
1097 
1098 									blankValue:
1099 
1100 									if(strict && attrName in attributes)
1101 										throw new MarkupException("Repeated attribute: " ~ attrName);
1102 
1103 									if(attrName.strip().length)
1104 										attributes[attrName] = attrValue;
1105 									else if(strict) throw new MarkupException("wtf, zero length attribute name");
1106 
1107 									if(!strict && pos < data.length && data[pos] == '<') {
1108 										// this is the broken tag that doesn't have a > at the end
1109 										data = data[0 .. pos] ~ ">" ~ data[pos.. $];
1110 										// let's insert one as a hack
1111 										goto case '>';
1112 									}
1113 
1114 									goto moreAttributes;
1115 							}
1116 					}
1117 			}
1118 
1119 			return Ele(2, null, null); // this is a <! or <? thing that got ignored prolly.
1120 			//assert(0);
1121 		}
1122 
1123 		eatWhitespace();
1124 		Ele r;
1125 		do {
1126 			r = readElement(); // there SHOULD only be one element...
1127 
1128 			if(r.type == 3 && r.element !is null)
1129 				piecesBeforeRoot ~= r.element;
1130 
1131 			if(r.type == 4)
1132 				break; // the document is completely empty...
1133 		} while (r.type != 0 || r.element.nodeType != 1); // we look past the xml prologue and doctype; root only begins on a regular node
1134 
1135 		root = r.element;
1136 		if(root !is null)
1137 			root.parent_ = this;
1138 
1139 		if(!strict) // in strict mode, we'll just ignore stuff after the xml
1140 		while(r.type != 4) {
1141 			r = readElement();
1142 			if(r.type != 4 && r.type != 2) { // if not empty and not ignored
1143 				if(r.element !is null)
1144 					piecesAfterRoot ~= r.element;
1145 			}
1146 		}
1147 
1148 		if(root is null)
1149 		{
1150 			if(strict)
1151 				assert(0, "empty document should be impossible in strict mode");
1152 			else
1153 				parseUtf8(`<html><head></head><body></body></html>`); // fill in a dummy document in loose mode since that's what browsers do
1154 		}
1155 
1156 		if(paragraphHackfixRequired) {
1157 			assert(!strict); // this should never happen in strict mode; it ought to never set the hack flag...
1158 
1159 			// in loose mode, we can see some "bad" nesting (it's valid html, but poorly formed xml).
1160 			// It's hard to handle above though because my code sucks. So, we'll fix it here.
1161 
1162 			// Where to insert based on the parent (for mixed closed/unclosed <p> tags). See #120
1163 			// Kind of inefficient because we can't detect when we recurse back out of a node.
1164 			Element[Element] insertLocations;
1165 			auto iterator = root.tree;
1166 			foreach(ele; iterator) {
1167 				if(ele.parentNode is null)
1168 					continue;
1169 
1170 				if(ele.tagName == "p" && ele.parentNode.tagName == ele.tagName) {
1171 					auto shouldBePreviousSibling = ele.parentNode;
1172 					auto holder = shouldBePreviousSibling.parentNode; // this is the two element's mutual holder...
1173 					if (auto p = holder in insertLocations) {
1174 						shouldBePreviousSibling = *p;
1175 						assert(shouldBePreviousSibling.parentNode is holder);
1176 					}
1177 					ele = holder.insertAfter(shouldBePreviousSibling, ele.removeFromTree());
1178 					insertLocations[holder] = ele;
1179 					iterator.currentKilled(); // the current branch can be skipped; we'll hit it soon anyway since it's now next up.
1180 				}
1181 			}
1182 		}
1183 	}
1184 
1185 	/* end massive parse function */
1186 
1187 	/// Gets the <title> element's innerText, if one exists
1188 	@property string title() {
1189 		bool doesItMatch(Element e) {
1190 			return (e.tagName == "title");
1191 		}
1192 
1193 		auto e = findFirst(&doesItMatch);
1194 		if(e)
1195 			return e.innerText();
1196 		return "";
1197 	}
1198 
1199 	/// Sets the title of the page, creating a <title> element if needed.
1200 	@property void title(string t) {
1201 		bool doesItMatch(Element e) {
1202 			return (e.tagName == "title");
1203 		}
1204 
1205 		auto e = findFirst(&doesItMatch);
1206 
1207 		if(!e) {
1208 			e = createElement("title");
1209 			auto heads = getElementsByTagName("head");
1210 			if(heads.length)
1211 				heads[0].appendChild(e);
1212 		}
1213 
1214 		if(e)
1215 			e.innerText = t;
1216 	}
1217 
1218 	// FIXME: would it work to alias root this; ???? might be a good idea
1219 	/// These functions all forward to the root element. See the documentation in the Element class.
1220 	Element getElementById(string id) {
1221 		return root.getElementById(id);
1222 	}
1223 
1224 	/// ditto
1225 	final SomeElementType requireElementById(SomeElementType = Element)(string id, string file = __FILE__, size_t line = __LINE__)
1226 		if( is(SomeElementType : Element))
1227 		out(ret) { assert(ret !is null); }
1228 	do {
1229 		return root.requireElementById!(SomeElementType)(id, file, line);
1230 	}
1231 
1232 	/// ditto
1233 	final SomeElementType requireSelector(SomeElementType = Element)(string selector, string file = __FILE__, size_t line = __LINE__)
1234 		if( is(SomeElementType : Element))
1235 		out(ret) { assert(ret !is null); }
1236 	do {
1237 		auto e = cast(SomeElementType) querySelector(selector);
1238 		if(e is null)
1239 			throw new ElementNotFoundException(SomeElementType.stringof, selector, this.root, file, line);
1240 		return e;
1241 	}
1242 
1243 	/// ditto
1244 	final MaybeNullElement!SomeElementType optionSelector(SomeElementType = Element)(string selector, string file = __FILE__, size_t line = __LINE__)
1245 		if(is(SomeElementType : Element))
1246 	{
1247 		auto e = cast(SomeElementType) querySelector(selector);
1248 		return MaybeNullElement!SomeElementType(e);
1249 	}
1250 
1251 	/// ditto
1252 	@scriptable
1253 	Element querySelector(string selector) {
1254 		// see comment below on Document.querySelectorAll
1255 		auto s = Selector(selector);//, !loose);
1256 		foreach(ref comp; s.components)
1257 			if(comp.parts.length && comp.parts[0].separation == 0)
1258 				comp.parts[0].separation = -1;
1259 		foreach(e; s.getMatchingElementsLazy(this.root))
1260 			return e;
1261 		return null;
1262 
1263 	}
1264 
1265 	/// ditto
1266 	@scriptable
1267 	Element[] querySelectorAll(string selector) {
1268 		// In standards-compliant code, the document is slightly magical
1269 		// in that it is a pseudoelement at top level. It should actually
1270 		// match the root as one of its children.
1271 		//
1272 		// In versions of dom.d before Dec 29 2019, this worked because
1273 		// querySelectorAll was willing to return itself. With that bug fix
1274 		// (search "arbitrary id asduiwh" in this file for associated unittest)
1275 		// this would have failed. Hence adding back the root if it matches the
1276 		// selector itself.
1277 		//
1278 		// I'd love to do this better later.
1279 
1280 		auto s = Selector(selector);//, !loose);
1281 		foreach(ref comp; s.components)
1282 			if(comp.parts.length && comp.parts[0].separation == 0)
1283 				comp.parts[0].separation = -1;
1284 		return s.getMatchingElements(this.root);
1285 	}
1286 
1287 	/// ditto
1288 	deprecated("use querySelectorAll instead")
1289 	Element[] getElementsBySelector(string selector) {
1290 		return root.getElementsBySelector(selector);
1291 	}
1292 
1293 	/// ditto
1294 	@scriptable
1295 	Element[] getElementsByTagName(string tag) {
1296 		return root.getElementsByTagName(tag);
1297 	}
1298 
1299 	/// ditto
1300 	@scriptable
1301 	Element[] getElementsByClassName(string tag) {
1302 		return root.getElementsByClassName(tag);
1303 	}
1304 
1305 	/** FIXME: btw, this could just be a lazy range...... */
1306 	Element getFirstElementByTagName(string tag) {
1307 		if(loose)
1308 			tag = tag.toLower();
1309 		bool doesItMatch(Element e) {
1310 			return e.tagName == tag;
1311 		}
1312 		return findFirst(&doesItMatch);
1313 	}
1314 
1315 	/// This returns the <body> element, if there is one. (It different than Javascript, where it is called 'body', because body is a keyword in D.)
1316 	Element mainBody() {
1317 		return getFirstElementByTagName("body");
1318 	}
1319 
1320 	/// this uses a weird thing... it's [name=] if no colon and
1321 	/// [property=] if colon
1322 	string getMeta(string name) {
1323 		string thing = name.indexOf(":") == -1 ? "name" : "property";
1324 		auto e = querySelector("head meta["~thing~"="~name~"]");
1325 		if(e is null)
1326 			return null;
1327 		return e.content;
1328 	}
1329 
1330 	/// Sets a meta tag in the document header. It is kinda hacky to work easily for both Facebook open graph and traditional html meta tags/
1331 	void setMeta(string name, string value) {
1332 		string thing = name.indexOf(":") == -1 ? "name" : "property";
1333 		auto e = querySelector("head meta["~thing~"="~name~"]");
1334 		if(e is null) {
1335 			e = requireSelector("head").addChild("meta");
1336 			e.setAttribute(thing, name);
1337 		}
1338 
1339 		e.content = value;
1340 	}
1341 
1342 	///.
1343 	Form[] forms() {
1344 		return cast(Form[]) getElementsByTagName("form");
1345 	}
1346 
1347 	///.
1348 	Form createForm()
1349 		out(ret) {
1350 			assert(ret !is null);
1351 		}
1352 	do {
1353 		return cast(Form) createElement("form");
1354 	}
1355 
1356 	///.
1357 	Element createElement(string name) {
1358 		if(loose)
1359 			name = name.toLower();
1360 
1361 		auto e = Element.make(name, null, null, selfClosedElements);
1362 
1363 		return e;
1364 
1365 //		return new Element(this, name, null, selfClosed);
1366 	}
1367 
1368 	///.
1369 	Element createFragment() {
1370 		return new DocumentFragment(this);
1371 	}
1372 
1373 	///.
1374 	Element createTextNode(string content) {
1375 		return new TextNode(this, content);
1376 	}
1377 
1378 
1379 	///.
1380 	Element findFirst(bool delegate(Element) doesItMatch) {
1381 		if(root is null)
1382 			return null;
1383 		Element result;
1384 
1385 		bool goThroughElement(Element e) {
1386 			if(doesItMatch(e)) {
1387 				result = e;
1388 				return true;
1389 			}
1390 
1391 			foreach(child; e.children) {
1392 				if(goThroughElement(child))
1393 					return true;
1394 			}
1395 
1396 			return false;
1397 		}
1398 
1399 		goThroughElement(root);
1400 
1401 		return result;
1402 	}
1403 
1404 	///.
1405 	void clear() {
1406 		root = null;
1407 		loose = false;
1408 	}
1409 
1410 	///.
1411 	void setProlog(string d) {
1412 		_prolog = d;
1413 		prologWasSet = true;
1414 	}
1415 
1416 	///.
1417 	private string _prolog = "<!DOCTYPE html>\n";
1418 	private bool prologWasSet = false; // set to true if the user changed it
1419 
1420 	@property string prolog() const {
1421 		// if the user explicitly changed it, do what they want
1422 		// or if we didn't keep/find stuff from the document itself,
1423 		// we'll use the builtin one as a default.
1424 		if(prologWasSet || piecesBeforeRoot.length == 0)
1425 			return _prolog;
1426 
1427 		string p;
1428 		foreach(e; piecesBeforeRoot)
1429 			p ~= e.toString() ~ "\n";
1430 		return p;
1431 	}
1432 
1433 	///.
1434 	override string toString() const {
1435 		return prolog ~ root.toString();
1436 	}
1437 
1438 	/++
1439 		Writes it out with whitespace for easier eyeball debugging
1440 
1441 		Do NOT use for anything other than eyeball debugging,
1442 		because whitespace may be significant content in XML.
1443 	+/
1444 	string toPrettyString(bool insertComments = false, int indentationLevel = 0, string indentWith = "\t") const {
1445 		import std.string;
1446 		string s = prolog.strip;
1447 
1448 		/*
1449 		if(insertComments) s ~= "<!--";
1450 		s ~= "\n";
1451 		if(insertComments) s ~= "-->";
1452 		*/
1453 
1454 		s ~= root.toPrettyStringImpl(insertComments, indentationLevel, indentWith);
1455 		foreach(a; piecesAfterRoot)
1456 			s ~= a.toPrettyStringImpl(insertComments, indentationLevel, indentWith);
1457 		return s;
1458 	}
1459 
1460 	///.
1461 	Element root;
1462 
1463 	/// if these were kept, this is stuff that appeared before the root element, such as <?xml version ?> decls and <!DOCTYPE>s
1464 	Element[] piecesBeforeRoot;
1465 
1466 	/// stuff after the root, only stored in non-strict mode and not used in toString, but available in case you want it
1467 	Element[] piecesAfterRoot;
1468 
1469 	///.
1470 	bool loose;
1471 
1472 
1473 
1474 	// what follows are for mutation events that you can observe
1475 	void delegate(DomMutationEvent)[] eventObservers;
1476 
1477 	void dispatchMutationEvent(DomMutationEvent e) {
1478 		foreach(o; eventObservers)
1479 			o(e);
1480 	}
1481 }
1482 
1483 interface DomParent {
1484 	inout(Document) asDocument() inout;
1485 	inout(Element) asElement() inout;
1486 }
1487 
1488 /// This represents almost everything in the DOM.
1489 /// Group: core_functionality
1490 class Element : DomParent {
1491 	inout(Document) asDocument() inout { return null; }
1492 	inout(Element) asElement() inout { return this; }
1493 
1494 	/// Returns a collection of elements by selector.
1495 	/// See: [Document.opIndex]
1496 	ElementCollection opIndex(string selector) {
1497 		auto e = ElementCollection(this);
1498 		return e[selector];
1499 	}
1500 
1501 	/++
1502 		Returns the child node with the particular index.
1503 
1504 		Be aware that child nodes include text nodes, including
1505 		whitespace-only nodes.
1506 	+/
1507 	Element opIndex(size_t index) {
1508 		if(index >= children.length)
1509 			return null;
1510 		return this.children[index];
1511 	}
1512 
1513 	/// Calls getElementById, but throws instead of returning null if the element is not found. You can also ask for a specific subclass of Element to dynamically cast to, which also throws if it cannot be done.
1514 	final SomeElementType requireElementById(SomeElementType = Element)(string id, string file = __FILE__, size_t line = __LINE__)
1515 	if(
1516 		is(SomeElementType : Element)
1517 	)
1518 	out(ret) {
1519 		assert(ret !is null);
1520 	}
1521 	do {
1522 		auto e = cast(SomeElementType) getElementById(id);
1523 		if(e is null)
1524 			throw new ElementNotFoundException(SomeElementType.stringof, "id=" ~ id, this, file, line);
1525 		return e;
1526 	}
1527 
1528 	/// ditto but with selectors instead of ids
1529 	final SomeElementType requireSelector(SomeElementType = Element)(string selector, string file = __FILE__, size_t line = __LINE__)
1530 	if(
1531 		is(SomeElementType : Element)
1532 	)
1533 	out(ret) {
1534 		assert(ret !is null);
1535 	}
1536 	do {
1537 		auto e = cast(SomeElementType) querySelector(selector);
1538 		if(e is null)
1539 			throw new ElementNotFoundException(SomeElementType.stringof, selector, this, file, line);
1540 		return e;
1541 	}
1542 
1543 
1544 	/++
1545 		If a matching selector is found, it returns that Element. Otherwise, the returned object returns null for all methods.
1546 	+/
1547 	final MaybeNullElement!SomeElementType optionSelector(SomeElementType = Element)(string selector, string file = __FILE__, size_t line = __LINE__)
1548 		if(is(SomeElementType : Element))
1549 	{
1550 		auto e = cast(SomeElementType) querySelector(selector);
1551 		return MaybeNullElement!SomeElementType(e);
1552 	}
1553 
1554 
1555 
1556 	/// get all the classes on this element
1557 	@property string[] classes() {
1558 		return split(className, " ");
1559 	}
1560 
1561 	/// Adds a string to the class attribute. The class attribute is used a lot in CSS.
1562 	@scriptable
1563 	Element addClass(string c) {
1564 		if(hasClass(c))
1565 			return this; // don't add it twice
1566 
1567 		string cn = getAttribute("class");
1568 		if(cn.length == 0) {
1569 			setAttribute("class", c);
1570 			return this;
1571 		} else {
1572 			setAttribute("class", cn ~ " " ~ c);
1573 		}
1574 
1575 		return this;
1576 	}
1577 
1578 	/// Removes a particular class name.
1579 	@scriptable
1580 	Element removeClass(string c) {
1581 		if(!hasClass(c))
1582 			return this;
1583 		string n;
1584 		foreach(name; classes) {
1585 			if(c == name)
1586 				continue; // cut it out
1587 			if(n.length)
1588 				n ~= " ";
1589 			n ~= name;
1590 		}
1591 
1592 		className = n.strip();
1593 
1594 		return this;
1595 	}
1596 
1597 	/// Returns whether the given class appears in this element.
1598 	bool hasClass(string c) {
1599 		string cn = className;
1600 
1601 		auto idx = cn.indexOf(c);
1602 		if(idx == -1)
1603 			return false;
1604 
1605 		foreach(cla; cn.split(" "))
1606 			if(cla == c)
1607 				return true;
1608 		return false;
1609 
1610 		/*
1611 		int rightSide = idx + c.length;
1612 
1613 		bool checkRight() {
1614 			if(rightSide == cn.length)
1615 				return true; // it's the only class
1616 			else if(iswhite(cn[rightSide]))
1617 				return true;
1618 			return false; // this is a substring of something else..
1619 		}
1620 
1621 		if(idx == 0) {
1622 			return checkRight();
1623 		} else {
1624 			if(!iswhite(cn[idx - 1]))
1625 				return false; // substring
1626 			return checkRight();
1627 		}
1628 
1629 		assert(0);
1630 		*/
1631 	}
1632 
1633 
1634 	/* *******************************
1635 		  DOM Mutation
1636 	*********************************/
1637 	/// convenience function to quickly add a tag with some text or
1638 	/// other relevant info (for example, it's a src for an <img> element
1639 	/// instead of inner text)
1640 	Element addChild(string tagName, string childInfo = null, string childInfo2 = null)
1641 		in {
1642 			assert(tagName !is null);
1643 		}
1644 		out(e) {
1645 			//assert(e.parentNode is this);
1646 			//assert(e.parentDocument is this.parentDocument);
1647 		}
1648 	do {
1649 		auto e = Element.make(tagName, childInfo, childInfo2);
1650 		// FIXME (maybe): if the thing is self closed, we might want to go ahead and
1651 		// return the parent. That will break existing code though.
1652 		return appendChild(e);
1653 	}
1654 
1655 	/// Another convenience function. Adds a child directly after the current one, returning
1656 	/// the new child.
1657 	///
1658 	/// Between this, addChild, and parentNode, you can build a tree as a single expression.
1659 	Element addSibling(string tagName, string childInfo = null, string childInfo2 = null)
1660 		in {
1661 			assert(tagName !is null);
1662 			assert(parentNode !is null);
1663 		}
1664 		out(e) {
1665 			assert(e.parentNode is this.parentNode);
1666 			assert(e.parentDocument is this.parentDocument);
1667 		}
1668 	do {
1669 		auto e = Element.make(tagName, childInfo, childInfo2);
1670 		return parentNode.insertAfter(this, e);
1671 	}
1672 
1673 	///
1674 	Element addSibling(Element e) {
1675 		return parentNode.insertAfter(this, e);
1676 	}
1677 
1678 	///
1679 	Element addChild(Element e) {
1680 		return this.appendChild(e);
1681 	}
1682 
1683 	/// Convenience function to append text intermixed with other children.
1684 	/// For example: div.addChildren("You can visit my website by ", new Link("mysite.com", "clicking here"), ".");
1685 	/// or div.addChildren("Hello, ", user.name, "!");
1686 
1687 	/// See also: appendHtml. This might be a bit simpler though because you don't have to think about escaping.
1688 	void addChildren(T...)(T t) {
1689 		foreach(item; t) {
1690 			static if(is(item : Element))
1691 				appendChild(item);
1692 			else static if (is(isSomeString!(item)))
1693 				appendText(to!string(item));
1694 			else static assert(0, "Cannot pass " ~ typeof(item).stringof ~ " to addChildren");
1695 		}
1696 	}
1697 
1698 	///.
1699 	Element addChild(string tagName, Element firstChild, string info2 = null)
1700 	in {
1701 		assert(firstChild !is null);
1702 	}
1703 	out(ret) {
1704 		assert(ret !is null);
1705 		assert(ret.parentNode is this);
1706 		assert(firstChild.parentNode is ret);
1707 
1708 		assert(ret.parentDocument is this.parentDocument);
1709 		//assert(firstChild.parentDocument is this.parentDocument);
1710 	}
1711 	do {
1712 		auto e = Element.make(tagName, "", info2);
1713 		e.appendChild(firstChild);
1714 		this.appendChild(e);
1715 		return e;
1716 	}
1717 
1718 	///
1719 	Element addChild(string tagName, in Html innerHtml, string info2 = null)
1720 	in {
1721 	}
1722 	out(ret) {
1723 		assert(ret !is null);
1724 		assert((cast(DocumentFragment) this !is null) || (ret.parentNode is this), ret.toString);// e.parentNode ? e.parentNode.toString : "null");
1725 		assert(ret.parentDocument is this.parentDocument);
1726 	}
1727 	do {
1728 		auto e = Element.make(tagName, "", info2);
1729 		this.appendChild(e);
1730 		e.innerHTML = innerHtml.source;
1731 		return e;
1732 	}
1733 
1734 
1735 	/// .
1736 	void appendChildren(Element[] children) {
1737 		foreach(ele; children)
1738 			appendChild(ele);
1739 	}
1740 
1741 	///.
1742 	void reparent(Element newParent)
1743 		in {
1744 			assert(newParent !is null);
1745 			assert(parentNode !is null);
1746 		}
1747 		out {
1748 			assert(this.parentNode is newParent);
1749 			//assert(isInArray(this, newParent.children));
1750 		}
1751 	do {
1752 		parentNode.removeChild(this);
1753 		newParent.appendChild(this);
1754 	}
1755 
1756 	/**
1757 		Strips this tag out of the document, putting its inner html
1758 		as children of the parent.
1759 
1760 		For example, given: `<p>hello <b>there</b></p>`, if you
1761 		call `stripOut` on the `b` element, you'll be left with
1762 		`<p>hello there<p>`.
1763 
1764 		The idea here is to make it easy to get rid of garbage
1765 		markup you aren't interested in.
1766 	*/
1767 	void stripOut()
1768 		in {
1769 			assert(parentNode !is null);
1770 		}
1771 		out {
1772 			assert(parentNode is null);
1773 			assert(children.length == 0);
1774 		}
1775 	do {
1776 		foreach(c; children)
1777 			c.parentNode = null; // remove the parent
1778 		if(children.length)
1779 			parentNode.replaceChild(this, this.children);
1780 		else
1781 			parentNode.removeChild(this);
1782 		this.children.length = 0; // we reparented them all above
1783 	}
1784 
1785 	/// shorthand for `this.parentNode.removeChild(this)` with `parentNode` `null` check
1786 	/// if the element already isn't in a tree, it does nothing.
1787 	Element removeFromTree()
1788 		in {
1789 
1790 		}
1791 		out(var) {
1792 			assert(this.parentNode is null);
1793 			assert(var is this);
1794 		}
1795 	do {
1796 		if(this.parentNode is null)
1797 			return this;
1798 
1799 		this.parentNode.removeChild(this);
1800 
1801 		return this;
1802 	}
1803 
1804 	/++
1805 		Wraps this element inside the given element.
1806 		It's like `this.replaceWith(what); what.appendchild(this);`
1807 
1808 		Given: `<b>cool</b>`, if you call `b.wrapIn(new Link("site.com", "my site is "));`
1809 		you'll end up with: `<a href="site.com">my site is <b>cool</b></a>`.
1810 	+/
1811 	Element wrapIn(Element what)
1812 		in {
1813 			assert(what !is null);
1814 		}
1815 		out(ret) {
1816 			assert(this.parentNode is what);
1817 			assert(ret is what);
1818 		}
1819 	do {
1820 		this.replaceWith(what);
1821 		what.appendChild(this);
1822 
1823 		return what;
1824 	}
1825 
1826 	/// Replaces this element with something else in the tree.
1827 	Element replaceWith(Element e)
1828 	in {
1829 		assert(this.parentNode !is null);
1830 	}
1831 	do {
1832 		e.removeFromTree();
1833 		this.parentNode.replaceChild(this, e);
1834 		return e;
1835 	}
1836 
1837 	/**
1838 		Splits the className into an array of each class given
1839 	*/
1840 	string[] classNames() const {
1841 		return className().split(" ");
1842 	}
1843 
1844 	/**
1845 		Fetches the first consecutive text nodes concatenated together.
1846 
1847 
1848 		`firstInnerText` of `<example>some text<span>more text</span></example>` is `some text`. It stops at the first child tag encountered.
1849 
1850 		See_also: [directText], [innerText]
1851 	*/
1852 	string firstInnerText() const {
1853 		string s;
1854 		foreach(child; children) {
1855 			if(child.nodeType != NodeType.Text)
1856 				break;
1857 
1858 			s ~= child.nodeValue();
1859 		}
1860 		return s;
1861 	}
1862 
1863 
1864 	/**
1865 		Returns the text directly under this element.
1866 		
1867 
1868 		Unlike [innerText], it does not recurse, and unlike [firstInnerText], it continues
1869 		past child tags. So, `<example>some <b>bold</b> text</example>`
1870 		will return `some  text` because it only gets the text, skipping non-text children.
1871 
1872 		See_also: [firstInnerText], [innerText]
1873 	*/
1874 	@property string directText() {
1875 		string ret;
1876 		foreach(e; children) {
1877 			if(e.nodeType == NodeType.Text)
1878 				ret ~= e.nodeValue();
1879 		}
1880 
1881 		return ret;
1882 	}
1883 
1884 	/**
1885 		Sets the direct text, without modifying other child nodes.
1886 
1887 
1888 		Unlike [innerText], this does *not* remove existing elements in the element.
1889 
1890 		It only replaces the first text node it sees.
1891 
1892 		If there are no text nodes, it calls [appendText].
1893 
1894 		So, given `<div><img />text here</div>`, it will keep the `<img />`, and replace the `text here`.
1895 	*/
1896 	@property void directText(string text) {
1897 		foreach(e; children) {
1898 			if(e.nodeType == NodeType.Text) {
1899 				auto it = cast(TextNode) e;
1900 				it.contents = text;
1901 				return;
1902 			}
1903 		}
1904 
1905 		appendText(text);
1906 	}
1907 
1908 	// do nothing, this is primarily a virtual hook
1909 	// for links and forms
1910 	void setValue(string field, string value) { }
1911 
1912 
1913 	// this is a thing so i can remove observer support if it gets slow
1914 	// I have not implemented all these yet
1915 	private void sendObserverEvent(DomMutationOperations operation, string s1 = null, string s2 = null, Element r = null, Element r2 = null) {
1916 		if(parentDocument is null) return;
1917 		DomMutationEvent me;
1918 		me.operation = operation;
1919 		me.target = this;
1920 		me.relatedString = s1;
1921 		me.relatedString2 = s2;
1922 		me.related = r;
1923 		me.related2 = r2;
1924 		parentDocument.dispatchMutationEvent(me);
1925 	}
1926 
1927 	// putting all the members up front
1928 
1929 	// this ought to be private. don't use it directly.
1930 	Element[] children;
1931 
1932 	/// The name of the tag. Remember, changing this doesn't change the dynamic type of the object.
1933 	string tagName;
1934 
1935 	/// This is where the attributes are actually stored. You should use getAttribute, setAttribute, and hasAttribute instead.
1936 	string[string] attributes;
1937 
1938 	/// In XML, it is valid to write <tag /> for all elements with no children, but that breaks HTML, so I don't do it here.
1939 	/// Instead, this flag tells if it should be. It is based on the source document's notation and a html element list.
1940 	private bool selfClosed;
1941 
1942 	private DomParent parent_;
1943 
1944 	/// Get the parent Document object that contains this element.
1945 	/// It may be null, so remember to check for that.
1946 	@property inout(Document) parentDocument() inout {
1947 		if(this.parent_ is null)
1948 			return null;
1949 		auto p = cast() this.parent_.asElement;
1950 		auto prev = cast() this;
1951 		while(p) {
1952 			prev = p;
1953 			if(p.parent_ is null)
1954 				return null;
1955 			p = cast() p.parent_.asElement;
1956 		}
1957 		return cast(inout) prev.parent_.asDocument;
1958 	}
1959 
1960 	deprecated @property void parentDocument(Document doc) {
1961 		parent_ = doc;
1962 	}
1963 
1964 	///.
1965 	inout(Element) parentNode() inout {
1966 		if(parent_ is null)
1967 			return null;
1968 
1969 		auto p = parent_.asElement;
1970 
1971 		if(cast(DocumentFragment) p) {
1972 			if(p.parent_ is null)
1973 				return null;
1974 			else
1975 				return p.parent_.asElement;
1976 		}
1977 
1978 		return p;
1979 	}
1980 
1981 	//protected
1982 	Element parentNode(Element e) {
1983 		parent_ = e;
1984 		return e;
1985 	}
1986 
1987 	// these are here for event handlers. Don't forget that this library never fires events.
1988 	// (I'm thinking about putting this in a version statement so you don't have the baggage. The instance size of this class is 56 bytes right now.)
1989 
1990 	version(dom_with_events) {
1991 		EventHandler[][string] bubblingEventHandlers;
1992 		EventHandler[][string] capturingEventHandlers;
1993 		EventHandler[string] defaultEventHandlers;
1994 
1995 		void addEventListener(string event, EventHandler handler, bool useCapture = false) {
1996 			if(event.length > 2 && event[0..2] == "on")
1997 				event = event[2 .. $];
1998 
1999 			if(useCapture)
2000 				capturingEventHandlers[event] ~= handler;
2001 			else
2002 				bubblingEventHandlers[event] ~= handler;
2003 		}
2004 	}
2005 
2006 
2007 	// and now methods
2008 
2009 	/++
2010 		Convenience function to try to do the right thing for HTML. This is the main way I create elements.
2011 
2012 		History:
2013 			On February 8, 2021, the `selfClosedElements` parameter was added. Previously, it used a private
2014 			immutable global list for HTML. It still defaults to the same list, but you can change it now via
2015 			the parameter.
2016 	+/
2017 	static Element make(string tagName, string childInfo = null, string childInfo2 = null, const string[] selfClosedElements = htmlSelfClosedElements) {
2018 		bool selfClosed = tagName.isInArray(selfClosedElements);
2019 
2020 		Element e;
2021 		// want to create the right kind of object for the given tag...
2022 		switch(tagName) {
2023 			case "#text":
2024 				e = new TextNode(null, childInfo);
2025 				return e;
2026 			// break;
2027 			case "table":
2028 				e = new Table(null);
2029 			break;
2030 			case "a":
2031 				e = new Link(null);
2032 			break;
2033 			case "form":
2034 				e = new Form(null);
2035 			break;
2036 			case "tr":
2037 				e = new TableRow(null);
2038 			break;
2039 			case "td", "th":
2040 				e = new TableCell(null, tagName);
2041 			break;
2042 			default:
2043 				e = new Element(null, tagName, null, selfClosed); // parent document should be set elsewhere
2044 		}
2045 
2046 		// make sure all the stuff is constructed properly FIXME: should probably be in all the right constructors too
2047 		e.tagName = tagName;
2048 		e.selfClosed = selfClosed;
2049 
2050 		if(childInfo !is null)
2051 			switch(tagName) {
2052 				/* html5 convenience tags */
2053 				case "audio":
2054 					if(childInfo.length)
2055 						e.addChild("source", childInfo);
2056 					if(childInfo2 !is null)
2057 						e.appendText(childInfo2);
2058 				break;
2059 				case "source":
2060 					e.src = childInfo;
2061 					if(childInfo2 !is null)
2062 						e.type = childInfo2;
2063 				break;
2064 				/* regular html 4 stuff */
2065 				case "img":
2066 					e.src = childInfo;
2067 					if(childInfo2 !is null)
2068 						e.alt = childInfo2;
2069 				break;
2070 				case "link":
2071 					e.href = childInfo;
2072 					if(childInfo2 !is null)
2073 						e.rel = childInfo2;
2074 				break;
2075 				case "option":
2076 					e.innerText = childInfo;
2077 					if(childInfo2 !is null)
2078 						e.value = childInfo2;
2079 				break;
2080 				case "input":
2081 					e.type = "hidden";
2082 					e.name = childInfo;
2083 					if(childInfo2 !is null)
2084 						e.value = childInfo2;
2085 				break;
2086 				case "button":
2087 					e.innerText = childInfo;
2088 					if(childInfo2 !is null)
2089 						e.type = childInfo2;
2090 				break;
2091 				case "a":
2092 					e.innerText = childInfo;
2093 					if(childInfo2 !is null)
2094 						e.href = childInfo2;
2095 				break;
2096 				case "script":
2097 				case "style":
2098 					e.innerRawSource = childInfo;
2099 				break;
2100 				case "meta":
2101 					e.name = childInfo;
2102 					if(childInfo2 !is null)
2103 						e.content = childInfo2;
2104 				break;
2105 				/* generically, assume we were passed text and perhaps class */
2106 				default:
2107 					e.innerText = childInfo;
2108 					if(childInfo2.length)
2109 						e.className = childInfo2;
2110 			}
2111 
2112 		return e;
2113 	}
2114 
2115 	static Element make(string tagName, in Html innerHtml, string childInfo2 = null) {
2116 		// FIXME: childInfo2 is ignored when info1 is null
2117 		auto m = Element.make(tagName, "not null"[0..0], childInfo2);
2118 		m.innerHTML = innerHtml.source;
2119 		return m;
2120 	}
2121 
2122 	static Element make(string tagName, Element child, string childInfo2 = null) {
2123 		auto m = Element.make(tagName, cast(string) null, childInfo2);
2124 		m.appendChild(child);
2125 		return m;
2126 	}
2127 
2128 
2129 	/// Generally, you don't want to call this yourself - use Element.make or document.createElement instead.
2130 	this(Document _parentDocument, string _tagName, string[string] _attributes = null, bool _selfClosed = false) {
2131 		tagName = _tagName;
2132 		if(_attributes !is null)
2133 			attributes = _attributes;
2134 		selfClosed = _selfClosed;
2135 
2136 		version(dom_node_indexes)
2137 			this.dataset.nodeIndex = to!string(&(this.attributes));
2138 
2139 		assert(_tagName.indexOf(" ") == -1);//, "<" ~ _tagName ~ "> is invalid");
2140 	}
2141 
2142 	/++
2143 		Convenience constructor when you don't care about the parentDocument. Note this might break things on the document.
2144 		Note also that without a parent document, elements are always in strict, case-sensitive mode.
2145 
2146 		History:
2147 			On February 8, 2021, the `selfClosedElements` parameter was added. It defaults to the same behavior as
2148 			before: using the hard-coded list of HTML elements, but it can now be overridden. If you use
2149 			[Document.createElement], it will use the list set for the current document. Otherwise, you can pass
2150 			something here if you like.
2151 	+/
2152 	this(string _tagName, string[string] _attributes = null, const string[] selfClosedElements = htmlSelfClosedElements) {
2153 		tagName = _tagName;
2154 		if(_attributes !is null)
2155 			attributes = _attributes;
2156 		selfClosed = tagName.isInArray(selfClosedElements);
2157 
2158 		// this is meant to reserve some memory. It makes a small, but consistent improvement.
2159 		//children.length = 8;
2160 		//children.length = 0;
2161 
2162 		version(dom_node_indexes)
2163 			this.dataset.nodeIndex = to!string(&(this.attributes));
2164 	}
2165 
2166 	private this(Document _parentDocument) {
2167 		version(dom_node_indexes)
2168 			this.dataset.nodeIndex = to!string(&(this.attributes));
2169 	}
2170 
2171 
2172 	/* *******************************
2173 	       Navigating the DOM
2174 	*********************************/
2175 
2176 	/// Returns the first child of this element. If it has no children, returns null.
2177 	/// Remember, text nodes are children too.
2178 	@property Element firstChild() {
2179 		return children.length ? children[0] : null;
2180 	}
2181 
2182 	///
2183 	@property Element lastChild() {
2184 		return children.length ? children[$ - 1] : null;
2185 	}
2186 	
2187 	/// UNTESTED
2188 	/// the next element you would encounter if you were reading it in the source
2189 	Element nextInSource() {
2190 		auto n = firstChild;
2191 		if(n is null)
2192 			n = nextSibling();
2193 		if(n is null) {
2194 			auto p = this.parentNode;
2195 			while(p !is null && n is null) {
2196 				n = p.nextSibling;
2197 			}
2198 		}
2199 
2200 		return n;
2201 	}
2202 
2203 	/// UNTESTED
2204 	/// ditto
2205 	Element previousInSource() {
2206 		auto p = previousSibling;
2207 		if(p is null) {
2208 			auto par = parentNode;
2209 			if(par)
2210 				p = par.lastChild;
2211 			if(p is null)
2212 				p = par;
2213 		}
2214 		return p;
2215 	}
2216 
2217 	///.
2218 	@property Element previousElementSibling() {
2219 		return previousSibling("*");
2220 	}
2221 
2222 	///.
2223 	@property Element previousSibling(string tagName = null) {
2224 		if(this.parentNode is null)
2225 			return null;
2226 		Element ps = null;
2227 		foreach(e; this.parentNode.childNodes) {
2228 			if(e is this)
2229 				break;
2230 			if(tagName == "*" && e.nodeType != NodeType.Text) {
2231 				ps = e;
2232 			} else if(tagName is null || e.tagName == tagName)
2233 				ps = e;
2234 		}
2235 
2236 		return ps;
2237 	}
2238 
2239 	///.
2240 	@property Element nextElementSibling() {
2241 		return nextSibling("*");
2242 	}
2243 
2244 	///.
2245 	@property Element nextSibling(string tagName = null) {
2246 		if(this.parentNode is null)
2247 			return null;
2248 		Element ns = null;
2249 		bool mightBe = false;
2250 		foreach(e; this.parentNode.childNodes) {
2251 			if(e is this) {
2252 				mightBe = true;
2253 				continue;
2254 			}
2255 			if(mightBe) {
2256 				if(tagName == "*" && e.nodeType != NodeType.Text) {
2257 					ns = e;
2258 					break;
2259 				}
2260 				if(tagName is null || e.tagName == tagName) {
2261 					ns = e;
2262 					break;
2263 				}
2264 			}
2265 		}
2266 
2267 		return ns;
2268 	}
2269 
2270 
2271 	/// Gets the nearest node, going up the chain, with the given tagName
2272 	/// May return null or throw.
2273 	T getParent(T = Element)(string tagName = null) if(is(T : Element)) {
2274 		if(tagName is null) {
2275 			static if(is(T == Form))
2276 				tagName = "form";
2277 			else static if(is(T == Table))
2278 				tagName = "table";
2279 			else static if(is(T == Link))
2280 				tagName == "a";
2281 		}
2282 
2283 		auto par = this.parentNode;
2284 		while(par !is null) {
2285 			if(tagName is null || par.tagName == tagName)
2286 				break;
2287 			par = par.parentNode;
2288 		}
2289 
2290 		static if(!is(T == Element)) {
2291 			auto t = cast(T) par;
2292 			if(t is null)
2293 				throw new ElementNotFoundException("", tagName ~ " parent not found", this);
2294 		} else
2295 			auto t = par;
2296 
2297 		return t;
2298 	}
2299 
2300 	///.
2301 	Element getElementById(string id) {
2302 		// FIXME: I use this function a lot, and it's kinda slow
2303 		// not terribly slow, but not great.
2304 		foreach(e; tree)
2305 			if(e.id == id)
2306 				return e;
2307 		return null;
2308 	}
2309 
2310 	/++
2311 		Returns a child element that matches the given `selector`.
2312 
2313 		Note: you can give multiple selectors, separated by commas.
2314 	 	It will return the first match it finds.
2315 
2316 		Tip: to use namespaces, escape the colon in the name:
2317 
2318 		---
2319 			element.querySelector(`ns\:tag`); // the backticks are raw strings then the backslash is interpreted by querySelector
2320 		---
2321 	+/
2322 	@scriptable
2323 	Element querySelector(string selector) {
2324 		Selector s = Selector(selector);
2325 		foreach(ele; tree)
2326 			if(s.matchesElement(ele))
2327 				return ele;
2328 		return null;
2329 	}
2330 
2331 	/// a more standards-compliant alias for getElementsBySelector
2332 	@scriptable
2333 	Element[] querySelectorAll(string selector) {
2334 		return getElementsBySelector(selector);
2335 	}
2336 
2337 	/// If the element matches the given selector. Previously known as `matchesSelector`.
2338 	@scriptable
2339 	bool matches(string selector) {
2340 		/+
2341 		bool caseSensitiveTags = true;
2342 		if(parentDocument && parentDocument.loose)
2343 			caseSensitiveTags = false;
2344 		+/
2345 
2346 		Selector s = Selector(selector);
2347 		return s.matchesElement(this);
2348 	}
2349 
2350 	/// Returns itself or the closest parent that matches the given selector, or null if none found
2351 	/// See_also: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
2352 	@scriptable
2353 	Element closest(string selector) {
2354 		Element e = this;
2355 		while(e !is null) {
2356 			if(e.matches(selector))
2357 				return e;
2358 			e = e.parentNode;
2359 		}
2360 		return null;
2361 	}
2362 
2363 	/**
2364 		Returns elements that match the given CSS selector
2365 
2366 		* -- all, default if nothing else is there
2367 
2368 		tag#id.class.class.class:pseudo[attrib=what][attrib=what] OP selector
2369 
2370 		It is all additive
2371 
2372 		OP
2373 
2374 		space = descendant
2375 		>     = direct descendant
2376 		+     = sibling (E+F Matches any F element immediately preceded by a sibling element E)
2377 
2378 		[foo]        Foo is present as an attribute
2379 		[foo="warning"]   Matches any E element whose "foo" attribute value is exactly equal to "warning".
2380 		E[foo~="warning"] Matches any E element whose "foo" attribute value is a list of space-separated values, one of which is exactly equal to "warning"
2381 		E[lang|="en"] Matches any E element whose "lang" attribute has a hyphen-separated list of values beginning (from the left) with "en".
2382 
2383 		[item$=sdas] ends with
2384 		[item^-sdsad] begins with
2385 
2386 		Quotes are optional here.
2387 
2388 		Pseudos:
2389 			:first-child
2390 			:last-child
2391 			:link (same as a[href] for our purposes here)
2392 
2393 
2394 		There can be commas separating the selector. A comma separated list result is OR'd onto the main.
2395 
2396 
2397 
2398 		This ONLY cares about elements. text, etc, are ignored
2399 
2400 
2401 		There should be two functions: given element, does it match the selector? and given a selector, give me all the elements
2402 	*/
2403 	Element[] getElementsBySelector(string selector) {
2404 		// FIXME: this function could probably use some performance attention
2405 		// ... but only mildly so according to the profiler in the big scheme of things; probably negligible in a big app.
2406 
2407 
2408 		bool caseSensitiveTags = true;
2409 		if(parentDocument && parentDocument.loose)
2410 			caseSensitiveTags = false;
2411 
2412 		Element[] ret;
2413 		foreach(sel; parseSelectorString(selector, caseSensitiveTags))
2414 			ret ~= sel.getElements(this);
2415 		return ret;
2416 	}
2417 
2418 	/// .
2419 	Element[] getElementsByClassName(string cn) {
2420 		// is this correct?
2421 		return getElementsBySelector("." ~ cn);
2422 	}
2423 
2424 	///.
2425 	Element[] getElementsByTagName(string tag) {
2426 		if(parentDocument && parentDocument.loose)
2427 			tag = tag.toLower();
2428 		Element[] ret;
2429 		foreach(e; tree)
2430 			if(e.tagName == tag)
2431 				ret ~= e;
2432 		return ret;
2433 	}
2434 
2435 
2436 	/* *******************************
2437 	          Attributes
2438 	*********************************/
2439 
2440 	/**
2441 		Gets the given attribute value, or null if the
2442 		attribute is not set.
2443 
2444 		Note that the returned string is decoded, so it no longer contains any xml entities.
2445 	*/
2446 	@scriptable
2447 	string getAttribute(string name) const {
2448 		if(parentDocument && parentDocument.loose)
2449 			name = name.toLower();
2450 		auto e = name in attributes;
2451 		if(e)
2452 			return *e;
2453 		else
2454 			return null;
2455 	}
2456 
2457 	/**
2458 		Sets an attribute. Returns this for easy chaining
2459 	*/
2460 	@scriptable
2461 	Element setAttribute(string name, string value) {
2462 		if(parentDocument && parentDocument.loose)
2463 			name = name.toLower();
2464 
2465 		// I never use this shit legitimately and neither should you
2466 		auto it = name.toLower();
2467 		if(it == "href" || it == "src") {
2468 			auto v = value.strip().toLower();
2469 			if(v.startsWith("vbscript:"))
2470 				value = value[9..$];
2471 			if(v.startsWith("javascript:"))
2472 				value = value[11..$];
2473 		}
2474 
2475 		attributes[name] = value;
2476 
2477 		sendObserverEvent(DomMutationOperations.setAttribute, name, value);
2478 
2479 		return this;
2480 	}
2481 
2482 	/**
2483 		Returns if the attribute exists.
2484 	*/
2485 	@scriptable
2486 	bool hasAttribute(string name) {
2487 		if(parentDocument && parentDocument.loose)
2488 			name = name.toLower();
2489 
2490 		if(name in attributes)
2491 			return true;
2492 		else
2493 			return false;
2494 	}
2495 
2496 	/**
2497 		Removes the given attribute from the element.
2498 	*/
2499 	@scriptable
2500 	Element removeAttribute(string name)
2501 	out(ret) {
2502 		assert(ret is this);
2503 	}
2504 	do {
2505 		if(parentDocument && parentDocument.loose)
2506 			name = name.toLower();
2507 		if(name in attributes)
2508 			attributes.remove(name);
2509 
2510 		sendObserverEvent(DomMutationOperations.removeAttribute, name);
2511 		return this;
2512 	}
2513 
2514 	/**
2515 		Gets the class attribute's contents. Returns
2516 		an empty string if it has no class.
2517 	*/
2518 	@property string className() const {
2519 		auto c = getAttribute("class");
2520 		if(c is null)
2521 			return "";
2522 		return c;
2523 	}
2524 
2525 	///.
2526 	@property Element className(string c) {
2527 		setAttribute("class", c);
2528 		return this;
2529 	}
2530 
2531 	/**
2532 		Provides easy access to common HTML attributes, object style.
2533 
2534 		---
2535 		auto element = Element.make("a");
2536 		a.href = "cool.html"; // this is the same as a.setAttribute("href", "cool.html");
2537 		string where = a.href; // same as a.getAttribute("href");
2538 		---
2539 
2540 	*/
2541 	@property string opDispatch(string name)(string v = null) if(isConvenientAttribute(name)) {
2542 		if(v !is null)
2543 			setAttribute(name, v);
2544 		return getAttribute(name);
2545 	}
2546 
2547 	/**
2548 		Old access to attributes. Use [attrs] instead.
2549 
2550 		DEPRECATED: generally open opDispatch caused a lot of unforeseen trouble with compile time duck typing and UFCS extensions.
2551 		so I want to remove it. A small whitelist of attributes is still allowed, but others are not.
2552 		
2553 		Instead, use element.attrs.attribute, element.attrs["attribute"],
2554 		or element.getAttribute("attribute")/element.setAttribute("attribute").
2555 	*/
2556 	@property string opDispatch(string name)(string v = null) if(!isConvenientAttribute(name)) {
2557 		static assert(0, "Don't use " ~ name ~ " direct on Element, instead use element.attrs.attributeName");
2558 	}
2559 
2560 	/*
2561 	// this would be nice for convenience, but it broke the getter above.
2562 	@property void opDispatch(string name)(bool boolean) if(name != "popFront") {
2563 		if(boolean)
2564 			setAttribute(name, name);
2565 		else
2566 			removeAttribute(name);
2567 	}
2568 	*/
2569 
2570 	/**
2571 		Returns the element's children.
2572 	*/
2573 	@property const(Element[]) childNodes() const {
2574 		return children;
2575 	}
2576 
2577 	/// Mutable version of the same
2578 	@property Element[] childNodes() { // FIXME: the above should be inout
2579 		return children;
2580 	}
2581 
2582 	/++
2583 		HTML5's dataset property. It is an alternate view into attributes with the data- prefix.
2584 		Given `<a data-my-property="cool" />`, we get `assert(a.dataset.myProperty == "cool");`
2585 	+/
2586 	@property DataSet dataset() {
2587 		return DataSet(this);
2588 	}
2589 
2590 	/++
2591 		Gives dot/opIndex access to attributes
2592 		---
2593 		ele.attrs.largeSrc = "foo"; // same as ele.setAttribute("largeSrc", "foo")
2594 		---
2595 	+/
2596 	@property AttributeSet attrs() {
2597 		return AttributeSet(this);
2598 	}
2599 
2600 	/++
2601 		Provides both string and object style (like in Javascript) access to the style attribute.
2602 
2603 		---
2604 		element.style.color = "red"; // translates into setting `color: red;` in the `style` attribute
2605 		---
2606 	+/
2607 	@property ElementStyle style() {
2608 		return ElementStyle(this);
2609 	}
2610 
2611 	/++
2612 		This sets the style attribute with a string.
2613 	+/
2614 	@property ElementStyle style(string s) {
2615 		this.setAttribute("style", s);
2616 		return this.style;
2617 	}
2618 
2619 	private void parseAttributes(string[] whichOnes = null) {
2620 /+
2621 		if(whichOnes is null)
2622 			whichOnes = attributes.keys;
2623 		foreach(attr; whichOnes) {
2624 			switch(attr) {
2625 				case "id":
2626 
2627 				break;
2628 				case "class":
2629 
2630 				break;
2631 				case "style":
2632 
2633 				break;
2634 				default:
2635 					// we don't care about it
2636 			}
2637 		}
2638 +/
2639 	}
2640 
2641 
2642 	// if you change something here, it won't apply... FIXME const? but changing it would be nice if it applies to the style attribute too though you should use style there.
2643 
2644 	// the next few methods are for implementing interactive kind of things
2645 	private CssStyle _computedStyle;
2646 
2647 	/// Don't use this.
2648 	@property CssStyle computedStyle() {
2649 		if(_computedStyle is null) {
2650 			auto style = this.getAttribute("style");
2651 		/* we'll treat shitty old html attributes as css here */
2652 			if(this.hasAttribute("width"))
2653 				style ~= "; width: " ~ this.attrs.width;
2654 			if(this.hasAttribute("height"))
2655 				style ~= "; height: " ~ this.attrs.height;
2656 			if(this.hasAttribute("bgcolor"))
2657 				style ~= "; background-color: " ~ this.attrs.bgcolor;
2658 			if(this.tagName == "body" && this.hasAttribute("text"))
2659 				style ~= "; color: " ~ this.attrs.text;
2660 			if(this.hasAttribute("color"))
2661 				style ~= "; color: " ~ this.attrs.color;
2662 		/* done */
2663 
2664 
2665 			_computedStyle = new CssStyle(null, style); // gives at least something to work with
2666 		}
2667 		return _computedStyle;
2668 	}
2669 
2670 	/// These properties are useless in most cases, but if you write a layout engine on top of this lib, they may be good
2671 	version(browser) {
2672 		void* expansionHook; ///ditto
2673 		int offsetWidth; ///ditto
2674 		int offsetHeight; ///ditto
2675 		int offsetLeft; ///ditto
2676 		int offsetTop; ///ditto
2677 		Element offsetParent; ///ditto
2678 		bool hasLayout; ///ditto
2679 		int zIndex; ///ditto
2680 
2681 		///ditto
2682 		int absoluteLeft() {
2683 			int a = offsetLeft;
2684 			auto p = offsetParent;
2685 			while(p) {
2686 				a += p.offsetLeft;
2687 				p = p.offsetParent;
2688 			}
2689 
2690 			return a;
2691 		}
2692 
2693 		///ditto
2694 		int absoluteTop() {
2695 			int a = offsetTop;
2696 			auto p = offsetParent;
2697 			while(p) {
2698 				a += p.offsetTop;
2699 				p = p.offsetParent;
2700 			}
2701 
2702 			return a;
2703 		}
2704 	}
2705 
2706 	// Back to the regular dom functions
2707 
2708     public:
2709 
2710 
2711 	/* *******************************
2712 	          DOM Mutation
2713 	*********************************/
2714 
2715 	/// Removes all inner content from the tag; all child text and elements are gone.
2716 	void removeAllChildren()
2717 		out {
2718 			assert(this.children.length == 0);
2719 		}
2720 	do {
2721 		foreach(child; children)
2722 			child.parentNode = null;
2723 		children = null;
2724 	}
2725 
2726 	/// History: added June 13, 2020
2727 	Element appendSibling(Element e) {
2728 		parentNode.insertAfter(this, e);
2729 		return e;
2730 	}
2731 
2732 	/// History: added June 13, 2020
2733 	Element prependSibling(Element e) {
2734 		parentNode.insertBefore(this, e);
2735 		return e;
2736 	}
2737 
2738 
2739     	/++
2740 		Appends the given element to this one. If it already has a parent, it is removed from that tree and moved to this one.
2741 
2742 		See_also: https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild
2743 
2744 		History:
2745 			Prior to 1 Jan 2020 (git tag v4.4.1 and below), it required that the given element must not have a parent already. This was in violation of standard, so it changed the behavior to remove it from the existing parent and instead move it here.
2746 	+/
2747 	Element appendChild(Element e)
2748 		in {
2749 			assert(e !is null);
2750 		}
2751 		out (ret) {
2752 			assert((cast(DocumentFragment) this !is null) || (e.parentNode is this), e.toString);// e.parentNode ? e.parentNode.toString : "null");
2753 			assert(e.parentDocument is this.parentDocument);
2754 			assert(e is ret);
2755 		}
2756 	do {
2757 		if(e.parentNode !is null)
2758 			e.parentNode.removeChild(e);
2759 
2760 		selfClosed = false;
2761 		if(auto frag = cast(DocumentFragment) e)
2762 			children ~= frag.children;
2763 		else
2764 			children ~= e;
2765 
2766 		e.parentNode = this;
2767 
2768 		/+
2769 		foreach(item; e.tree)
2770 			item.parentDocument = this.parentDocument;
2771 		+/
2772 
2773 		sendObserverEvent(DomMutationOperations.appendChild, null, null, e);
2774 
2775 		return e;
2776 	}
2777 
2778 	/// Inserts the second element to this node, right before the first param
2779 	Element insertBefore(in Element where, Element what)
2780 		in {
2781 			assert(where !is null);
2782 			assert(where.parentNode is this);
2783 			assert(what !is null);
2784 			assert(what.parentNode is null);
2785 		}
2786 		out (ret) {
2787 			assert(where.parentNode is this);
2788 			assert(what.parentNode is this);
2789 
2790 			assert(what.parentDocument is this.parentDocument);
2791 			assert(ret is what);
2792 		}
2793 	do {
2794 		foreach(i, e; children) {
2795 			if(e is where) {
2796 				if(auto frag = cast(DocumentFragment) what) {
2797 					children = children[0..i] ~ frag.children ~ children[i..$];
2798 					foreach(child; frag.children)
2799 						child.parentNode = this;
2800 				} else {
2801 					children = children[0..i] ~ what ~ children[i..$];
2802 				}
2803 				what.parentNode = this;
2804 				return what;
2805 			}
2806 		}
2807 
2808 		return what;
2809 
2810 		assert(0);
2811 	}
2812 
2813 	/++
2814 		Inserts the given element `what` as a sibling of the `this` element, after the element `where` in the parent node.
2815 	+/
2816 	Element insertAfter(in Element where, Element what)
2817 		in {
2818 			assert(where !is null);
2819 			assert(where.parentNode is this);
2820 			assert(what !is null);
2821 			assert(what.parentNode is null);
2822 		}
2823 		out (ret) {
2824 			assert(where.parentNode is this);
2825 			assert(what.parentNode is this);
2826 			assert(what.parentDocument is this.parentDocument);
2827 			assert(ret is what);
2828 		}
2829 	do {
2830 		foreach(i, e; children) {
2831 			if(e is where) {
2832 				if(auto frag = cast(DocumentFragment) what) {
2833 					children = children[0 .. i + 1] ~ what.children ~ children[i + 1 .. $];
2834 					foreach(child; frag.children)
2835 						child.parentNode = this;
2836 				} else
2837 					children = children[0 .. i + 1] ~ what ~ children[i + 1 .. $];
2838 				what.parentNode = this;
2839 				return what;
2840 			}
2841 		}
2842 
2843 		return what;
2844 
2845 		assert(0);
2846 	}
2847 
2848 	/// swaps one child for a new thing. Returns the old child which is now parentless.
2849 	Element swapNode(Element child, Element replacement)
2850 		in {
2851 			assert(child !is null);
2852 			assert(replacement !is null);
2853 			assert(child.parentNode is this);
2854 		}
2855 		out(ret) {
2856 			assert(ret is child);
2857 			assert(ret.parentNode is null);
2858 			assert(replacement.parentNode is this);
2859 			assert(replacement.parentDocument is this.parentDocument);
2860 		}
2861 	do {
2862 		foreach(ref c; this.children)
2863 			if(c is child) {
2864 				c.parentNode = null;
2865 				c = replacement;
2866 				c.parentNode = this;
2867 				return child;
2868 			}
2869 		assert(0);
2870 	}
2871 
2872 
2873 	/++
2874 		Appends the given to the node.
2875 
2876 
2877 		Calling `e.appendText(" hi")` on `<example>text <b>bold</b></example>`
2878 		yields `<example>text <b>bold</b> hi</example>`.
2879 
2880 		See_Also:
2881 			[firstInnerText], [directText], [innerText], [appendChild]
2882 	+/
2883 	@scriptable
2884 	Element appendText(string text) {
2885 		Element e = new TextNode(parentDocument, text);
2886 		appendChild(e);
2887 		return this;
2888 	}
2889 
2890 	/++
2891 		Returns child elements which are of a tag type (excludes text, comments, etc.).
2892 
2893 
2894 		childElements of `<example>text <b>bold</b></example>` is just the `<b>` tag.
2895 
2896 		Params:
2897 			tagName = filter results to only the child elements with the given tag name.
2898 	+/
2899 	@property Element[] childElements(string tagName = null) {
2900 		Element[] ret;
2901 		foreach(c; children)
2902 			if(c.nodeType == 1 && (tagName is null || c.tagName == tagName))
2903 				ret ~= c;
2904 		return ret;
2905 	}
2906 
2907 	/++
2908 		Appends the given html to the element, returning the elements appended
2909 
2910 
2911 		This is similar to `element.innerHTML += "html string";` in Javascript.
2912 	+/
2913 	@scriptable
2914 	Element[] appendHtml(string html) {
2915 		Document d = new Document("<root>" ~ html ~ "</root>");
2916 		return stealChildren(d.root);
2917 	}
2918 
2919 
2920 	///.
2921 	void insertChildAfter(Element child, Element where)
2922 		in {
2923 			assert(child !is null);
2924 			assert(where !is null);
2925 			assert(where.parentNode is this);
2926 			assert(!selfClosed);
2927 			//assert(isInArray(where, children));
2928 		}
2929 		out {
2930 			assert(child.parentNode is this);
2931 			assert(where.parentNode is this);
2932 			//assert(isInArray(where, children));
2933 			//assert(isInArray(child, children));
2934 		}
2935 	do {
2936 		foreach(ref i, c; children) {
2937 			if(c is where) {
2938 				i++;
2939 				if(auto frag = cast(DocumentFragment) child) {
2940 					children = children[0..i] ~ child.children ~ children[i..$];
2941 					//foreach(child; frag.children)
2942 						//child.parentNode = this;
2943 				} else
2944 					children = children[0..i] ~ child ~ children[i..$];
2945 				child.parentNode = this;
2946 				break;
2947 			}
2948 		}
2949 	}
2950 
2951 	/++
2952 		Reparents all the child elements of `e` to `this`, leaving `e` childless.
2953 
2954 		Params:
2955 			e = the element whose children you want to steal
2956 			position = an existing child element in `this` before which you want the stolen children to be inserted. If `null`, it will append the stolen children at the end of our current children.
2957 	+/
2958 	Element[] stealChildren(Element e, Element position = null)
2959 		in {
2960 			assert(!selfClosed);
2961 			assert(e !is null);
2962 			//if(position !is null)
2963 				//assert(isInArray(position, children));
2964 		}
2965 		out (ret) {
2966 			assert(e.children.length == 0);
2967 			// all the parentNode is this checks fail because DocumentFragments do not appear in the parent tree, they are invisible...
2968 			version(none)
2969 			debug foreach(child; ret) {
2970 				assert(child.parentNode is this);
2971 				assert(child.parentDocument is this.parentDocument);
2972 			}
2973 		}
2974 	do {
2975 		foreach(c; e.children) {
2976 			c.parentNode = this;
2977 		}
2978 		if(position is null)
2979 			children ~= e.children;
2980 		else {
2981 			foreach(i, child; children) {
2982 				if(child is position) {
2983 					children = children[0..i] ~
2984 						e.children ~
2985 						children[i..$];
2986 					break;
2987 				}
2988 			}
2989 		}
2990 
2991 		auto ret = e.children[];
2992 		e.children.length = 0;
2993 
2994 		return ret;
2995 	}
2996 
2997     	/// Puts the current element first in our children list. The given element must not have a parent already.
2998 	Element prependChild(Element e)
2999 		in {
3000 			assert(e.parentNode is null);
3001 			assert(!selfClosed);
3002 		}
3003 		out {
3004 			assert(e.parentNode is this);
3005 			assert(e.parentDocument is this.parentDocument);
3006 			assert(children[0] is e);
3007 		}
3008 	do {
3009 		if(auto frag = cast(DocumentFragment) e) {
3010 			children = e.children ~ children;
3011 			foreach(child; frag.children)
3012 				child.parentNode = this;
3013 		} else
3014 			children = e ~ children;
3015 		e.parentNode = this;
3016 		return e;
3017 	}
3018 
3019 
3020 	/**
3021 		Returns a string containing all child elements, formatted such that it could be pasted into
3022 		an XML file.
3023 	*/
3024 	@property string innerHTML(Appender!string where = appender!string()) const {
3025 		if(children is null)
3026 			return "";
3027 
3028 		auto start = where.data.length;
3029 
3030 		foreach(child; children) {
3031 			assert(child !is null);
3032 
3033 			child.writeToAppender(where);
3034 		}
3035 
3036 		return where.data[start .. $];
3037 	}
3038 
3039 	/**
3040 		Takes some html and replaces the element's children with the tree made from the string.
3041 	*/
3042 	@property Element innerHTML(string html, bool strict = false) {
3043 		if(html.length)
3044 			selfClosed = false;
3045 
3046 		if(html.length == 0) {
3047 			// I often say innerHTML = ""; as a shortcut to clear it out,
3048 			// so let's optimize that slightly.
3049 			removeAllChildren();
3050 			return this;
3051 		}
3052 
3053 		auto doc = new Document();
3054 		doc.parseUtf8("<innerhtml>" ~ html ~ "</innerhtml>", strict, strict); // FIXME: this should preserve the strictness of the parent document
3055 
3056 		children = doc.root.children;
3057 		foreach(c; children) {
3058 			c.parentNode = this;
3059 		}
3060 
3061 		doc.root.children = null;
3062 
3063 		return this;
3064 	}
3065 
3066 	/// ditto
3067 	@property Element innerHTML(Html html) {
3068 		return this.innerHTML = html.source;
3069 	}
3070 
3071 	/**
3072 		Replaces this node with the given html string, which is parsed
3073 
3074 		Note: this invalidates the this reference, since it is removed
3075 		from the tree.
3076 
3077 		Returns the new children that replace this.
3078 	*/
3079 	@property Element[] outerHTML(string html) {
3080 		auto doc = new Document();
3081 		doc.parseUtf8("<innerhtml>" ~ html ~ "</innerhtml>"); // FIXME: needs to preserve the strictness
3082 
3083 		children = doc.root.children;
3084 		foreach(c; children) {
3085 			c.parentNode = this;
3086 		}
3087 
3088 		stripOut();
3089 
3090 		return doc.root.children;
3091 	}
3092 
3093 	/++
3094 		Returns all the html for this element, including the tag itself.
3095 
3096 		This is equivalent to calling toString().
3097 	+/
3098 	@property string outerHTML() {
3099 		return this.toString();
3100 	}
3101 
3102 	/// This sets the inner content of the element *without* trying to parse it.
3103 	/// You can inject any code in there; this serves as an escape hatch from the dom.
3104 	///
3105 	/// The only times you might actually need it are for < style > and < script > tags in html.
3106 	/// Other than that, innerHTML and/or innerText should do the job.
3107 	@property void innerRawSource(string rawSource) {
3108 		children.length = 0;
3109 		auto rs = new RawSource(parentDocument, rawSource);
3110 		children ~= rs;
3111 		rs.parentNode = this;
3112 	}
3113 
3114 	///.
3115 	Element replaceChild(Element find, Element replace)
3116 		in {
3117 			assert(find !is null);
3118 			assert(find.parentNode is this);
3119 			assert(replace !is null);
3120 			assert(replace.parentNode is null);
3121 		}
3122 		out(ret) {
3123 			assert(ret is replace);
3124 			assert(replace.parentNode is this);
3125 			assert(replace.parentDocument is this.parentDocument);
3126 			assert(find.parentNode is null);
3127 		}
3128 	do {
3129 		// FIXME
3130 		//if(auto frag = cast(DocumentFragment) replace)
3131 			//return this.replaceChild(frag, replace.children);
3132 		for(int i = 0; i < children.length; i++) {
3133 			if(children[i] is find) {
3134 				replace.parentNode = this;
3135 				children[i].parentNode = null;
3136 				children[i] = replace;
3137 				return replace;
3138 			}
3139 		}
3140 
3141 		throw new Exception("no such child ");// ~  find.toString ~ " among " ~ typeid(this).toString);//.toString ~ " magic \n\n\n" ~ find.parentNode.toString);
3142 	}
3143 
3144 	/**
3145 		Replaces the given element with a whole group.
3146 	*/
3147 	void replaceChild(Element find, Element[] replace)
3148 		in {
3149 			assert(find !is null);
3150 			assert(replace !is null);
3151 			assert(find.parentNode is this);
3152 			debug foreach(r; replace)
3153 				assert(r.parentNode is null);
3154 		}
3155 		out {
3156 			assert(find.parentNode is null);
3157 			assert(children.length >= replace.length);
3158 			debug foreach(child; children)
3159 				assert(child !is find);
3160 			debug foreach(r; replace)
3161 				assert(r.parentNode is this);
3162 		}
3163 	do {
3164 		if(replace.length == 0) {
3165 			removeChild(find);
3166 			return;
3167 		}
3168 		assert(replace.length);
3169 		for(int i = 0; i < children.length; i++) {
3170 			if(children[i] is find) {
3171 				children[i].parentNode = null; // this element should now be dead
3172 				children[i] = replace[0];
3173 				foreach(e; replace) {
3174 					e.parentNode = this;
3175 				}
3176 
3177 				children = .insertAfter(children, i, replace[1..$]);
3178 
3179 				return;
3180 			}
3181 		}
3182 
3183 		throw new Exception("no such child");
3184 	}
3185 
3186 
3187 	/**
3188 		Removes the given child from this list.
3189 
3190 		Returns the removed element.
3191 	*/
3192 	Element removeChild(Element c)
3193 		in {
3194 			assert(c !is null);
3195 			assert(c.parentNode is this);
3196 		}
3197 		out {
3198 			debug foreach(child; children)
3199 				assert(child !is c);
3200 			assert(c.parentNode is null);
3201 		}
3202 	do {
3203 		foreach(i, e; children) {
3204 			if(e is c) {
3205 				children = children[0..i] ~ children [i+1..$];
3206 				c.parentNode = null;
3207 				return c;
3208 			}
3209 		}
3210 
3211 		throw new Exception("no such child");
3212 	}
3213 
3214 	/// This removes all the children from this element, returning the old list.
3215 	Element[] removeChildren()
3216 		out (ret) {
3217 			assert(children.length == 0);
3218 			debug foreach(r; ret)
3219 				assert(r.parentNode is null);
3220 		}
3221 	do {
3222 		Element[] oldChildren = children.dup;
3223 		foreach(c; oldChildren)
3224 			c.parentNode = null;
3225 
3226 		children.length = 0;
3227 
3228 		return oldChildren;
3229 	}
3230 
3231 	/**
3232 		Fetch the inside text, with all tags stripped out.
3233 
3234 		<p>cool <b>api</b> &amp; code dude<p>
3235 		innerText of that is "cool api & code dude".
3236 
3237 		This does not match what real innerText does!
3238 		http://perfectionkills.com/the-poor-misunderstood-innerText/
3239 
3240 		It is more like textContent.
3241 	*/
3242 	@scriptable
3243 	@property string innerText() const {
3244 		string s;
3245 		foreach(child; children) {
3246 			if(child.nodeType != NodeType.Text)
3247 				s ~= child.innerText;
3248 			else
3249 				s ~= child.nodeValue();
3250 		}
3251 		return s;
3252 	}
3253 
3254 	///
3255 	alias textContent = innerText;
3256 
3257 	/**
3258 		Sets the inside text, replacing all children. You don't
3259 		have to worry about entity encoding.
3260 	*/
3261 	@scriptable
3262 	@property void innerText(string text) {
3263 		selfClosed = false;
3264 		Element e = new TextNode(parentDocument, text);
3265 		children = [e];
3266 		e.parentNode = this;
3267 	}
3268 
3269 	/**
3270 		Strips this node out of the document, replacing it with the given text
3271 	*/
3272 	@property void outerText(string text) {
3273 		parentNode.replaceChild(this, new TextNode(parentDocument, text));
3274 	}
3275 
3276 	/**
3277 		Same result as innerText; the tag with all inner tags stripped out
3278 	*/
3279 	@property string outerText() const {
3280 		return innerText;
3281 	}
3282 
3283 
3284 	/* *******************************
3285 	          Miscellaneous
3286 	*********************************/
3287 
3288 	/// This is a full clone of the element. Alias for cloneNode(true) now. Don't extend it.
3289 	@property Element cloned()
3290 	/+
3291 		out(ret) {
3292 			// FIXME: not sure why these fail...
3293 			assert(ret.children.length == this.children.length, format("%d %d", ret.children.length, this.children.length));
3294 			assert(ret.tagName == this.tagName);
3295 		}
3296 	do {
3297 	+/
3298 	{
3299 		return this.cloneNode(true);
3300 	}
3301 
3302 	/// Clones the node. If deepClone is true, clone all inner tags too. If false, only do this tag (and its attributes), but it will have no contents.
3303 	Element cloneNode(bool deepClone) {
3304 		auto e = Element.make(this.tagName);
3305 		e.attributes = this.attributes.aadup;
3306 		e.selfClosed = this.selfClosed;
3307 
3308 		if(deepClone) {
3309 			foreach(child; children) {
3310 				e.appendChild(child.cloneNode(true));
3311 			}
3312 		}
3313 		
3314 
3315 		return e;
3316 	}
3317 
3318 	/// W3C DOM interface. Only really meaningful on [TextNode] instances, but the interface is present on the base class.
3319 	string nodeValue() const {
3320 		return "";
3321 	}
3322 
3323 	// should return int
3324 	///.
3325 	@property int nodeType() const {
3326 		return 1;
3327 	}
3328 
3329 
3330 	invariant () {
3331 		debug assert(tagName.indexOf(" ") == -1);
3332 
3333 		// commented cuz it gets into recursive pain and eff dat.
3334 		/+
3335 		if(children !is null)
3336 		foreach(child; children) {
3337 		//	assert(parentNode !is null);
3338 			assert(child !is null);
3339 			assert(child.parent_.asElement is this, format("%s is not a parent of %s (it thought it was %s)", tagName, child.tagName, child.parent_.asElement is null ? "null" : child.parent_.asElement.tagName));
3340 			assert(child !is this);
3341 			//assert(child !is parentNode);
3342 		}
3343 		+/
3344 
3345 		/+
3346 		// this isn't helping
3347 		if(parent_ && parent_.asElement) {
3348 			bool found = false;
3349 			foreach(child; parent_.asElement.children)
3350 				if(child is this)
3351 					found = true;
3352 			assert(found, format("%s lists %s as parent, but it is not in children", typeid(this), typeid(this.parent_.asElement)));
3353 		}
3354 		+/
3355 
3356 		/+ // only depend on parentNode's accuracy if you shuffle things around and use the top elements - where the contracts guarantee it on out
3357 		if(parentNode !is null) {
3358 			// if you have a parent, you should share the same parentDocument; this is appendChild()'s job
3359 			auto lol = cast(TextNode) this;
3360 			assert(parentDocument is parentNode.parentDocument, lol is null ? this.tagName : lol.contents);
3361 		}
3362 		+/
3363 		//assert(parentDocument !is null); // no more; if it is present, we use it, but it is not required
3364 		// reason is so you can create these without needing a reference to the document
3365 	}
3366 
3367 	/**
3368 		Turns the whole element, including tag, attributes, and children, into a string which could be pasted into
3369 		an XML file.
3370 	*/
3371 	override string toString() const {
3372 		return writeToAppender();
3373 	}
3374 
3375 	protected string toPrettyStringIndent(bool insertComments, int indentationLevel, string indentWith) const {
3376 		if(indentWith is null || this.children.length == 0)
3377 			return null;
3378 		string s;
3379 
3380 		if(insertComments) s ~= "<!--";
3381 		s ~= "\n";
3382 		foreach(indent; 0 .. indentationLevel)
3383 			s ~= indentWith;
3384 		if(insertComments) s ~= "-->";
3385 
3386 		return s;
3387 	}
3388 
3389 	/++
3390 		Writes out with formatting. Be warned: formatting changes the contents. Use ONLY
3391 		for eyeball debugging.
3392 
3393 		$(PITFALL
3394 			This function is not stable. Its interface and output may change without
3395 			notice. The only promise I make is that it will continue to make a best-
3396 			effort attempt at being useful for debugging by human eyes.
3397 
3398 			I have used it in the past for diffing html documents, but even then, it
3399 			might change between versions. If it is useful, great, but beware; this
3400 			use is at your own risk.
3401 		)
3402 
3403 		History:
3404 			On November 19, 2021, I changed this to `final`. If you were overriding it,
3405 			change our override to `toPrettyStringImpl` instead. It now just calls
3406 			`toPrettyStringImpl.strip` to be an entry point for a stand-alone call.
3407 
3408 			If you are calling it as part of another implementation, you might want to
3409 			change that call to `toPrettyStringImpl` as well.
3410 
3411 			I am NOT considering this a breaking change since this function is documented
3412 			to only be used for eyeball debugging anyway, which means the exact format is
3413 			not specified and the override behavior can generally not be relied upon.
3414 
3415 			(And I find it extremely unlikely anyone was subclassing anyway, but if you were,
3416 			email me, and we'll see what we can do. I'd like to know at least.)
3417 
3418 			I reserve the right to make future changes in the future without considering
3419 			them breaking as well.
3420 	+/
3421 	final string toPrettyString(bool insertComments = false, int indentationLevel = 0, string indentWith = "\t") const {
3422 		return toPrettyStringImpl(insertComments, indentationLevel, indentWith).strip;
3423 	}
3424 
3425 	string toPrettyStringImpl(bool insertComments = false, int indentationLevel = 0, string indentWith = "\t") const {
3426 
3427 		// first step is to concatenate any consecutive text nodes to simplify
3428 		// the white space analysis. this changes the tree! but i'm allowed since
3429 		// the comment always says it changes the comments
3430 		//
3431 		// actually i'm not allowed cuz it is const so i will cheat and lie
3432 		/+
3433 		TextNode lastTextChild = null;
3434 		for(int a = 0; a < this.children.length; a++) {
3435 			auto child = this.children[a];
3436 			if(auto tn = cast(TextNode) child) {
3437 				if(lastTextChild) {
3438 					lastTextChild.contents ~= tn.contents;
3439 					for(int b = a; b < this.children.length - 1; b++)
3440 						this.children[b] = this.children[b + 1];
3441 					this.children = this.children[0 .. $-1];
3442 				} else {
3443 					lastTextChild = tn;
3444 				}
3445 			} else {
3446 				lastTextChild = null;
3447 			}
3448 		}
3449 		+/
3450 
3451 		auto inlineElements = (parentDocument is null ? null : parentDocument.inlineElements);
3452 
3453 		const(Element)[] children;
3454 
3455 		TextNode lastTextChild = null;
3456 		for(int a = 0; a < this.children.length; a++) {
3457 			auto child = this.children[a];
3458 			if(auto tn = cast(const(TextNode)) child) {
3459 				if(lastTextChild !is null) {
3460 					lastTextChild.contents ~= tn.contents;
3461 				} else {
3462 					lastTextChild = new TextNode("");
3463 					lastTextChild.parentNode = cast(Element) this;
3464 					lastTextChild.contents ~= tn.contents;
3465 					children ~= lastTextChild;
3466 				}
3467 			} else {
3468 				lastTextChild = null;
3469 				children ~= child;
3470 			}
3471 		}
3472 
3473 		string s = toPrettyStringIndent(insertComments, indentationLevel, indentWith);
3474 
3475 		s ~= "<";
3476 		s ~= tagName;
3477 
3478 		// i sort these for consistent output. might be more legible
3479 		// but especially it keeps it the same for diff purposes.
3480 		import std.algorithm : sort;
3481 		auto keys = sort(attributes.keys);
3482 		foreach(n; keys) {
3483 			auto v = attributes[n];
3484 			s ~= " ";
3485 			s ~= n;
3486 			s ~= "=\"";
3487 			s ~= htmlEntitiesEncode(v);
3488 			s ~= "\"";
3489 		}
3490 
3491 		if(selfClosed){
3492 			s ~= " />";
3493 			return s;
3494 		}
3495 
3496 		s ~= ">";
3497 
3498 		// for simple `<collection><item>text</item><item>text</item></collection>`, let's
3499 		// just keep them on the same line
3500 		if(tagName.isInArray(inlineElements) || allAreInlineHtml(children, inlineElements)) {
3501 			foreach(child; children) {
3502 				s ~= child.toString();//toPrettyString(false, 0, null);
3503 			}
3504 		} else {
3505 			foreach(child; children) {
3506 				assert(child !is null);
3507 
3508 				s ~= child.toPrettyStringImpl(insertComments, indentationLevel + 1, indentWith);
3509 			}
3510 
3511 			s ~= toPrettyStringIndent(insertComments, indentationLevel, indentWith);
3512 		}
3513 
3514 		s ~= "</";
3515 		s ~= tagName;
3516 		s ~= ">";
3517 
3518 		return s;
3519 	}
3520 
3521 	/+
3522 	/// Writes out the opening tag only, if applicable.
3523 	string writeTagOnly(Appender!string where = appender!string()) const {
3524 	+/
3525 
3526 	/// This is the actual implementation used by toString. You can pass it a preallocated buffer to save some time.
3527 	/// Note: the ordering of attributes in the string is undefined.
3528 	/// Returns the string it creates.
3529 	string writeToAppender(Appender!string where = appender!string()) const {
3530 		assert(tagName !is null);
3531 
3532 		where.reserve((this.children.length + 1) * 512);
3533 
3534 		auto start = where.data.length;
3535 
3536 		where.put("<");
3537 		where.put(tagName);
3538 
3539 		import std.algorithm : sort;
3540 		auto keys = sort(attributes.keys);
3541 		foreach(n; keys) {
3542 			auto v = attributes[n]; // I am sorting these for convenience with another project. order of AAs is undefined, so I'm allowed to do it.... and it is still undefined, I might change it back later.
3543 			//assert(v !is null);
3544 			where.put(" ");
3545 			where.put(n);
3546 			where.put("=\"");
3547 			htmlEntitiesEncode(v, where);
3548 			where.put("\"");
3549 		}
3550 
3551 		if(selfClosed){
3552 			where.put(" />");
3553 			return where.data[start .. $];
3554 		}
3555 
3556 		where.put('>');
3557 
3558 		innerHTML(where);
3559 
3560 		where.put("</");
3561 		where.put(tagName);
3562 		where.put('>');
3563 
3564 		return where.data[start .. $];
3565 	}
3566 
3567 	/**
3568 		Returns a lazy range of all its children, recursively.
3569 	*/
3570 	@property ElementStream tree() {
3571 		return new ElementStream(this);
3572 	}
3573 
3574 	// I moved these from Form because they are generally useful.
3575 	// Ideally, I'd put them in arsd.html and use UFCS, but that doesn't work with the opDispatch here.
3576 	/// Tags: HTML, HTML5
3577 	// FIXME: add overloads for other label types...
3578 	Element addField(string label, string name, string type = "text", FormFieldOptions fieldOptions = FormFieldOptions.none) {
3579 		auto fs = this;
3580 		auto i = fs.addChild("label");
3581 
3582 		if(!(type == "checkbox" || type == "radio"))
3583 			i.addChild("span", label);
3584 
3585 		Element input;
3586 		if(type == "textarea")
3587 			input = i.addChild("textarea").
3588 			setAttribute("name", name).
3589 			setAttribute("rows", "6");
3590 		else
3591 			input = i.addChild("input").
3592 			setAttribute("name", name).
3593 			setAttribute("type", type);
3594 
3595 		if(type == "checkbox" || type == "radio")
3596 			i.addChild("span", label);
3597 
3598 		// these are html 5 attributes; you'll have to implement fallbacks elsewhere. In Javascript or maybe I'll add a magic thing to html.d later.
3599 		fieldOptions.applyToElement(input);
3600 		return i;
3601 	}
3602 
3603 	Element addField(Element label, string name, string type = "text", FormFieldOptions fieldOptions = FormFieldOptions.none) {
3604 		auto fs = this;
3605 		auto i = fs.addChild("label");
3606 		i.addChild(label);
3607 		Element input;
3608 		if(type == "textarea")
3609 			input = i.addChild("textarea").
3610 			setAttribute("name", name).
3611 			setAttribute("rows", "6");
3612 		else
3613 			input = i.addChild("input").
3614 			setAttribute("name", name).
3615 			setAttribute("type", type);
3616 
3617 		// these are html 5 attributes; you'll have to implement fallbacks elsewhere. In Javascript or maybe I'll add a magic thing to html.d later.
3618 		fieldOptions.applyToElement(input);
3619 		return i;
3620 	}
3621 
3622 	Element addField(string label, string name, FormFieldOptions fieldOptions) {
3623 		return addField(label, name, "text", fieldOptions);
3624 	}
3625 
3626 	Element addField(string label, string name, string[string] options, FormFieldOptions fieldOptions = FormFieldOptions.none) {
3627 		auto fs = this;
3628 		auto i = fs.addChild("label");
3629 		i.addChild("span", label);
3630 		auto sel = i.addChild("select").setAttribute("name", name);
3631 
3632 		foreach(k, opt; options)
3633 			sel.addChild("option", opt, k);
3634 
3635 		// FIXME: implement requirements somehow
3636 
3637 		return i;
3638 	}
3639 
3640 	Element addSubmitButton(string label = null) {
3641 		auto t = this;
3642 		auto holder = t.addChild("div");
3643 		holder.addClass("submit-holder");
3644 		auto i = holder.addChild("input");
3645 		i.type = "submit";
3646 		if(label.length)
3647 			i.value = label;
3648 		return holder;
3649 	}
3650 
3651 }
3652 // computedStyle could argubaly be removed to bring size down
3653 //pragma(msg, __traits(classInstanceSize, Element));
3654 //pragma(msg, Element.tupleof);
3655 
3656 // FIXME: since Document loosens the input requirements, it should probably be the sub class...
3657 /// Specializes Document for handling generic XML. (always uses strict mode, uses xml mime type and file header)
3658 /// Group: core_functionality
3659 class XmlDocument : Document {
3660 	this(string data) {
3661 		selfClosedElements = null;
3662 		inlineElements = null;
3663 		contentType = "text/xml; charset=utf-8";
3664 		_prolog = `<?xml version="1.0" encoding="UTF-8"?>` ~ "\n";
3665 
3666 		parseStrict(data);
3667 	}
3668 }
3669 
3670 
3671 
3672 
3673 import std.string;
3674 
3675 /* domconvenience follows { */
3676 
3677 /// finds comments that match the given txt. Case insensitive, strips whitespace.
3678 /// Group: core_functionality
3679 Element[] findComments(Document document, string txt) {
3680 	return findComments(document.root, txt);
3681 }
3682 
3683 /// ditto
3684 Element[] findComments(Element element, string txt) {
3685 	txt = txt.strip().toLower();
3686 	Element[] ret;
3687 
3688 	foreach(comment; element.getElementsByTagName("#comment")) {
3689 		string t = comment.nodeValue().strip().toLower();
3690 		if(t == txt)
3691 			ret ~= comment;
3692 	}
3693 
3694 	return ret;
3695 }
3696 
3697 /// An option type that propagates null. See: [Element.optionSelector]
3698 /// Group: implementations
3699 struct MaybeNullElement(SomeElementType) {
3700 	this(SomeElementType ele) {
3701 		this.element = ele;
3702 	}
3703 	SomeElementType element;
3704 
3705 	/// Forwards to the element, wit a null check inserted that propagates null.
3706 	auto opDispatch(string method, T...)(T args) {
3707 		alias type = typeof(__traits(getMember, element, method)(args));
3708 		static if(is(type : Element)) {
3709 			if(element is null)
3710 				return MaybeNullElement!type(null);
3711 			return __traits(getMember, element, method)(args);
3712 		} else static if(is(type == string)) {
3713 			if(element is null)
3714 				return cast(string) null;
3715 			return __traits(getMember, element, method)(args);
3716 		} else static if(is(type == void)) {
3717 			if(element is null)
3718 				return;
3719 			__traits(getMember, element, method)(args);
3720 		} else {
3721 			static assert(0);
3722 		}
3723 	}
3724 
3725 	/// Allows implicit casting to the wrapped element.
3726 	alias element this;
3727 }
3728 
3729 /++
3730 	A collection of elements which forwards methods to the children.
3731 +/
3732 /// Group: implementations
3733 struct ElementCollection {
3734 	///
3735 	this(Element e) {
3736 		elements = [e];
3737 	}
3738 
3739 	///
3740 	this(Element e, string selector) {
3741 		elements = e.querySelectorAll(selector);
3742 	}
3743 
3744 	///
3745 	this(Element[] e) {
3746 		elements = e;
3747 	}
3748 
3749 	Element[] elements;
3750 	//alias elements this; // let it implicitly convert to the underlying array
3751 
3752 	///
3753 	ElementCollection opIndex(string selector) {
3754 		ElementCollection ec;
3755 		foreach(e; elements)
3756 			ec.elements ~= e.getElementsBySelector(selector);
3757 		return ec;
3758 	}
3759 
3760 	///
3761 	Element opIndex(int i) {
3762 		return elements[i];
3763 	}
3764 
3765 	/// if you slice it, give the underlying array for easy forwarding of the
3766 	/// collection to range expecting algorithms or looping over.
3767 	Element[] opSlice() {
3768 		return elements;
3769 	}
3770 
3771 	/// And input range primitives so we can foreach over this
3772 	void popFront() {
3773 		elements = elements[1..$];
3774 	}
3775 
3776 	/// ditto
3777 	Element front() {
3778 		return elements[0];
3779 	}
3780 
3781 	/// ditto
3782 	bool empty() {
3783 		return !elements.length;
3784 	}
3785 
3786 	/++
3787 		Collects strings from the collection, concatenating them together
3788 		Kinda like running reduce and ~= on it.
3789 
3790 		---
3791 		document["p"].collect!"innerText";
3792 		---
3793 	+/
3794 	string collect(string method)(string separator = "") {
3795 		string text;
3796 		foreach(e; elements) {
3797 			text ~= mixin("e." ~ method);
3798 			text ~= separator;
3799 		}
3800 		return text;
3801 	}
3802 
3803 	/// Forward method calls to each individual [Element|element] of the collection
3804 	/// returns this so it can be chained.
3805 	ElementCollection opDispatch(string name, T...)(T t) {
3806 		foreach(e; elements) {
3807 			mixin("e." ~ name)(t);
3808 		}
3809 		return this;
3810 	}
3811 
3812 	/++
3813 		Calls [Element.wrapIn] on each member of the collection, but clones the argument `what` for each one.
3814 	+/
3815 	ElementCollection wrapIn(Element what) {
3816 		foreach(e; elements) {
3817 			e.wrapIn(what.cloneNode(false));
3818 		}
3819 
3820 		return this;
3821 	}
3822 
3823 	/// Concatenates two ElementCollection together.
3824 	ElementCollection opBinary(string op : "~")(ElementCollection rhs) {
3825 		return ElementCollection(this.elements ~ rhs.elements);
3826 	}
3827 }
3828 
3829 
3830 /// this puts in operators and opDispatch to handle string indexes and properties, forwarding to get and set functions.
3831 /// Group: implementations
3832 mixin template JavascriptStyleDispatch() {
3833 	///
3834 	string opDispatch(string name)(string v = null) if(name != "popFront") { // popFront will make this look like a range. Do not want.
3835 		if(v !is null)
3836 			return set(name, v);
3837 		return get(name);
3838 	}
3839 
3840 	///
3841 	string opIndex(string key) const {
3842 		return get(key);
3843 	}
3844 
3845 	///
3846 	string opIndexAssign(string value, string field) {
3847 		return set(field, value);
3848 	}
3849 
3850 	// FIXME: doesn't seem to work
3851 	string* opBinary(string op)(string key)  if(op == "in") {
3852 		return key in fields;
3853 	}
3854 }
3855 
3856 /// A proxy object to do the Element class' dataset property. See Element.dataset for more info.
3857 ///
3858 /// Do not create this object directly.
3859 /// Group: implementations
3860 struct DataSet {
3861 	///
3862 	this(Element e) {
3863 		this._element = e;
3864 	}
3865 
3866 	private Element _element;
3867 	///
3868 	string set(string name, string value) {
3869 		_element.setAttribute("data-" ~ unCamelCase(name), value);
3870 		return value;
3871 	}
3872 
3873 	///
3874 	string get(string name) const {
3875 		return _element.getAttribute("data-" ~ unCamelCase(name));
3876 	}
3877 
3878 	///
3879 	mixin JavascriptStyleDispatch!();
3880 }
3881 
3882 /// Proxy object for attributes which will replace the main opDispatch eventually
3883 /// Group: implementations
3884 struct AttributeSet {
3885 	///
3886 	this(Element e) {
3887 		this._element = e;
3888 	}
3889 
3890 	private Element _element;
3891 	///
3892 	string set(string name, string value) {
3893 		_element.setAttribute(name, value);
3894 		return value;
3895 	}
3896 
3897 	///
3898 	string get(string name) const {
3899 		return _element.getAttribute(name);
3900 	}
3901 
3902 	///
3903 	mixin JavascriptStyleDispatch!();
3904 }
3905 
3906 
3907 
3908 /// for style, i want to be able to set it with a string like a plain attribute,
3909 /// but also be able to do properties Javascript style.
3910 
3911 /// Group: implementations
3912 struct ElementStyle {
3913 	this(Element parent) {
3914 		_element = parent;
3915 	}
3916 
3917 	Element _element;
3918 
3919 	@property ref inout(string) _attribute() inout {
3920 		auto s = "style" in _element.attributes;
3921 		if(s is null) {
3922 			auto e = cast() _element; // const_cast
3923 			e.attributes["style"] = ""; // we need something to reference
3924 			s = cast(inout) ("style" in e.attributes);
3925 		}
3926 
3927 		assert(s !is null);
3928 		return *s;
3929 	}
3930 
3931 	alias _attribute this; // this is meant to allow element.style = element.style ~ " string "; to still work.
3932 
3933 	string set(string name, string value) {
3934 		if(name.length == 0)
3935 			return value;
3936 		if(name == "cssFloat")
3937 			name = "float";
3938 		else
3939 			name = unCamelCase(name);
3940 		auto r = rules();
3941 		r[name] = value;
3942 
3943 		_attribute = "";
3944 		foreach(k, v; r) {
3945 			if(v is null || v.length == 0) /* css can't do empty rules anyway so we'll use that to remove */
3946 				continue;
3947 			if(_attribute.length)
3948 				_attribute ~= " ";
3949 			_attribute ~= k ~ ": " ~ v ~ ";";
3950 		}
3951 
3952 		_element.setAttribute("style", _attribute); // this is to trigger the observer call
3953 
3954 		return value;
3955 	}
3956 	string get(string name) const {
3957 		if(name == "cssFloat")
3958 			name = "float";
3959 		else
3960 			name = unCamelCase(name);
3961 		auto r = rules();
3962 		if(name in r)
3963 			return r[name];
3964 		return null;
3965 	}
3966 
3967 	string[string] rules() const {
3968 		string[string] ret;
3969 		foreach(rule;  _attribute.split(";")) {
3970 			rule = rule.strip();
3971 			if(rule.length == 0)
3972 				continue;
3973 			auto idx = rule.indexOf(":");
3974 			if(idx == -1)
3975 				ret[rule] = "";
3976 			else {
3977 				auto name = rule[0 .. idx].strip();
3978 				auto value = rule[idx + 1 .. $].strip();
3979 
3980 				ret[name] = value;
3981 			}
3982 		}
3983 
3984 		return ret;
3985 	}
3986 
3987 	mixin JavascriptStyleDispatch!();
3988 }
3989 
3990 /// Converts a camel cased propertyName to a css style dashed property-name
3991 string unCamelCase(string a) {
3992 	string ret;
3993 	foreach(c; a)
3994 		if((c >= 'A' && c <= 'Z'))
3995 			ret ~= "-" ~ toLower("" ~ c)[0];
3996 		else
3997 			ret ~= c;
3998 	return ret;
3999 }
4000 
4001 /// Translates a css style property-name to a camel cased propertyName
4002 string camelCase(string a) {
4003 	string ret;
4004 	bool justSawDash = false;
4005 	foreach(c; a)
4006 		if(c == '-') {
4007 			justSawDash = true;
4008 		} else {
4009 			if(justSawDash) {
4010 				justSawDash = false;
4011 				ret ~= toUpper("" ~ c);
4012 			} else
4013 				ret ~= c;
4014 		}
4015 	return ret;
4016 }
4017 
4018 
4019 
4020 
4021 
4022 
4023 
4024 
4025 
4026 // domconvenience ends }
4027 
4028 
4029 
4030 
4031 
4032 
4033 
4034 
4035 
4036 
4037 
4038 // @safe:
4039 
4040 // NOTE: do *NOT* override toString on Element subclasses. It won't work.
4041 // Instead, override writeToAppender();
4042 
4043 // FIXME: should I keep processing instructions like <?blah ?> and <!-- blah --> (comments too lol)? I *want* them stripped out of most my output, but I want to be able to parse and create them too.
4044 
4045 // Stripping them is useful for reading php as html.... but adding them
4046 // is good for building php.
4047 
4048 // I need to maintain compatibility with the way it is now too.
4049 
4050 import std.string;
4051 import std.exception;
4052 import std.uri;
4053 import std.array;
4054 import std.range;
4055 
4056 //import std.stdio;
4057 
4058 // tag soup works for most the crap I know now! If you have two bad closing tags back to back, it might erase one, but meh
4059 // that's rarer than the flipped closing tags that hack fixes so I'm ok with it. (Odds are it should be erased anyway; it's
4060 // most likely a typo so I say kill kill kill.
4061 
4062 
4063 /++
4064 	This might belong in another module, but it represents a file with a mime type and some data.
4065 	Document implements this interface with type = text/html (see Document.contentType for more info)
4066 	and data = document.toString, so you can return Documents anywhere web.d expects FileResources.
4067 +/
4068 /// Group: bonus_functionality
4069 interface FileResource {
4070 	/// the content-type of the file. e.g. "text/html; charset=utf-8" or "image/png"
4071 	@property string contentType() const;
4072 	/// the data
4073 	immutable(ubyte)[] getData() const;
4074 	/++
4075 		filename, return null if none
4076 
4077 		History:
4078 			Added December 25, 2020
4079 	+/
4080 	@property string filename() const;
4081 }
4082 
4083 
4084 
4085 
4086 ///.
4087 /// Group: bonus_functionality
4088 enum NodeType { Text = 3 }
4089 
4090 
4091 /// You can use this to do an easy null check or a dynamic cast+null check on any element.
4092 /// Group: core_functionality
4093 T require(T = Element, string file = __FILE__, int line = __LINE__)(Element e) if(is(T : Element))
4094 	in {}
4095 	out(ret) { assert(ret !is null); }
4096 do {
4097 	auto ret = cast(T) e;
4098 	if(ret is null)
4099 		throw new ElementNotFoundException(T.stringof, "passed value", e, file, line);
4100 	return ret;
4101 }
4102 
4103 
4104 ///.
4105 /// Group: core_functionality
4106 class DocumentFragment : Element {
4107 	///.
4108 	this(Document _parentDocument) {
4109 		tagName = "#fragment";
4110 		super(_parentDocument);
4111 	}
4112 
4113 	/++
4114 		Creates a document fragment from the given HTML. Note that the HTML is assumed to close all tags contained inside it.
4115 
4116 		Since: March 29, 2018 (or git tagged v2.1.0)
4117 	+/
4118 	this(Html html) {
4119 		this(null);
4120 
4121 		this.innerHTML = html.source;
4122 	}
4123 
4124 	///.
4125 	override string writeToAppender(Appender!string where = appender!string()) const {
4126 		return this.innerHTML(where);
4127 	}
4128 
4129 	override string toPrettyStringImpl(bool insertComments, int indentationLevel, string indentWith) const {
4130 		string s;
4131 		foreach(child; children)
4132 			s ~= child.toPrettyStringImpl(insertComments, indentationLevel, indentWith);
4133 		return s;
4134 	}
4135 
4136 	/// DocumentFragments don't really exist in a dom, so they ignore themselves in parent nodes
4137 	/*
4138 	override inout(Element) parentNode() inout {
4139 		return children.length ? children[0].parentNode : null;
4140 	}
4141 	*/
4142 	/+
4143 	override Element parentNode(Element p) {
4144 		this.parentNode = p;
4145 		foreach(child; children)
4146 			child.parentNode = p;
4147 		return p;
4148 	}
4149 	+/
4150 }
4151 
4152 /// Given text, encode all html entities on it - &, <, >, and ". This function also
4153 /// encodes all 8 bit characters as entities, thus ensuring the resultant text will work
4154 /// even if your charset isn't set right. You can suppress with by setting encodeNonAscii = false
4155 ///
4156 /// The output parameter can be given to append to an existing buffer. You don't have to
4157 /// pass one; regardless, the return value will be usable for you, with just the data encoded.
4158 /// Group: core_functionality
4159 string htmlEntitiesEncode(string data, Appender!string output = appender!string(), bool encodeNonAscii = true) {
4160 	// if there's no entities, we can save a lot of time by not bothering with the
4161 	// decoding loop. This check cuts the net toString time by better than half in my test.
4162 	// let me know if it made your tests worse though, since if you use an entity in just about
4163 	// every location, the check will add time... but I suspect the average experience is like mine
4164 	// since the check gives up as soon as it can anyway.
4165 
4166 	bool shortcut = true;
4167 	foreach(char c; data) {
4168 		// non ascii chars are always higher than 127 in utf8; we'd better go to the full decoder if we see it.
4169 		if(c == '<' || c == '>' || c == '"' || c == '&' || (encodeNonAscii && cast(uint) c > 127)) {
4170 			shortcut = false; // there's actual work to be done
4171 			break;
4172 		}
4173 	}
4174 
4175 	if(shortcut) {
4176 		output.put(data);
4177 		return data;
4178 	}
4179 
4180 	auto start = output.data.length;
4181 
4182 	output.reserve(data.length + 64); // grab some extra space for the encoded entities
4183 
4184 	foreach(dchar d; data) {
4185 		if(d == '&')
4186 			output.put("&amp;");
4187 		else if (d == '<')
4188 			output.put("&lt;");
4189 		else if (d == '>')
4190 			output.put("&gt;");
4191 		else if (d == '\"')
4192 			output.put("&quot;");
4193 //		else if (d == '\'')
4194 //			output.put("&#39;"); // if you are in an attribute, it might be important to encode for the same reason as double quotes
4195 			// FIXME: should I encode apostrophes too? as &#39;... I could also do space but if your html is so bad that it doesn't
4196 			// quote attributes at all, maybe you deserve the xss. Encoding spaces will make everything really ugly so meh
4197 			// idk about apostrophes though. Might be worth it, might not.
4198 		else if (!encodeNonAscii || (d < 128 && d > 0))
4199 			output.put(d);
4200 		else
4201 			output.put("&#" ~ std.conv.to!string(cast(int) d) ~ ";");
4202 	}
4203 
4204 	//assert(output !is null); // this fails on empty attributes.....
4205 	return output.data[start .. $];
4206 
4207 //	data = data.replace("\u00a0", "&nbsp;");
4208 }
4209 
4210 /// An alias for htmlEntitiesEncode; it works for xml too
4211 /// Group: core_functionality
4212 string xmlEntitiesEncode(string data) {
4213 	return htmlEntitiesEncode(data);
4214 }
4215 
4216 /// This helper function is used for decoding html entities. It has a hard-coded list of entities and characters.
4217 /// Group: core_functionality
4218 dchar parseEntity(in dchar[] entity) {
4219 
4220 	char[128] buffer;
4221 	int bpos;
4222 	foreach(char c; entity[1 .. $-1])
4223 		buffer[bpos++] = c;
4224 	char[] entityAsString = buffer[0 .. bpos];
4225 
4226 	int min = 0;
4227 	int max = cast(int) availableEntities.length;
4228 
4229 	keep_looking:
4230 	if(min + 1 < max) {
4231 		int spot = (max - min) / 2 + min;
4232 		if(availableEntities[spot] == entityAsString) {
4233 			return availableEntitiesValues[spot];
4234 		} else if(entityAsString < availableEntities[spot]) {
4235 			max = spot;
4236 			goto keep_looking;
4237 		} else {
4238 			min = spot;
4239 			goto keep_looking;
4240 		}
4241 	}
4242 
4243 	switch(entity[1..$-1]) {
4244 		case "quot":
4245 			return '"';
4246 		case "apos":
4247 			return '\'';
4248 		case "lt":
4249 			return '<';
4250 		case "gt":
4251 			return '>';
4252 		case "amp":
4253 			return '&';
4254 		// the next are html rather than xml
4255 
4256 		// and handling numeric entities
4257 		default:
4258 			if(entity[1] == '#') {
4259 				if(entity[2] == 'x' /*|| (!strict && entity[2] == 'X')*/) {
4260 					auto hex = entity[3..$-1];
4261 
4262 					auto p = intFromHex(to!string(hex).toLower());
4263 					return cast(dchar) p;
4264 				} else {
4265 					auto decimal = entity[2..$-1];
4266 
4267 					// dealing with broken html entities
4268 					while(decimal.length && (decimal[0] < '0' || decimal[0] >   '9'))
4269 						decimal = decimal[1 .. $];
4270 
4271 					if(decimal.length == 0)
4272 						return ' '; // this is really broken html
4273 					// done with dealing with broken stuff
4274 
4275 					auto p = std.conv.to!int(decimal);
4276 					return cast(dchar) p;
4277 				}
4278 			} else
4279 				return '\ufffd'; // replacement character diamond thing
4280 	}
4281 
4282 	assert(0);
4283 }
4284 
4285 unittest {
4286 	// not in the binary search
4287 	assert(parseEntity("&quot;"d) == '"');
4288 
4289 	// numeric value
4290 	assert(parseEntity("&#x0534;") == '\u0534');
4291 
4292 	// not found at all
4293 	assert(parseEntity("&asdasdasd;"d) == '\ufffd');
4294 
4295 	// random values in the bin search
4296 	assert(parseEntity("&Tab;"d) == '\t');
4297 	assert(parseEntity("&raquo;"d) == '\&raquo;');
4298 
4299 	// near the middle and edges of the bin search
4300 	assert(parseEntity("&ascr;"d) == '\U0001d4b6');
4301 	assert(parseEntity("&ast;"d) == '\u002a');
4302 	assert(parseEntity("&AElig;"d) == '\u00c6');
4303 	assert(parseEntity("&zwnj;"d) == '\u200c');
4304 }
4305 
4306 import std.utf;
4307 import std.stdio;
4308 
4309 /// This takes a string of raw HTML and decodes the entities into a nice D utf-8 string.
4310 /// By default, it uses loose mode - it will try to return a useful string from garbage input too.
4311 /// Set the second parameter to true if you'd prefer it to strictly throw exceptions on garbage input.
4312 /// Group: core_functionality
4313 string htmlEntitiesDecode(string data, bool strict = false) {
4314 	// this check makes a *big* difference; about a 50% improvement of parse speed on my test.
4315 	if(data.indexOf("&") == -1) // all html entities begin with &
4316 		return data; // if there are no entities in here, we can return the original slice and save some time
4317 
4318 	char[] a; // this seems to do a *better* job than appender!
4319 
4320 	char[4] buffer;
4321 
4322 	bool tryingEntity = false;
4323 	dchar[16] entityBeingTried;
4324 	int entityBeingTriedLength = 0;
4325 	int entityAttemptIndex = 0;
4326 
4327 	foreach(dchar ch; data) {
4328 		if(tryingEntity) {
4329 			entityAttemptIndex++;
4330 			entityBeingTried[entityBeingTriedLength++] = ch;
4331 
4332 			// I saw some crappy html in the wild that looked like &0&#1111; this tries to handle that.
4333 			if(ch == '&') {
4334 				if(strict)
4335 					throw new Exception("unterminated entity; & inside another at " ~ to!string(entityBeingTried[0 .. entityBeingTriedLength]));
4336 
4337 				// if not strict, let's try to parse both.
4338 
4339 				if(entityBeingTried[0 .. entityBeingTriedLength] == "&&")
4340 					a ~= "&"; // double amp means keep the first one, still try to parse the next one
4341 				else
4342 					a ~= buffer[0.. std.utf.encode(buffer, parseEntity(entityBeingTried[0 .. entityBeingTriedLength]))];
4343 
4344 				// tryingEntity is still true
4345 				entityBeingTriedLength = 1;
4346 				entityAttemptIndex = 0; // restarting o this
4347 			} else
4348 			if(ch == ';') {
4349 				tryingEntity = false;
4350 				a ~= buffer[0.. std.utf.encode(buffer, parseEntity(entityBeingTried[0 .. entityBeingTriedLength]))];
4351 			} else if(ch == ' ') {
4352 				// e.g. you &amp i
4353 				if(strict)
4354 					throw new Exception("unterminated entity at " ~ to!string(entityBeingTried[0 .. entityBeingTriedLength]));
4355 				else {
4356 					tryingEntity = false;
4357 					a ~= to!(char[])(entityBeingTried[0 .. entityBeingTriedLength]);
4358 				}
4359 			} else {
4360 				if(entityAttemptIndex >= 9) {
4361 					if(strict)
4362 						throw new Exception("unterminated entity at " ~ to!string(entityBeingTried[0 .. entityBeingTriedLength]));
4363 					else {
4364 						tryingEntity = false;
4365 						a ~= to!(char[])(entityBeingTried[0 .. entityBeingTriedLength]);
4366 					}
4367 				}
4368 			}
4369 		} else {
4370 			if(ch == '&') {
4371 				tryingEntity = true;
4372 				entityBeingTriedLength = 0;
4373 				entityBeingTried[entityBeingTriedLength++] = ch;
4374 				entityAttemptIndex = 0;
4375 			} else {
4376 				a ~= buffer[0 .. std.utf.encode(buffer, ch)];
4377 			}
4378 		}
4379 	}
4380 
4381 	if(tryingEntity) {
4382 		if(strict)
4383 			throw new Exception("unterminated entity at " ~ to!string(entityBeingTried[0 .. entityBeingTriedLength]));
4384 
4385 		// otherwise, let's try to recover, at least so we don't drop any data
4386 		a ~= to!string(entityBeingTried[0 .. entityBeingTriedLength]);
4387 		// FIXME: what if we have "cool &amp"? should we try to parse it?
4388 	}
4389 
4390 	return cast(string) a; // assumeUnique is actually kinda slow, lol
4391 }
4392 
4393 /// Group: implementations
4394 abstract class SpecialElement : Element {
4395 	this(Document _parentDocument) {
4396 		super(_parentDocument);
4397 	}
4398 
4399 	///.
4400 	override Element appendChild(Element e) {
4401 		assert(0, "Cannot append to a special node");
4402 	}
4403 
4404 	///.
4405 	@property override int nodeType() const {
4406 		return 100;
4407 	}
4408 }
4409 
4410 ///.
4411 /// Group: implementations
4412 class RawSource : SpecialElement {
4413 	///.
4414 	this(Document _parentDocument, string s) {
4415 		super(_parentDocument);
4416 		source = s;
4417 		tagName = "#raw";
4418 	}
4419 
4420 	///.
4421 	override string nodeValue() const {
4422 		return this.toString();
4423 	}
4424 
4425 	///.
4426 	override string writeToAppender(Appender!string where = appender!string()) const {
4427 		where.put(source);
4428 		return source;
4429 	}
4430 
4431 	override string toPrettyStringImpl(bool, int, string) const {
4432 		return source;
4433 	}
4434 
4435 
4436 	override RawSource cloneNode(bool deep) {
4437 		return new RawSource(parentDocument, source);
4438 	}
4439 
4440 	///.
4441 	string source;
4442 }
4443 
4444 /// Group: implementations
4445 abstract class ServerSideCode : SpecialElement {
4446 	this(Document _parentDocument, string type) {
4447 		super(_parentDocument);
4448 		tagName = "#" ~ type;
4449 	}
4450 
4451 	///.
4452 	override string nodeValue() const {
4453 		return this.source;
4454 	}
4455 
4456 	///.
4457 	override string writeToAppender(Appender!string where = appender!string()) const {
4458 		auto start = where.data.length;
4459 		where.put("<");
4460 		where.put(source);
4461 		where.put(">");
4462 		return where.data[start .. $];
4463 	}
4464 
4465 	override string toPrettyStringImpl(bool, int, string) const {
4466 		return "<" ~ source ~ ">";
4467 	}
4468 
4469 	///.
4470 	string source;
4471 }
4472 
4473 ///.
4474 /// Group: implementations
4475 class PhpCode : ServerSideCode {
4476 	///.
4477 	this(Document _parentDocument, string s) {
4478 		super(_parentDocument, "php");
4479 		source = s;
4480 	}
4481 
4482 	override PhpCode cloneNode(bool deep) {
4483 		return new PhpCode(parentDocument, source);
4484 	}
4485 }
4486 
4487 ///.
4488 /// Group: implementations
4489 class AspCode : ServerSideCode {
4490 	///.
4491 	this(Document _parentDocument, string s) {
4492 		super(_parentDocument, "asp");
4493 		source = s;
4494 	}
4495 
4496 	override AspCode cloneNode(bool deep) {
4497 		return new AspCode(parentDocument, source);
4498 	}
4499 }
4500 
4501 ///.
4502 /// Group: implementations
4503 class BangInstruction : SpecialElement {
4504 	///.
4505 	this(Document _parentDocument, string s) {
4506 		super(_parentDocument);
4507 		source = s;
4508 		tagName = "#bpi";
4509 	}
4510 
4511 	///.
4512 	override string nodeValue() const {
4513 		return this.source;
4514 	}
4515 
4516 	override BangInstruction cloneNode(bool deep) {
4517 		return new BangInstruction(parentDocument, source);
4518 	}
4519 
4520 	///.
4521 	override string writeToAppender(Appender!string where = appender!string()) const {
4522 		auto start = where.data.length;
4523 		where.put("<!");
4524 		where.put(source);
4525 		where.put(">");
4526 		return where.data[start .. $];
4527 	}
4528 
4529 	override string toPrettyStringImpl(bool, int, string) const {
4530 		string s;
4531 		s ~= "<!";
4532 		s ~= source;
4533 		s ~= ">";
4534 		return s;
4535 	}
4536 
4537 	///.
4538 	string source;
4539 }
4540 
4541 ///.
4542 /// Group: implementations
4543 class QuestionInstruction : SpecialElement {
4544 	///.
4545 	this(Document _parentDocument, string s) {
4546 		super(_parentDocument);
4547 		source = s;
4548 		tagName = "#qpi";
4549 	}
4550 
4551 	override QuestionInstruction cloneNode(bool deep) {
4552 		return new QuestionInstruction(parentDocument, source);
4553 	}
4554 
4555 	///.
4556 	override string nodeValue() const {
4557 		return this.source;
4558 	}
4559 
4560 	///.
4561 	override string writeToAppender(Appender!string where = appender!string()) const {
4562 		auto start = where.data.length;
4563 		where.put("<");
4564 		where.put(source);
4565 		where.put(">");
4566 		return where.data[start .. $];
4567 	}
4568 
4569 	override string toPrettyStringImpl(bool, int, string) const {
4570 		string s;
4571 		s ~= "<";
4572 		s ~= source;
4573 		s ~= ">";
4574 		return s;
4575 	}
4576 
4577 
4578 	///.
4579 	string source;
4580 }
4581 
4582 ///.
4583 /// Group: implementations
4584 class HtmlComment : SpecialElement {
4585 	///.
4586 	this(Document _parentDocument, string s) {
4587 		super(_parentDocument);
4588 		source = s;
4589 		tagName = "#comment";
4590 	}
4591 
4592 	override HtmlComment cloneNode(bool deep) {
4593 		return new HtmlComment(parentDocument, source);
4594 	}
4595 
4596 	///.
4597 	override string nodeValue() const {
4598 		return this.source;
4599 	}
4600 
4601 	///.
4602 	override string writeToAppender(Appender!string where = appender!string()) const {
4603 		auto start = where.data.length;
4604 		where.put("<!--");
4605 		where.put(source);
4606 		where.put("-->");
4607 		return where.data[start .. $];
4608 	}
4609 
4610 	override string toPrettyStringImpl(bool, int, string) const {
4611 		string s;
4612 		s ~= "<!--";
4613 		s ~= source;
4614 		s ~= "-->";
4615 		return s;
4616 	}
4617 
4618 
4619 	///.
4620 	string source;
4621 }
4622 
4623 
4624 
4625 
4626 ///.
4627 /// Group: implementations
4628 class TextNode : Element {
4629   public:
4630 	///.
4631 	this(Document _parentDocument, string e) {
4632 		super(_parentDocument);
4633 		contents = e;
4634 		tagName = "#text";
4635 	}
4636 
4637 	///
4638 	this(string e) {
4639 		this(null, e);
4640 	}
4641 
4642 	string opDispatch(string name)(string v = null) if(0) { return null; } // text nodes don't have attributes
4643 
4644 	///.
4645 	static TextNode fromUndecodedString(Document _parentDocument, string html) {
4646 		auto e = new TextNode(_parentDocument, "");
4647 		e.contents = htmlEntitiesDecode(html, _parentDocument is null ? false : !_parentDocument.loose);
4648 		return e;
4649 	}
4650 
4651 	///.
4652 	override @property TextNode cloneNode(bool deep) {
4653 		auto n = new TextNode(parentDocument, contents);
4654 		return n;
4655 	}
4656 
4657 	///.
4658 	override string nodeValue() const {
4659 		return this.contents; //toString();
4660 	}
4661 
4662 	///.
4663 	@property override int nodeType() const {
4664 		return NodeType.Text;
4665 	}
4666 
4667 	///.
4668 	override string writeToAppender(Appender!string where = appender!string()) const {
4669 		string s;
4670 		if(contents.length)
4671 			s = htmlEntitiesEncode(contents, where);
4672 		else
4673 			s = "";
4674 
4675 		assert(s !is null);
4676 		return s;
4677 	}
4678 
4679 	override string toPrettyStringImpl(bool insertComments = false, int indentationLevel = 0, string indentWith = "\t") const {
4680 		string s;
4681 
4682 		string contents = this.contents;
4683 		// we will first collapse the whitespace per html
4684 		// sort of. note this can break stuff yo!!!!
4685 		if(this.parentNode is null || this.parentNode.tagName != "pre") {
4686 			string n = "";
4687 			bool lastWasWhitespace = indentationLevel > 0;
4688 			foreach(char c; contents) {
4689 				if(c.isSimpleWhite) {
4690 					if(!lastWasWhitespace)
4691 						n ~= ' ';
4692 					lastWasWhitespace = true;
4693 				} else {
4694 					n ~= c;
4695 					lastWasWhitespace = false;
4696 				}
4697 			}
4698 
4699 			contents = n;
4700 		}
4701 
4702 		if(this.parentNode !is null && this.parentNode.tagName != "p") {
4703 			contents = contents.strip;
4704 		}
4705 
4706 		auto e = htmlEntitiesEncode(contents);
4707 		import std.algorithm.iteration : splitter;
4708 		bool first = true;
4709 		foreach(line; splitter(e, "\n")) {
4710 			if(first) {
4711 				s ~= toPrettyStringIndent(insertComments, indentationLevel, indentWith);
4712 				first = false;
4713 			} else {
4714 				s ~= "\n";
4715 				if(insertComments)
4716 					s ~= "<!--";
4717 				foreach(i; 0 .. indentationLevel)
4718 					s ~= "\t";
4719 				if(insertComments)
4720 					s ~= "-->";
4721 			}
4722 			s ~= line.stripRight;
4723 		}
4724 		return s;
4725 	}
4726 
4727 	///.
4728 	override Element appendChild(Element e) {
4729 		assert(0, "Cannot append to a text node");
4730 	}
4731 
4732 	///.
4733 	string contents;
4734 	// alias contents content; // I just mistype this a lot,
4735 }
4736 
4737 /**
4738 	There are subclasses of Element offering improved helper
4739 	functions for the element in HTML.
4740 */
4741 
4742 ///.
4743 /// Group: implementations
4744 class Link : Element {
4745 
4746 	///.
4747 	this(Document _parentDocument) {
4748 		super(_parentDocument);
4749 		this.tagName = "a";
4750 	}
4751 
4752 
4753 	///.
4754 	this(string href, string text) {
4755 		super("a");
4756 		setAttribute("href", href);
4757 		innerText = text;
4758 	}
4759 /+
4760 	/// Returns everything in the href EXCEPT the query string
4761 	@property string targetSansQuery() {
4762 
4763 	}
4764 
4765 	///.
4766 	@property string domainName() {
4767 
4768 	}
4769 
4770 	///.
4771 	@property string path
4772 +/
4773 	/// This gets a variable from the URL's query string.
4774 	string getValue(string name) {
4775 		auto vars = variablesHash();
4776 		if(name in vars)
4777 			return vars[name];
4778 		return null;
4779 	}
4780 
4781 	private string[string] variablesHash() {
4782 		string href = getAttribute("href");
4783 		if(href is null)
4784 			return null;
4785 
4786 		auto ques = href.indexOf("?");
4787 		string str = "";
4788 		if(ques != -1) {
4789 			str = href[ques+1..$];
4790 
4791 			auto fragment = str.indexOf("#");
4792 			if(fragment != -1)
4793 				str = str[0..fragment];
4794 		}
4795 
4796 		string[] variables = str.split("&");
4797 
4798 		string[string] hash;
4799 
4800 		foreach(var; variables) {
4801 			auto index = var.indexOf("=");
4802 			if(index == -1)
4803 				hash[var] = "";
4804 			else {
4805 				hash[decodeComponent(var[0..index])] = decodeComponent(var[index + 1 .. $]);
4806 			}
4807 		}
4808 
4809 		return hash;
4810 	}
4811 
4812 	///.
4813 	/*private*/ void updateQueryString(string[string] vars) {
4814 		string href = getAttribute("href");
4815 
4816 		auto question = href.indexOf("?");
4817 		if(question != -1)
4818 			href = href[0..question];
4819 
4820 		string frag = "";
4821 		auto fragment = href.indexOf("#");
4822 		if(fragment != -1) {
4823 			frag = href[fragment..$];
4824 			href = href[0..fragment];
4825 		}
4826 
4827 		string query = "?";
4828 		bool first = true;
4829 		foreach(name, value; vars) {
4830 			if(!first)
4831 				query ~= "&";
4832 			else
4833 				first = false;
4834 
4835 			query ~= encodeComponent(name);
4836 			if(value.length)
4837 				query ~= "=" ~ encodeComponent(value);
4838 		}
4839 
4840 		if(query != "?")
4841 			href ~= query;
4842 
4843 		href ~= frag;
4844 
4845 		setAttribute("href", href);
4846 	}
4847 
4848 	/// Sets or adds the variable with the given name to the given value
4849 	/// It automatically URI encodes the values and takes care of the ? and &.
4850 	override void setValue(string name, string variable) {
4851 		auto vars = variablesHash();
4852 		vars[name] = variable;
4853 
4854 		updateQueryString(vars);
4855 	}
4856 
4857 	/// Removes the given variable from the query string
4858 	void removeValue(string name) {
4859 		auto vars = variablesHash();
4860 		vars.remove(name);
4861 
4862 		updateQueryString(vars);
4863 	}
4864 
4865 	/*
4866 	///.
4867 	override string toString() {
4868 
4869 	}
4870 
4871 	///.
4872 	override string getAttribute(string name) {
4873 		if(name == "href") {
4874 
4875 		} else
4876 			return super.getAttribute(name);
4877 	}
4878 	*/
4879 }
4880 
4881 ///.
4882 /// Group: implementations
4883 class Form : Element {
4884 
4885 	///.
4886 	this(Document _parentDocument) {
4887 		super(_parentDocument);
4888 		tagName = "form";
4889 	}
4890 
4891 	override Element addField(string label, string name, string type = "text", FormFieldOptions fieldOptions = FormFieldOptions.none) {
4892 		auto t = this.querySelector("fieldset div");
4893 		if(t is null)
4894 			return super.addField(label, name, type, fieldOptions);
4895 		else
4896 			return t.addField(label, name, type, fieldOptions);
4897 	}
4898 
4899 	override Element addField(string label, string name, FormFieldOptions fieldOptions) {
4900 		auto type = "text";
4901 		auto t = this.querySelector("fieldset div");
4902 		if(t is null)
4903 			return super.addField(label, name, type, fieldOptions);
4904 		else
4905 			return t.addField(label, name, type, fieldOptions);
4906 	}
4907 
4908 	override Element addField(string label, string name, string[string] options, FormFieldOptions fieldOptions = FormFieldOptions.none) {
4909 		auto t = this.querySelector("fieldset div");
4910 		if(t is null)
4911 			return super.addField(label, name, options, fieldOptions);
4912 		else
4913 			return t.addField(label, name, options, fieldOptions);
4914 	}
4915 
4916 	override void setValue(string field, string value) {
4917 		setValue(field, value, true);
4918 	}
4919 
4920 	// FIXME: doesn't handle arrays; multiple fields can have the same name
4921 
4922 	/// Set's the form field's value. For input boxes, this sets the value attribute. For
4923 	/// textareas, it sets the innerText. For radio boxes and select boxes, it removes
4924 	/// the checked/selected attribute from all, and adds it to the one matching the value.
4925 	/// For checkboxes, if the value is non-null and not empty, it checks the box.
4926 
4927 	/// If you set a value that doesn't exist, it throws an exception if makeNew is false.
4928 	/// Otherwise, it makes a new input with type=hidden to keep the value.
4929 	void setValue(string field, string value, bool makeNew) {
4930 		auto eles = getField(field);
4931 		if(eles.length == 0) {
4932 			if(makeNew) {
4933 				addInput(field, value);
4934 				return;
4935 			} else
4936 				throw new Exception("form field does not exist");
4937 		}
4938 
4939 		if(eles.length == 1) {
4940 			auto e = eles[0];
4941 			switch(e.tagName) {
4942 				default: assert(0);
4943 				case "textarea":
4944 					e.innerText = value;
4945 				break;
4946 				case "input":
4947 					string type = e.getAttribute("type");
4948 					if(type is null) {
4949 						e.value = value;
4950 						return;
4951 					}
4952 					switch(type) {
4953 						case "checkbox":
4954 						case "radio":
4955 							if(value.length && value != "false")
4956 								e.setAttribute("checked", "checked");
4957 							else
4958 								e.removeAttribute("checked");
4959 						break;
4960 						default:
4961 							e.value = value;
4962 							return;
4963 					}
4964 				break;
4965 				case "select":
4966 					bool found = false;
4967 					foreach(child; e.tree) {
4968 						if(child.tagName != "option")
4969 							continue;
4970 						string val = child.getAttribute("value");
4971 						if(val is null)
4972 							val = child.innerText;
4973 						if(val == value) {
4974 							child.setAttribute("selected", "selected");
4975 							found = true;
4976 						} else
4977 							child.removeAttribute("selected");
4978 					}
4979 
4980 					if(!found) {
4981 						e.addChild("option", value)
4982 						.setAttribute("selected", "selected");
4983 					}
4984 				break;
4985 			}
4986 		} else {
4987 			// assume radio boxes
4988 			foreach(e; eles) {
4989 				string val = e.getAttribute("value");
4990 				//if(val is null)
4991 				//	throw new Exception("don't know what to do with radio boxes with null value");
4992 				if(val == value)
4993 					e.setAttribute("checked", "checked");
4994 				else
4995 					e.removeAttribute("checked");
4996 			}
4997 		}
4998 	}
4999 
5000 	/// This takes an array of strings and adds hidden <input> elements for each one of them. Unlike setValue,
5001 	/// it makes no attempt to find and modify existing elements in the form to the new values.
5002 	void addValueArray(string key, string[] arrayOfValues) {
5003 		foreach(arr; arrayOfValues)
5004 			addChild("input", key, arr);
5005 	}
5006 
5007 	/// Gets the value of the field; what would be given if it submitted right now. (so
5008 	/// it handles select boxes and radio buttons too). For checkboxes, if a value isn't
5009 	/// given, but it is checked, it returns "checked", since null and "" are indistinguishable
5010 	string getValue(string field) {
5011 		auto eles = getField(field);
5012 		if(eles.length == 0)
5013 			return "";
5014 		if(eles.length == 1) {
5015 			auto e = eles[0];
5016 			switch(e.tagName) {
5017 				default: assert(0);
5018 				case "input":
5019 					if(e.type == "checkbox") {
5020 						if(e.checked)
5021 							return e.value.length ? e.value : "checked";
5022 						return "";
5023 					} else
5024 						return e.value;
5025 				case "textarea":
5026 					return e.innerText;
5027 				case "select":
5028 					foreach(child; e.tree) {
5029 						if(child.tagName != "option")
5030 							continue;
5031 						if(child.selected)
5032 							return child.value;
5033 					}
5034 				break;
5035 			}
5036 		} else {
5037 			// assuming radio
5038 			foreach(e; eles) {
5039 				if(e.checked)
5040 					return e.value;
5041 			}
5042 		}
5043 
5044 		return "";
5045 	}
5046 
5047 	// FIXME: doesn't handle multiple elements with the same name (except radio buttons)
5048 	///.
5049 	string getPostableData() {
5050 		bool[string] namesDone;
5051 
5052 		string ret;
5053 		bool outputted = false;
5054 
5055 		foreach(e; getElementsBySelector("[name]")) {
5056 			if(e.name in namesDone)
5057 				continue;
5058 
5059 			if(outputted)
5060 				ret ~= "&";
5061 			else
5062 				outputted = true;
5063 
5064 			ret ~= std.uri.encodeComponent(e.name) ~ "=" ~ std.uri.encodeComponent(getValue(e.name));
5065 
5066 			namesDone[e.name] = true;
5067 		}
5068 
5069 		return ret;
5070 	}
5071 
5072 	/// Gets the actual elements with the given name
5073 	Element[] getField(string name) {
5074 		Element[] ret;
5075 		foreach(e; tree) {
5076 			if(e.name == name)
5077 				ret ~= e;
5078 		}
5079 		return ret;
5080 	}
5081 
5082 	/// Grabs the <label> with the given for tag, if there is one.
5083 	Element getLabel(string forId) {
5084 		foreach(e; tree)
5085 			if(e.tagName == "label" && e.getAttribute("for") == forId)
5086 				return e;
5087 		return null;
5088 	}
5089 
5090 	/// Adds a new INPUT field to the end of the form with the given attributes.
5091 	Element addInput(string name, string value, string type = "hidden") {
5092 		auto e = new Element(parentDocument, "input", null, true);
5093 		e.name = name;
5094 		e.value = value;
5095 		e.type = type;
5096 
5097 		appendChild(e);
5098 
5099 		return e;
5100 	}
5101 
5102 	/// Removes the given field from the form. It finds the element and knocks it right out.
5103 	void removeField(string name) {
5104 		foreach(e; getField(name))
5105 			e.parentNode.removeChild(e);
5106 	}
5107 
5108 	/+
5109 	/// Returns all form members.
5110 	@property Element[] elements() {
5111 
5112 	}
5113 
5114 	///.
5115 	string opDispatch(string name)(string v = null)
5116 		// filter things that should actually be attributes on the form
5117 		if( name != "method" && name != "action" && name != "enctype"
5118 		 && name != "style"  && name != "name" && name != "id" && name != "class")
5119 	{
5120 
5121 	}
5122 	+/
5123 /+
5124 	void submit() {
5125 		// take its elements and submit them through http
5126 	}
5127 +/
5128 }
5129 
5130 import std.conv;
5131 
5132 ///.
5133 /// Group: implementations
5134 class Table : Element {
5135 
5136 	///.
5137 	this(Document _parentDocument) {
5138 		super(_parentDocument);
5139 		tagName = "table";
5140 	}
5141 
5142 	/// Creates an element with the given type and content.
5143 	Element th(T)(T t) {
5144 		Element e;
5145 		if(parentDocument !is null)
5146 			e = parentDocument.createElement("th");
5147 		else
5148 			e = Element.make("th");
5149 		static if(is(T == Html))
5150 			e.innerHTML = t;
5151 		else
5152 			e.innerText = to!string(t);
5153 		return e;
5154 	}
5155 
5156 	/// ditto
5157 	Element td(T)(T t) {
5158 		Element e;
5159 		if(parentDocument !is null)
5160 			e = parentDocument.createElement("td");
5161 		else
5162 			e = Element.make("td");
5163 		static if(is(T == Html))
5164 			e.innerHTML = t;
5165 		else
5166 			e.innerText = to!string(t);
5167 		return e;
5168 	}
5169 
5170 	/// .
5171 	Element appendHeaderRow(T...)(T t) {
5172 		return appendRowInternal("th", "thead", t);
5173 	}
5174 
5175 	/// .
5176 	Element appendFooterRow(T...)(T t) {
5177 		return appendRowInternal("td", "tfoot", t);
5178 	}
5179 
5180 	/// .
5181 	Element appendRow(T...)(T t) {
5182 		return appendRowInternal("td", "tbody", t);
5183 	}
5184 
5185 	void addColumnClasses(string[] classes...) {
5186 		auto grid = getGrid();
5187 		foreach(row; grid)
5188 		foreach(i, cl; classes) {
5189 			if(cl.length)
5190 			if(i < row.length)
5191 				row[i].addClass(cl);
5192 		}
5193 	}
5194 
5195 	private Element appendRowInternal(T...)(string innerType, string findType, T t) {
5196 		Element row = Element.make("tr");
5197 
5198 		foreach(e; t) {
5199 			static if(is(typeof(e) : Element)) {
5200 				if(e.tagName == "td" || e.tagName == "th")
5201 					row.appendChild(e);
5202 				else {
5203 					Element a = Element.make(innerType);
5204 
5205 					a.appendChild(e);
5206 
5207 					row.appendChild(a);
5208 				}
5209 			} else static if(is(typeof(e) == Html)) {
5210 				Element a = Element.make(innerType);
5211 				a.innerHTML = e.source;
5212 				row.appendChild(a);
5213 			} else static if(is(typeof(e) == Element[])) {
5214 				Element a = Element.make(innerType);
5215 				foreach(ele; e)
5216 					a.appendChild(ele);
5217 				row.appendChild(a);
5218 			} else static if(is(typeof(e) == string[])) {
5219 				foreach(ele; e) {
5220 					Element a = Element.make(innerType);
5221 					a.innerText = to!string(ele);
5222 					row.appendChild(a);
5223 				}
5224 			} else {
5225 				Element a = Element.make(innerType);
5226 				a.innerText = to!string(e);
5227 				row.appendChild(a);
5228 			}
5229 		}
5230 
5231 		foreach(e; children) {
5232 			if(e.tagName == findType) {
5233 				e.appendChild(row);
5234 				return row;
5235 			}
5236 		}
5237 
5238 		// the type was not found if we are here... let's add it so it is well-formed
5239 		auto lol = this.addChild(findType);
5240 		lol.appendChild(row);
5241 
5242 		return row;
5243 	}
5244 
5245 	///.
5246 	Element captionElement() {
5247 		Element cap;
5248 		foreach(c; children) {
5249 			if(c.tagName == "caption") {
5250 				cap = c;
5251 				break;
5252 			}
5253 		}
5254 
5255 		if(cap is null) {
5256 			cap = Element.make("caption");
5257 			appendChild(cap);
5258 		}
5259 
5260 		return cap;
5261 	}
5262 
5263 	///.
5264 	@property string caption() {
5265 		return captionElement().innerText;
5266 	}
5267 
5268 	///.
5269 	@property void caption(string text) {
5270 		captionElement().innerText = text;
5271 	}
5272 
5273 	/// Gets the logical layout of the table as a rectangular grid of
5274 	/// cells. It considers rowspan and colspan. A cell with a large
5275 	/// span is represented in the grid by being referenced several times.
5276 	/// The tablePortition parameter can get just a <thead>, <tbody>, or
5277 	/// <tfoot> portion if you pass one.
5278 	///
5279 	/// Note: the rectangular grid might include null cells.
5280 	///
5281 	/// This is kinda expensive so you should call once when you want the grid,
5282 	/// then do lookups on the returned array.
5283 	TableCell[][] getGrid(Element tablePortition = null)
5284 		in {
5285 			if(tablePortition is null)
5286 				assert(tablePortition is null);
5287 			else {
5288 				assert(tablePortition !is null);
5289 				assert(tablePortition.parentNode is this);
5290 				assert(
5291 					tablePortition.tagName == "tbody"
5292 					||
5293 					tablePortition.tagName == "tfoot"
5294 					||
5295 					tablePortition.tagName == "thead"
5296 				);
5297 			}
5298 		}
5299 	do {
5300 		if(tablePortition is null)
5301 			tablePortition = this;
5302 
5303 		TableCell[][] ret;
5304 
5305 		// FIXME: will also return rows of sub tables!
5306 		auto rows = tablePortition.getElementsByTagName("tr");
5307 		ret.length = rows.length;
5308 
5309 		int maxLength = 0;
5310 
5311 		int insertCell(int row, int position, TableCell cell) {
5312 			if(row >= ret.length)
5313 				return position; // not supposed to happen - a rowspan is prolly too big.
5314 
5315 			if(position == -1) {
5316 				position++;
5317 				foreach(item; ret[row]) {
5318 					if(item is null)
5319 						break;
5320 					position++;
5321 				}
5322 			}
5323 
5324 			if(position < ret[row].length)
5325 				ret[row][position] = cell;
5326 			else
5327 				foreach(i; ret[row].length .. position + 1) {
5328 					if(i == position)
5329 						ret[row] ~= cell;
5330 					else
5331 						ret[row] ~= null;
5332 				}
5333 			return position;
5334 		}
5335 
5336 		foreach(i, rowElement; rows) {
5337 			auto row = cast(TableRow) rowElement;
5338 			assert(row !is null);
5339 			assert(i < ret.length);
5340 
5341 			int position = 0;
5342 			foreach(cellElement; rowElement.childNodes) {
5343 				auto cell = cast(TableCell) cellElement;
5344 				if(cell is null)
5345 					continue;
5346 
5347 				// FIXME: colspan == 0 or rowspan == 0
5348 				// is supposed to mean fill in the rest of
5349 				// the table, not skip it
5350 				foreach(int j; 0 .. cell.colspan) {
5351 					foreach(int k; 0 .. cell.rowspan)
5352 						// if the first row, always append.
5353 						insertCell(k + cast(int) i, k == 0 ? -1 : position, cell);
5354 					position++;
5355 				}
5356 			}
5357 
5358 			if(ret[i].length > maxLength)
5359 				maxLength = cast(int) ret[i].length;
5360 		}
5361 
5362 		// want to ensure it's rectangular
5363 		foreach(ref r; ret) {
5364 			foreach(i; r.length .. maxLength)
5365 				r ~= null;
5366 		}
5367 
5368 		return ret;
5369 	}
5370 }
5371 
5372 /// Represents a table row element - a <tr>
5373 /// Group: implementations
5374 class TableRow : Element {
5375 	///.
5376 	this(Document _parentDocument) {
5377 		super(_parentDocument);
5378 		tagName = "tr";
5379 	}
5380 
5381 	// FIXME: the standard says there should be a lot more in here,
5382 	// but meh, I never use it and it's a pain to implement.
5383 }
5384 
5385 /// Represents anything that can be a table cell - <td> or <th> html.
5386 /// Group: implementations
5387 class TableCell : Element {
5388 	///.
5389 	this(Document _parentDocument, string _tagName) {
5390 		super(_parentDocument, _tagName);
5391 	}
5392 
5393 	@property int rowspan() const {
5394 		int ret = 1;
5395 		auto it = getAttribute("rowspan");
5396 		if(it.length)
5397 			ret = to!int(it);
5398 		return ret;
5399 	}
5400 
5401 	@property int colspan() const {
5402 		int ret = 1;
5403 		auto it = getAttribute("colspan");
5404 		if(it.length)
5405 			ret = to!int(it);
5406 		return ret;
5407 	}
5408 
5409 	@property int rowspan(int i) {
5410 		setAttribute("rowspan", to!string(i));
5411 		return i;
5412 	}
5413 
5414 	@property int colspan(int i) {
5415 		setAttribute("colspan", to!string(i));
5416 		return i;
5417 	}
5418 
5419 }
5420 
5421 
5422 ///.
5423 /// Group: implementations
5424 class MarkupException : Exception {
5425 
5426 	///.
5427 	this(string message, string file = __FILE__, size_t line = __LINE__) {
5428 		super(message, file, line);
5429 	}
5430 }
5431 
5432 /// This is used when you are using one of the require variants of navigation, and no matching element can be found in the tree.
5433 /// Group: implementations
5434 class ElementNotFoundException : Exception {
5435 
5436 	/// type == kind of element you were looking for and search == a selector describing the search.
5437 	this(string type, string search, Element searchContext, string file = __FILE__, size_t line = __LINE__) {
5438 		this.searchContext = searchContext;
5439 		super("Element of type '"~type~"' matching {"~search~"} not found.", file, line);
5440 	}
5441 
5442 	Element searchContext;
5443 }
5444 
5445 /// The html struct is used to differentiate between regular text nodes and html in certain functions
5446 ///
5447 /// Easiest way to construct it is like this: `auto html = Html("<p>hello</p>");`
5448 /// Group: core_functionality
5449 struct Html {
5450 	/// This string holds the actual html. Use it to retrieve the contents.
5451 	string source;
5452 }
5453 
5454 // for the observers
5455 enum DomMutationOperations {
5456 	setAttribute,
5457 	removeAttribute,
5458 	appendChild, // tagname, attributes[], innerHTML
5459 	insertBefore,
5460 	truncateChildren,
5461 	removeChild,
5462 	appendHtml,
5463 	replaceHtml,
5464 	appendText,
5465 	replaceText,
5466 	replaceTextOnly
5467 }
5468 
5469 // and for observers too
5470 struct DomMutationEvent {
5471 	DomMutationOperations operation;
5472 	Element target;
5473 	Element related; // what this means differs with the operation
5474 	Element related2;
5475 	string relatedString;
5476 	string relatedString2;
5477 }
5478 
5479 
5480 private immutable static string[] htmlSelfClosedElements = [
5481 	// html 4
5482 	"img", "hr", "input", "br", "col", "link", "meta",
5483 	// html 5
5484 	"source" ];
5485 
5486 private immutable static string[] htmlInlineElements = [
5487 	"span", "strong", "em", "b", "i", "a"
5488 ];
5489 
5490 
5491 static import std.conv;
5492 
5493 ///.
5494 int intFromHex(string hex) {
5495 	int place = 1;
5496 	int value = 0;
5497 	for(sizediff_t a = hex.length - 1; a >= 0; a--) {
5498 		int v;
5499 		char q = hex[a];
5500 		if( q >= '0' && q <= '9')
5501 			v = q - '0';
5502 		else if (q >= 'a' && q <= 'f')
5503 			v = q - 'a' + 10;
5504 		else throw new Exception("Illegal hex character: " ~ q);
5505 
5506 		value += v * place;
5507 
5508 		place *= 16;
5509 	}
5510 
5511 	return value;
5512 }
5513 
5514 
5515 // CSS selector handling
5516 
5517 // EXTENSIONS
5518 // dd - dt means get the dt directly before that dd (opposite of +)                  NOT IMPLEMENTED
5519 // dd -- dt means rewind siblings until you hit a dt, go as far as you need to       NOT IMPLEMENTED
5520 // dt < dl means get the parent of that dt iff it is a dl (usable for "get a dt that are direct children of dl")
5521 // dt << dl  means go as far up as needed to find a dl (you have an element and want its containers)      NOT IMPLEMENTED
5522 // :first  means to stop at the first hit, don't do more (so p + p == p ~ p:first
5523 
5524 
5525 
5526 // CSS4 draft currently says you can change the subject (the element actually returned) by putting a ! at the end of it.
5527 // That might be useful to implement, though I do have parent selectors too.
5528 
5529 		///.
5530 		static immutable string[] selectorTokens = [
5531 			// It is important that the 2 character possibilities go first here for accurate lexing
5532 		    "~=", "*=", "|=", "^=", "$=", "!=",
5533 		    "::", ">>",
5534 		    "<<", // my any-parent extension (reciprocal of whitespace)
5535 		    // " - ", // previous-sibling extension (whitespace required to disambiguate tag-names)
5536 		    ".", ">", "+", "*", ":", "[", "]", "=", "\"", "#", ",", " ", "~", "<", "(", ")"
5537 		]; // other is white space or a name.
5538 
5539 		///.
5540 		sizediff_t idToken(string str, sizediff_t position) {
5541 			sizediff_t tid = -1;
5542 			char c = str[position];
5543 			foreach(a, token; selectorTokens)
5544 
5545 				if(c == token[0]) {
5546 					if(token.length > 1) {
5547 						if(position + 1 >= str.length   ||   str[position+1] != token[1])
5548 							continue; // not this token
5549 					}
5550 					tid = a;
5551 					break;
5552 				}
5553 			return tid;
5554 		}
5555 
5556 	///.
5557 	// look, ma, no phobos!
5558 	// new lexer by ketmar
5559 	string[] lexSelector (string selstr) {
5560 
5561 		static sizediff_t idToken (string str, size_t stpos) {
5562 			char c = str[stpos];
5563 			foreach (sizediff_t tidx, immutable token; selectorTokens) {
5564 				if (c == token[0]) {
5565 					if (token.length > 1) {
5566 						assert(token.length == 2, token); // we don't have 3-char tokens yet
5567 						if (str.length-stpos < 2 || str[stpos+1] != token[1]) continue;
5568 					}
5569 					return tidx;
5570 				}
5571 			}
5572 			return -1;
5573 		}
5574 
5575 		// skip spaces and comments
5576 		static string removeLeadingBlanks (string str) {
5577 			size_t curpos = 0;
5578 			while (curpos < str.length) {
5579 				immutable char ch = str[curpos];
5580 				// this can overflow on 4GB strings on 32-bit; 'cmon, don't be silly, nobody cares!
5581 				if (ch == '/' && str.length-curpos > 1 && str[curpos+1] == '*') {
5582 					// comment
5583 					curpos += 2;
5584 					while (curpos < str.length) {
5585 						if (str[curpos] == '*' && str.length-curpos > 1 && str[curpos+1] == '/') {
5586 							curpos += 2;
5587 							break;
5588 						}
5589 						++curpos;
5590 					}
5591 				} else if (ch < 32) { // The < instead of <= is INTENTIONAL. See note from adr below.
5592 					++curpos;
5593 
5594 					// FROM ADR: This does NOT catch ' '! Spaces have semantic meaning in CSS! While
5595 					// "foo bar" is clear, and can only have one meaning, consider ".foo .bar".
5596 					// That is not the same as ".foo.bar". If the space is stripped, important
5597 					// information is lost, despite the tokens being separatable anyway.
5598 					//
5599 					// The parser really needs to be aware of the presence of a space.
5600 				} else {
5601 					break;
5602 				}
5603 			}
5604 			return str[curpos..$];
5605 		}
5606 
5607 		static bool isBlankAt() (string str, size_t pos) {
5608 			// we should consider unicode spaces too, but... unicode sux anyway.
5609 			return
5610 				(pos < str.length && // in string
5611 				 (str[pos] <= 32 || // space
5612 					(str.length-pos > 1 && str[pos] == '/' && str[pos+1] == '*'))); // comment
5613 		}
5614 
5615 		string[] tokens;
5616 		// lexx it!
5617 		while ((selstr = removeLeadingBlanks(selstr)).length > 0) {
5618 			if(selstr[0] == '\"' || selstr[0] == '\'') {
5619 				auto end = selstr[0];
5620 				auto pos = 1;
5621 				bool escaping;
5622 				while(pos < selstr.length && !escaping && selstr[pos] != end) {
5623 					if(escaping)
5624 						escaping = false;
5625 					else if(selstr[pos] == '\\')
5626 						escaping = true;
5627 					pos++;
5628 				}
5629 
5630 				// FIXME: do better unescaping
5631 				tokens ~= selstr[1 .. pos].replace(`\"`, `"`).replace(`\'`, `'`).replace(`\\`, `\`);
5632 				if(pos+1 >= selstr.length)
5633 					assert(0, selstr);
5634 				selstr = selstr[pos + 1.. $];
5635 				continue;
5636 			}
5637 
5638 
5639 			// no tokens starts with escape
5640 			immutable tid = idToken(selstr, 0);
5641 			if (tid >= 0) {
5642 				// special token
5643 				tokens ~= selectorTokens[tid]; // it's funnier this way
5644 				selstr = selstr[selectorTokens[tid].length..$];
5645 				continue;
5646 			}
5647 			// from start to space or special token
5648 			size_t escapePos = size_t.max;
5649 			size_t curpos = 0; // i can has chizburger^w escape at the start
5650 			while (curpos < selstr.length) {
5651 				if (selstr[curpos] == '\\') {
5652 					// this is escape, just skip it and next char
5653 					if (escapePos == size_t.max) escapePos = curpos;
5654 					curpos = (selstr.length-curpos >= 2 ? curpos+2 : selstr.length);
5655 				} else {
5656 					if (isBlankAt(selstr, curpos) || idToken(selstr, curpos) >= 0) break;
5657 					++curpos;
5658 				}
5659 			}
5660 			// identifier
5661 			if (escapePos != size_t.max) {
5662 				// i hate it when it happens
5663 				string id = selstr[0..escapePos];
5664 				while (escapePos < curpos) {
5665 					if (curpos-escapePos < 2) break;
5666 					id ~= selstr[escapePos+1]; // escaped char
5667 					escapePos += 2;
5668 					immutable stp = escapePos;
5669 					while (escapePos < curpos && selstr[escapePos] != '\\') ++escapePos;
5670 					if (escapePos > stp) id ~= selstr[stp..escapePos];
5671 				}
5672 				if (id.length > 0) tokens ~= id;
5673 			} else {
5674 				tokens ~= selstr[0..curpos];
5675 			}
5676 			selstr = selstr[curpos..$];
5677 		}
5678 		return tokens;
5679 	}
5680 	version(unittest_domd_lexer) unittest {
5681 		assert(lexSelector(r" test\=me  /*d*/") == [r"test=me"]);
5682 		assert(lexSelector(r"div/**/. id") == ["div", ".", "id"]);
5683 		assert(lexSelector(r" < <") == ["<", "<"]);
5684 		assert(lexSelector(r" <<") == ["<<"]);
5685 		assert(lexSelector(r" <</") == ["<<", "/"]);
5686 		assert(lexSelector(r" <</*") == ["<<"]);
5687 		assert(lexSelector(r" <\</*") == ["<", "<"]);
5688 		assert(lexSelector(r"heh\") == ["heh"]);
5689 		assert(lexSelector(r"alice \") == ["alice"]);
5690 		assert(lexSelector(r"alice,is#best") == ["alice", ",", "is", "#", "best"]);
5691 	}
5692 
5693 	///.
5694 	struct SelectorPart {
5695 		string tagNameFilter; ///.
5696 		string[] attributesPresent; /// [attr]
5697 		string[2][] attributesEqual; /// [attr=value]
5698 		string[2][] attributesStartsWith; /// [attr^=value]
5699 		string[2][] attributesEndsWith; /// [attr$=value]
5700 		// split it on space, then match to these
5701 		string[2][] attributesIncludesSeparatedBySpaces; /// [attr~=value]
5702 		// split it on dash, then match to these
5703 		string[2][] attributesIncludesSeparatedByDashes; /// [attr|=value]
5704 		string[2][] attributesInclude; /// [attr*=value]
5705 		string[2][] attributesNotEqual; /// [attr!=value] -- extension by me
5706 
5707 		string[] hasSelectors; /// :has(this)
5708 		string[] notSelectors; /// :not(this)
5709 
5710 		string[] isSelectors; /// :is(this)
5711 		string[] whereSelectors; /// :where(this)
5712 
5713 		ParsedNth[] nthOfType; /// .
5714 		ParsedNth[] nthLastOfType; /// .
5715 		ParsedNth[] nthChild; /// .
5716 
5717 		bool firstChild; ///.
5718 		bool lastChild; ///.
5719 
5720 		bool firstOfType; /// .
5721 		bool lastOfType; /// .
5722 
5723 		bool emptyElement; ///.
5724 		bool whitespaceOnly; ///
5725 		bool oddChild; ///.
5726 		bool evenChild; ///.
5727 
5728 		bool scopeElement; /// the css :scope thing; matches just the `this` element. NOT IMPLEMENTED
5729 
5730 		bool rootElement; ///.
5731 
5732 		int separation = -1; /// -1 == only itself; the null selector, 0 == tree, 1 == childNodes, 2 == childAfter, 3 == youngerSibling, 4 == parentOf
5733 
5734 		bool isCleanSlateExceptSeparation() {
5735 			auto cp = this;
5736 			cp.separation = -1;
5737 			return cp is SelectorPart.init;
5738 		}
5739 
5740 		///.
5741 		string toString() {
5742 			string ret;
5743 			switch(separation) {
5744 				default: assert(0);
5745 				case -1: break;
5746 				case 0: ret ~= " "; break;
5747 				case 1: ret ~= " > "; break;
5748 				case 2: ret ~= " + "; break;
5749 				case 3: ret ~= " ~ "; break;
5750 				case 4: ret ~= " < "; break;
5751 			}
5752 			ret ~= tagNameFilter;
5753 			foreach(a; attributesPresent) ret ~= "[" ~ a ~ "]";
5754 			foreach(a; attributesEqual) ret ~= "[" ~ a[0] ~ "=\"" ~ a[1] ~ "\"]";
5755 			foreach(a; attributesEndsWith) ret ~= "[" ~ a[0] ~ "$=\"" ~ a[1] ~ "\"]";
5756 			foreach(a; attributesStartsWith) ret ~= "[" ~ a[0] ~ "^=\"" ~ a[1] ~ "\"]";
5757 			foreach(a; attributesNotEqual) ret ~= "[" ~ a[0] ~ "!=\"" ~ a[1] ~ "\"]";
5758 			foreach(a; attributesInclude) ret ~= "[" ~ a[0] ~ "*=\"" ~ a[1] ~ "\"]";
5759 			foreach(a; attributesIncludesSeparatedByDashes) ret ~= "[" ~ a[0] ~ "|=\"" ~ a[1] ~ "\"]";
5760 			foreach(a; attributesIncludesSeparatedBySpaces) ret ~= "[" ~ a[0] ~ "~=\"" ~ a[1] ~ "\"]";
5761 
5762 			foreach(a; notSelectors) ret ~= ":not(" ~ a ~ ")";
5763 			foreach(a; hasSelectors) ret ~= ":has(" ~ a ~ ")";
5764 
5765 			foreach(a; isSelectors) ret ~= ":is(" ~ a ~ ")";
5766 			foreach(a; whereSelectors) ret ~= ":where(" ~ a ~ ")";
5767 
5768 			foreach(a; nthChild) ret ~= ":nth-child(" ~ a.toString ~ ")";
5769 			foreach(a; nthOfType) ret ~= ":nth-of-type(" ~ a.toString ~ ")";
5770 			foreach(a; nthLastOfType) ret ~= ":nth-last-of-type(" ~ a.toString ~ ")";
5771 
5772 			if(firstChild) ret ~= ":first-child";
5773 			if(lastChild) ret ~= ":last-child";
5774 			if(firstOfType) ret ~= ":first-of-type";
5775 			if(lastOfType) ret ~= ":last-of-type";
5776 			if(emptyElement) ret ~= ":empty";
5777 			if(whitespaceOnly) ret ~= ":whitespace-only";
5778 			if(oddChild) ret ~= ":odd-child";
5779 			if(evenChild) ret ~= ":even-child";
5780 			if(rootElement) ret ~= ":root";
5781 			if(scopeElement) ret ~= ":scope";
5782 
5783 			return ret;
5784 		}
5785 
5786 		// USEFUL
5787 		///.
5788 		bool matchElement(Element e) {
5789 			// FIXME: this can be called a lot of times, and really add up in times according to the profiler.
5790 			// Each individual call is reasonably fast already, but it adds up.
5791 			if(e is null) return false;
5792 			if(e.nodeType != 1) return false;
5793 
5794 			if(tagNameFilter != "" && tagNameFilter != "*")
5795 				if(e.tagName != tagNameFilter)
5796 					return false;
5797 			if(firstChild) {
5798 				if(e.parentNode is null)
5799 					return false;
5800 				if(e.parentNode.childElements[0] !is e)
5801 					return false;
5802 			}
5803 			if(lastChild) {
5804 				if(e.parentNode is null)
5805 					return false;
5806 				auto ce = e.parentNode.childElements;
5807 				if(ce[$-1] !is e)
5808 					return false;
5809 			}
5810 			if(firstOfType) {
5811 				if(e.parentNode is null)
5812 					return false;
5813 				auto ce = e.parentNode.childElements;
5814 				foreach(c; ce) {
5815 					if(c.tagName == e.tagName) {
5816 						if(c is e)
5817 							return true;
5818 						else
5819 							return false;
5820 					}
5821 				}
5822 			}
5823 			if(lastOfType) {
5824 				if(e.parentNode is null)
5825 					return false;
5826 				auto ce = e.parentNode.childElements;
5827 				foreach_reverse(c; ce) {
5828 					if(c.tagName == e.tagName) {
5829 						if(c is e)
5830 							return true;
5831 						else
5832 							return false;
5833 					}
5834 				}
5835 			}
5836 			/+
5837 			if(scopeElement) {
5838 				if(e !is this_)
5839 					return false;
5840 			}
5841 			+/
5842 			if(emptyElement) {
5843 				if(e.children.length)
5844 					return false;
5845 			}
5846 			if(whitespaceOnly) {
5847 				if(e.innerText.strip.length)
5848 					return false;
5849 			}
5850 			if(rootElement) {
5851 				if(e.parentNode !is null)
5852 					return false;
5853 			}
5854 			if(oddChild || evenChild) {
5855 				if(e.parentNode is null)
5856 					return false;
5857 				foreach(i, child; e.parentNode.childElements) {
5858 					if(child is e) {
5859 						if(oddChild && !(i&1))
5860 							return false;
5861 						if(evenChild && (i&1))
5862 							return false;
5863 						break;
5864 					}
5865 				}
5866 			}
5867 
5868 			bool matchWithSeparator(string attr, string value, string separator) {
5869 				foreach(s; attr.split(separator))
5870 					if(s == value)
5871 						return true;
5872 				return false;
5873 			}
5874 
5875 			foreach(a; attributesPresent)
5876 				if(a !in e.attributes)
5877 					return false;
5878 			foreach(a; attributesEqual)
5879 				if(a[0] !in e.attributes || e.attributes[a[0]] != a[1])
5880 					return false;
5881 			foreach(a; attributesNotEqual)
5882 				// FIXME: maybe it should say null counts... this just bit me.
5883 				// I did [attr][attr!=value] to work around.
5884 				//
5885 				// if it's null, it's not equal, right?
5886 				//if(a[0] !in e.attributes || e.attributes[a[0]] == a[1])
5887 				if(e.getAttribute(a[0]) == a[1])
5888 					return false;
5889 			foreach(a; attributesInclude)
5890 				if(a[0] !in e.attributes || (e.attributes[a[0]].indexOf(a[1]) == -1))
5891 					return false;
5892 			foreach(a; attributesStartsWith)
5893 				if(a[0] !in e.attributes || !e.attributes[a[0]].startsWith(a[1]))
5894 					return false;
5895 			foreach(a; attributesEndsWith)
5896 				if(a[0] !in e.attributes || !e.attributes[a[0]].endsWith(a[1]))
5897 					return false;
5898 			foreach(a; attributesIncludesSeparatedBySpaces)
5899 				if(a[0] !in e.attributes || !matchWithSeparator(e.attributes[a[0]], a[1], " "))
5900 					return false;
5901 			foreach(a; attributesIncludesSeparatedByDashes)
5902 				if(a[0] !in e.attributes || !matchWithSeparator(e.attributes[a[0]], a[1], "-"))
5903 					return false;
5904 			foreach(a; hasSelectors) {
5905 				if(e.querySelector(a) is null)
5906 					return false;
5907 			}
5908 			foreach(a; notSelectors) {
5909 				auto sel = Selector(a);
5910 				if(sel.matchesElement(e))
5911 					return false;
5912 			}
5913 			foreach(a; isSelectors) {
5914 				auto sel = Selector(a);
5915 				if(!sel.matchesElement(e))
5916 					return false;
5917 			}
5918 			foreach(a; whereSelectors) {
5919 				auto sel = Selector(a);
5920 				if(!sel.matchesElement(e))
5921 					return false;
5922 			}
5923 
5924 			foreach(a; nthChild) {
5925 				if(e.parentNode is null)
5926 					return false;
5927 
5928 				auto among = e.parentNode.childElements;
5929 
5930 				if(!a.solvesFor(among, e))
5931 					return false;
5932 			}
5933 			foreach(a; nthOfType) {
5934 				if(e.parentNode is null)
5935 					return false;
5936 
5937 				auto among = e.parentNode.childElements(e.tagName);
5938 
5939 				if(!a.solvesFor(among, e))
5940 					return false;
5941 			}
5942 			foreach(a; nthLastOfType) {
5943 				if(e.parentNode is null)
5944 					return false;
5945 
5946 				auto among = retro(e.parentNode.childElements(e.tagName));
5947 
5948 				if(!a.solvesFor(among, e))
5949 					return false;
5950 			}
5951 
5952 			return true;
5953 		}
5954 	}
5955 
5956 	struct ParsedNth {
5957 		int multiplier;
5958 		int adder;
5959 
5960 		string of;
5961 
5962 		this(string text) {
5963 			auto original = text;
5964 			consumeWhitespace(text);
5965 			if(text.startsWith("odd")) {
5966 				multiplier = 2;
5967 				adder = 1;
5968 
5969 				text = text[3 .. $];
5970 			} else if(text.startsWith("even")) {
5971 				multiplier = 2;
5972 				adder = 1;
5973 
5974 				text = text[4 .. $];
5975 			} else {
5976 				int n = (text.length && text[0] == 'n') ? 1 : parseNumber(text);
5977 				consumeWhitespace(text);
5978 				if(text.length && text[0] == 'n') {
5979 					multiplier = n;
5980 					text = text[1 .. $];
5981 					consumeWhitespace(text);
5982 					if(text.length) {
5983 						if(text[0] == '+') {
5984 							text = text[1 .. $];
5985 							adder = parseNumber(text);
5986 						} else if(text[0] == '-') {
5987 							text = text[1 .. $];
5988 							adder = -parseNumber(text);
5989 						} else if(text[0] == 'o') {
5990 							// continue, this is handled below
5991 						} else
5992 							throw new Exception("invalid css string at " ~ text ~ " in " ~ original);
5993 					}
5994 				} else {
5995 					adder = n;
5996 				}
5997 			}
5998 
5999 			consumeWhitespace(text);
6000 			if(text.startsWith("of")) {
6001 				text = text[2 .. $];
6002 				consumeWhitespace(text);
6003 				of = text[0 .. $];
6004 			}
6005 		}
6006 
6007 		string toString() {
6008 			return format("%dn%s%d%s%s", multiplier, adder >= 0 ? "+" : "", adder, of.length ? " of " : "", of);
6009 		}
6010 
6011 		bool solvesFor(R)(R elements, Element e) {
6012 			int idx = 1;
6013 			bool found = false;
6014 			foreach(ele; elements) {
6015 				if(of.length) {
6016 					auto sel = Selector(of);
6017 					if(!sel.matchesElement(ele))
6018 						continue;
6019 				}
6020 				if(ele is e) {
6021 					found = true;
6022 					break;
6023 				}
6024 				idx++;
6025 			}
6026 			if(!found) return false;
6027 
6028 			// multiplier* n + adder = idx
6029 			// if there is a solution for integral n, it matches
6030 
6031 			idx -= adder;
6032 			if(multiplier) {
6033 				if(idx % multiplier == 0)
6034 					return true;
6035 			} else {
6036 				return idx == 0;
6037 			}
6038 			return false;
6039 		}
6040 
6041 		private void consumeWhitespace(ref string text) {
6042 			while(text.length && text[0] == ' ')
6043 				text = text[1 .. $];
6044 		}
6045 
6046 		private int parseNumber(ref string text) {
6047 			consumeWhitespace(text);
6048 			if(text.length == 0) return 0;
6049 			bool negative = text[0] == '-';
6050 			if(text[0] == '+')
6051 				text = text[1 .. $];
6052 			if(negative) text = text[1 .. $];
6053 			int i = 0;
6054 			while(i < text.length && (text[i] >= '0' && text[i] <= '9'))
6055 				i++;
6056 			if(i == 0)
6057 				return 0;
6058 			int cool = to!int(text[0 .. i]);
6059 			text = text[i .. $];
6060 			return negative ? -cool : cool;
6061 		}
6062 	}
6063 
6064 	// USEFUL
6065 	///.
6066 	Element[] getElementsBySelectorParts(Element start, SelectorPart[] parts) {
6067 		Element[] ret;
6068 		if(!parts.length) {
6069 			return [start]; // the null selector only matches the start point; it
6070 				// is what terminates the recursion
6071 		}
6072 
6073 		auto part = parts[0];
6074 		//writeln("checking ", part, " against ", start, " with ", part.separation);
6075 		switch(part.separation) {
6076 			default: assert(0);
6077 			case -1:
6078 			case 0: // tree
6079 				foreach(e; start.tree) {
6080 					if(part.separation == 0 && start is e)
6081 						continue; // space doesn't match itself!
6082 					if(part.matchElement(e)) {
6083 						ret ~= getElementsBySelectorParts(e, parts[1..$]);
6084 					}
6085 				}
6086 			break;
6087 			case 1: // children
6088 				foreach(e; start.childNodes) {
6089 					if(part.matchElement(e)) {
6090 						ret ~= getElementsBySelectorParts(e, parts[1..$]);
6091 					}
6092 				}
6093 			break;
6094 			case 2: // next-sibling
6095 				auto e = start.nextSibling("*");
6096 				if(part.matchElement(e))
6097 					ret ~= getElementsBySelectorParts(e, parts[1..$]);
6098 			break;
6099 			case 3: // younger sibling
6100 				auto tmp = start.parentNode;
6101 				if(tmp !is null) {
6102 					sizediff_t pos = -1;
6103 					auto children = tmp.childElements;
6104 					foreach(i, child; children) {
6105 						if(child is start) {
6106 							pos = i;
6107 							break;
6108 						}
6109 					}
6110 					assert(pos != -1);
6111 					foreach(e; children[pos+1..$]) {
6112 						if(part.matchElement(e))
6113 							ret ~= getElementsBySelectorParts(e, parts[1..$]);
6114 					}
6115 				}
6116 			break;
6117 			case 4: // immediate parent node, an extension of mine to walk back up the tree
6118 				auto e = start.parentNode;
6119 				if(part.matchElement(e)) {
6120 					ret ~= getElementsBySelectorParts(e, parts[1..$]);
6121 				}
6122 				/*
6123 					Example of usefulness:
6124 
6125 					Consider you have an HTML table. If you want to get all rows that have a th, you can do:
6126 
6127 					table th < tr
6128 
6129 					Get all th descendants of the table, then walk back up the tree to fetch their parent tr nodes
6130 				*/
6131 			break;
6132 			case 5: // any parent note, another extension of mine to go up the tree (backward of the whitespace operator)
6133 				/*
6134 					Like with the < operator, this is best used to find some parent of a particular known element.
6135 
6136 					Say you have an anchor inside a
6137 				*/
6138 		}
6139 
6140 		return ret;
6141 	}
6142 
6143 	/++
6144 		Represents a parsed CSS selector. You never have to use this directly, but you can if you know it is going to be reused a lot to avoid a bit of repeat parsing.
6145 
6146 		See_Also:
6147 			$(LIST
6148 				* [Element.querySelector]
6149 				* [Element.querySelectorAll]
6150 				* [Element.matches]
6151 				* [Element.closest]
6152 				* [Document.querySelector]
6153 				* [Document.querySelectorAll]
6154 			)
6155 	+/
6156 	/// Group: core_functionality
6157 	struct Selector {
6158 		SelectorComponent[] components;
6159 		string original;
6160 		/++
6161 			Parses the selector string and constructs the usable structure.
6162 		+/
6163 		this(string cssSelector) {
6164 			components = parseSelectorString(cssSelector);
6165 			original = cssSelector;
6166 		}
6167 
6168 		/++
6169 			Returns true if the given element matches this selector,
6170 			considered relative to an arbitrary element.
6171 
6172 			You can do a form of lazy [Element.querySelectorAll|querySelectorAll] by using this
6173 			with [std.algorithm.iteration.filter]:
6174 
6175 			---
6176 			Selector sel = Selector("foo > bar");
6177 			auto lazySelectorRange = element.tree.filter!(e => sel.matchElement(e))(document.root);
6178 			---
6179 		+/
6180 		bool matchesElement(Element e, Element relativeTo = null) {
6181 			foreach(component; components)
6182 				if(component.matchElement(e, relativeTo))
6183 					return true;
6184 
6185 			return false;
6186 		}
6187 
6188 		/++
6189 			Reciprocal of [Element.querySelectorAll]
6190 		+/
6191 		Element[] getMatchingElements(Element start) {
6192 			Element[] ret;
6193 			foreach(component; components)
6194 				ret ~= getElementsBySelectorParts(start, component.parts);
6195 			return removeDuplicates(ret);
6196 		}
6197 
6198 		/++
6199 			Like [getMatchingElements], but returns a lazy range. Be careful
6200 			about mutating the dom as you iterate through this.
6201 		+/
6202 		auto getMatchingElementsLazy(Element start, Element relativeTo = null) {
6203 			import std.algorithm.iteration;
6204 			return start.tree.filter!(a => this.matchesElement(a, relativeTo));
6205 		}
6206 
6207 
6208 		/// Returns the string this was built from
6209 		string toString() {
6210 			return original;
6211 		}
6212 
6213 		/++
6214 			Returns a string from the parsed result
6215 
6216 
6217 			(may not match the original, this is mostly for debugging right now but in the future might be useful for pretty-printing)
6218 		+/
6219 		string parsedToString() {
6220 			string ret;
6221 
6222 			foreach(idx, component; components) {
6223 				if(idx) ret ~= ", ";
6224 				ret ~= component.toString();
6225 			}
6226 
6227 			return ret;
6228 		}
6229 	}
6230 
6231 	///.
6232 	struct SelectorComponent {
6233 		///.
6234 		SelectorPart[] parts;
6235 
6236 		///.
6237 		string toString() {
6238 			string ret;
6239 			foreach(part; parts)
6240 				ret ~= part.toString();
6241 			return ret;
6242 		}
6243 
6244 		// USEFUL
6245 		///.
6246 		Element[] getElements(Element start) {
6247 			return removeDuplicates(getElementsBySelectorParts(start, parts));
6248 		}
6249 
6250 		// USEFUL (but not implemented)
6251 		/// If relativeTo == null, it assumes the root of the parent document.
6252 		bool matchElement(Element e, Element relativeTo = null) {
6253 			if(e is null) return false;
6254 			Element where = e;
6255 			int lastSeparation = -1;
6256 
6257 			auto lparts = parts;
6258 
6259 			if(parts.length && parts[0].separation > 0) {
6260 				// if it starts with a non-trivial separator, inject
6261 				// a "*" matcher to act as a root. for cases like document.querySelector("> body")
6262 				// which implies html
6263 
6264 				// however, if it is a child-matching selector and there are no children,
6265 				// bail out early as it obviously cannot match.
6266 				bool hasNonTextChildren = false;
6267 				foreach(c; e.children)
6268 					if(c.nodeType != 3) {
6269 						hasNonTextChildren = true;
6270 						break;
6271 					}
6272 				if(!hasNonTextChildren)
6273 					return false;
6274 
6275 				// there is probably a MUCH better way to do this.
6276 				auto dummy = SelectorPart.init;
6277 				dummy.tagNameFilter = "*";
6278 				dummy.separation = 0;
6279 				lparts = dummy ~ lparts;
6280 			}
6281 
6282 			foreach(part; retro(lparts)) {
6283 
6284 				 // writeln("matching ", where, " with ", part, " via ", lastSeparation);
6285 				 // writeln(parts);
6286 
6287 				if(lastSeparation == -1) {
6288 					if(!part.matchElement(where))
6289 						return false;
6290 				} else if(lastSeparation == 0) { // generic parent
6291 					// need to go up the whole chain
6292 					where = where.parentNode;
6293 
6294 					while(where !is null) {
6295 						if(part.matchElement(where))
6296 							break;
6297 
6298 						if(where is relativeTo)
6299 							return false;
6300 
6301 						where = where.parentNode;
6302 					}
6303 
6304 					if(where is null)
6305 						return false;
6306 				} else if(lastSeparation == 1) { // the > operator
6307 					where = where.parentNode;
6308 
6309 					if(!part.matchElement(where))
6310 						return false;
6311 				} else if(lastSeparation == 2) { // the + operator
6312 				//writeln("WHERE", where, " ", part);
6313 					where = where.previousSibling("*");
6314 
6315 					if(!part.matchElement(where))
6316 						return false;
6317 				} else if(lastSeparation == 3) { // the ~ operator
6318 					where = where.previousSibling("*");
6319 					while(where !is null) {
6320 						if(part.matchElement(where))
6321 							break;
6322 
6323 						if(where is relativeTo)
6324 							return false;
6325 
6326 						where = where.previousSibling("*");
6327 					}
6328 
6329 					if(where is null)
6330 						return false;
6331 				} else if(lastSeparation == 4) { // my bad idea extension < operator, don't use this anymore
6332 					// FIXME
6333 				}
6334 
6335 				lastSeparation = part.separation;
6336 
6337 				if(where is relativeTo)
6338 					return false; // at end of line, if we aren't done by now, the match fails
6339 			}
6340 			return true; // if we got here, it is a success
6341 		}
6342 
6343 		// the string should NOT have commas. Use parseSelectorString for that instead
6344 		///.
6345 		static SelectorComponent fromString(string selector) {
6346 			return parseSelector(lexSelector(selector));
6347 		}
6348 	}
6349 
6350 	///.
6351 	SelectorComponent[] parseSelectorString(string selector, bool caseSensitiveTags = true) {
6352 		SelectorComponent[] ret;
6353 		auto tokens = lexSelector(selector); // this will parse commas too
6354 		// and now do comma-separated slices (i haz phobosophobia!)
6355 		int parensCount = 0;
6356 		while (tokens.length > 0) {
6357 			size_t end = 0;
6358 			while (end < tokens.length && (parensCount > 0 || tokens[end] != ",")) {
6359 				if(tokens[end] == "(") parensCount++;
6360 				if(tokens[end] == ")") parensCount--;
6361 				++end;
6362 			}
6363 			if (end > 0) ret ~= parseSelector(tokens[0..end], caseSensitiveTags);
6364 			if (tokens.length-end < 2) break;
6365 			tokens = tokens[end+1..$];
6366 		}
6367 		return ret;
6368 	}
6369 
6370 	///.
6371 	SelectorComponent parseSelector(string[] tokens, bool caseSensitiveTags = true) {
6372 		SelectorComponent s;
6373 
6374 		SelectorPart current;
6375 		void commit() {
6376 			// might as well skip null items
6377 			if(!current.isCleanSlateExceptSeparation()) {
6378 				s.parts ~= current;
6379 				current = current.init; // start right over
6380 			}
6381 		}
6382 		enum State {
6383 			Starting,
6384 			ReadingClass,
6385 			ReadingId,
6386 			ReadingAttributeSelector,
6387 			ReadingAttributeComparison,
6388 			ExpectingAttributeCloser,
6389 			ReadingPseudoClass,
6390 			ReadingAttributeValue,
6391 
6392 			SkippingFunctionalSelector,
6393 		}
6394 		State state = State.Starting;
6395 		string attributeName, attributeValue, attributeComparison;
6396 		int parensCount;
6397 		foreach(idx, token; tokens) {
6398 			string readFunctionalSelector() {
6399 				string s;
6400 				if(tokens[idx + 1] != "(")
6401 					throw new Exception("parse error");
6402 				int pc = 1;
6403 				foreach(t; tokens[idx + 2 .. $]) {
6404 					if(t == "(")
6405 						pc++;
6406 					if(t == ")")
6407 						pc--;
6408 					if(pc == 0)
6409 						break;
6410 					s ~= t;
6411 				}
6412 
6413 				return s;
6414 			}
6415 
6416 			sizediff_t tid = -1;
6417 			foreach(i, item; selectorTokens)
6418 				if(token == item) {
6419 					tid = i;
6420 					break;
6421 				}
6422 			final switch(state) {
6423 				case State.Starting: // fresh, might be reading an operator or a tagname
6424 					if(tid == -1) {
6425 						if(!caseSensitiveTags)
6426 							token = token.toLower();
6427 
6428 						if(current.isCleanSlateExceptSeparation()) {
6429 							current.tagNameFilter = token;
6430 							// default thing, see comment under "*" below
6431 							if(current.separation == -1) current.separation = 0;
6432 						} else {
6433 							// if it was already set, we must see two thingies
6434 							// separated by whitespace...
6435 							commit();
6436 							current.separation = 0; // tree
6437 							current.tagNameFilter = token;
6438 						}
6439 					} else {
6440 						// Selector operators
6441 						switch(token) {
6442 							case "*":
6443 								current.tagNameFilter = "*";
6444 								// the idea here is if we haven't actually set a separation
6445 								// yet (e.g. the > operator), it should assume the generic
6446 								// whitespace (descendant) mode to avoid matching self with -1
6447 								if(current.separation == -1) current.separation = 0;
6448 							break;
6449 							case " ":
6450 								// If some other separation has already been set,
6451 								// this is irrelevant whitespace, so we should skip it.
6452 								// this happens in the case of "foo > bar" for example.
6453 								if(current.isCleanSlateExceptSeparation() && current.separation > 0)
6454 									continue;
6455 								commit();
6456 								current.separation = 0; // tree
6457 							break;
6458 							case ">>":
6459 								commit();
6460 								current.separation = 0; // alternate syntax for tree from html5 css
6461 							break;
6462 							case ">":
6463 								commit();
6464 								current.separation = 1; // child
6465 							break;
6466 							case "+":
6467 								commit();
6468 								current.separation = 2; // sibling directly after
6469 							break;
6470 							case "~":
6471 								commit();
6472 								current.separation = 3; // any sibling after
6473 							break;
6474 							case "<":
6475 								commit();
6476 								current.separation = 4; // immediate parent of
6477 							break;
6478 							case "[":
6479 								state = State.ReadingAttributeSelector;
6480 								if(current.separation == -1) current.separation = 0;
6481 							break;
6482 							case ".":
6483 								state = State.ReadingClass;
6484 								if(current.separation == -1) current.separation = 0;
6485 							break;
6486 							case "#":
6487 								state = State.ReadingId;
6488 								if(current.separation == -1) current.separation = 0;
6489 							break;
6490 							case ":":
6491 							case "::":
6492 								state = State.ReadingPseudoClass;
6493 								if(current.separation == -1) current.separation = 0;
6494 							break;
6495 
6496 							default:
6497 								assert(0, token);
6498 						}
6499 					}
6500 				break;
6501 				case State.ReadingClass:
6502 					current.attributesIncludesSeparatedBySpaces ~= ["class", token];
6503 					state = State.Starting;
6504 				break;
6505 				case State.ReadingId:
6506 					current.attributesEqual ~= ["id", token];
6507 					state = State.Starting;
6508 				break;
6509 				case State.ReadingPseudoClass:
6510 					switch(token) {
6511 						case "first-of-type":
6512 							current.firstOfType = true;
6513 						break;
6514 						case "last-of-type":
6515 							current.lastOfType = true;
6516 						break;
6517 						case "only-of-type":
6518 							current.firstOfType = true;
6519 							current.lastOfType = true;
6520 						break;
6521 						case "first-child":
6522 							current.firstChild = true;
6523 						break;
6524 						case "last-child":
6525 							current.lastChild = true;
6526 						break;
6527 						case "only-child":
6528 							current.firstChild = true;
6529 							current.lastChild = true;
6530 						break;
6531 						case "scope":
6532 							current.scopeElement = true;
6533 						break;
6534 						case "empty":
6535 							// one with no children
6536 							current.emptyElement = true;
6537 						break;
6538 						case "whitespace-only":
6539 							current.whitespaceOnly = true;
6540 						break;
6541 						case "link":
6542 							current.attributesPresent ~= "href";
6543 						break;
6544 						case "root":
6545 							current.rootElement = true;
6546 						break;
6547 						case "nth-child":
6548 							current.nthChild ~= ParsedNth(readFunctionalSelector());
6549 							state = State.SkippingFunctionalSelector;
6550 						continue;
6551 						case "nth-of-type":
6552 							current.nthOfType ~= ParsedNth(readFunctionalSelector());
6553 							state = State.SkippingFunctionalSelector;
6554 						continue;
6555 						case "nth-last-of-type":
6556 							current.nthLastOfType ~= ParsedNth(readFunctionalSelector());
6557 							state = State.SkippingFunctionalSelector;
6558 						continue;
6559 						case "is":
6560 							state = State.SkippingFunctionalSelector;
6561 							current.isSelectors ~= readFunctionalSelector();
6562 						continue; // now the rest of the parser skips past the parens we just handled
6563 						case "where":
6564 							state = State.SkippingFunctionalSelector;
6565 							current.whereSelectors ~= readFunctionalSelector();
6566 						continue; // now the rest of the parser skips past the parens we just handled
6567 						case "not":
6568 							state = State.SkippingFunctionalSelector;
6569 							current.notSelectors ~= readFunctionalSelector();
6570 						continue; // now the rest of the parser skips past the parens we just handled
6571 						case "has":
6572 							state = State.SkippingFunctionalSelector;
6573 							current.hasSelectors ~= readFunctionalSelector();
6574 						continue; // now the rest of the parser skips past the parens we just handled
6575 						// back to standards though not quite right lol
6576 						case "disabled":
6577 							current.attributesPresent ~= "disabled";
6578 						break;
6579 						case "checked":
6580 							current.attributesPresent ~= "checked";
6581 						break;
6582 
6583 						case "visited", "active", "hover", "target", "focus", "selected":
6584 							current.attributesPresent ~= "nothing";
6585 							// FIXME
6586 						/+
6587 						// extensions not implemented
6588 						//case "text": // takes the text in the element and wraps it in an element, returning it
6589 						+/
6590 							goto case;
6591 						case "before", "after":
6592 							current.attributesPresent ~= "FIXME";
6593 
6594 						break;
6595 						// My extensions
6596 						case "odd-child":
6597 							current.oddChild = true;
6598 						break;
6599 						case "even-child":
6600 							current.evenChild = true;
6601 						break;
6602 						default:
6603 							//if(token.indexOf("lang") == -1)
6604 							//assert(0, token);
6605 						break;
6606 					}
6607 					state = State.Starting;
6608 				break;
6609 				case State.SkippingFunctionalSelector:
6610 					if(token == "(") {
6611 						parensCount++;
6612 					} else if(token == ")") {
6613 						parensCount--;
6614 					}
6615 
6616 					if(parensCount == 0)
6617 						state = State.Starting;
6618 				break;
6619 				case State.ReadingAttributeSelector:
6620 					attributeName = token;
6621 					attributeComparison = null;
6622 					attributeValue = null;
6623 					state = State.ReadingAttributeComparison;
6624 				break;
6625 				case State.ReadingAttributeComparison:
6626 					// FIXME: these things really should be quotable in the proper lexer...
6627 					if(token != "]") {
6628 						if(token.indexOf("=") == -1) {
6629 							// not a comparison; consider it
6630 							// part of the attribute
6631 							attributeValue ~= token;
6632 						} else {
6633 							attributeComparison = token;
6634 							state = State.ReadingAttributeValue;
6635 						}
6636 						break;
6637 					}
6638 					goto case;
6639 				case State.ExpectingAttributeCloser:
6640 					if(token != "]") {
6641 						// not the closer; consider it part of comparison
6642 						if(attributeComparison == "")
6643 							attributeName ~= token;
6644 						else
6645 							attributeValue ~= token;
6646 						break;
6647 					}
6648 
6649 					// Selector operators
6650 					switch(attributeComparison) {
6651 						default: assert(0);
6652 						case "":
6653 							current.attributesPresent ~= attributeName;
6654 						break;
6655 						case "=":
6656 							current.attributesEqual ~= [attributeName, attributeValue];
6657 						break;
6658 						case "|=":
6659 							current.attributesIncludesSeparatedByDashes ~= [attributeName, attributeValue];
6660 						break;
6661 						case "~=":
6662 							current.attributesIncludesSeparatedBySpaces ~= [attributeName, attributeValue];
6663 						break;
6664 						case "$=":
6665 							current.attributesEndsWith ~= [attributeName, attributeValue];
6666 						break;
6667 						case "^=":
6668 							current.attributesStartsWith ~= [attributeName, attributeValue];
6669 						break;
6670 						case "*=":
6671 							current.attributesInclude ~= [attributeName, attributeValue];
6672 						break;
6673 						case "!=":
6674 							current.attributesNotEqual ~= [attributeName, attributeValue];
6675 						break;
6676 					}
6677 
6678 					state = State.Starting;
6679 				break;
6680 				case State.ReadingAttributeValue:
6681 					attributeValue = token;
6682 					state = State.ExpectingAttributeCloser;
6683 				break;
6684 			}
6685 		}
6686 
6687 		commit();
6688 
6689 		return s;
6690 	}
6691 
6692 ///.
6693 Element[] removeDuplicates(Element[] input) {
6694 	Element[] ret;
6695 
6696 	bool[Element] already;
6697 	foreach(e; input) {
6698 		if(e in already) continue;
6699 		already[e] = true;
6700 		ret ~= e;
6701 	}
6702 
6703 	return ret;
6704 }
6705 
6706 // done with CSS selector handling
6707 
6708 
6709 // FIXME: use the better parser from html.d
6710 /// This is probably not useful to you unless you're writing a browser or something like that.
6711 /// It represents a *computed* style, like what the browser gives you after applying stylesheets, inline styles, and html attributes.
6712 /// From here, you can start to make a layout engine for the box model and have a css aware browser.
6713 class CssStyle {
6714 	///.
6715 	this(string rule, string content) {
6716 		rule = rule.strip();
6717 		content = content.strip();
6718 
6719 		if(content.length == 0)
6720 			return;
6721 
6722 		originatingRule = rule;
6723 		originatingSpecificity = getSpecificityOfRule(rule); // FIXME: if there's commas, this won't actually work!
6724 
6725 		foreach(part; content.split(";")) {
6726 			part = part.strip();
6727 			if(part.length == 0)
6728 				continue;
6729 			auto idx = part.indexOf(":");
6730 			if(idx == -1)
6731 				continue;
6732 				//throw new Exception("Bad css rule (no colon): " ~ part);
6733 
6734 			Property p;
6735 
6736 			p.name = part[0 .. idx].strip();
6737 			p.value = part[idx + 1 .. $].replace("! important", "!important").replace("!important", "").strip(); // FIXME don't drop important
6738 			p.givenExplicitly = true;
6739 			p.specificity = originatingSpecificity;
6740 
6741 			properties ~= p;
6742 		}
6743 
6744 		foreach(property; properties)
6745 			expandShortForm(property, originatingSpecificity);
6746 	}
6747 
6748 	///.
6749 	Specificity getSpecificityOfRule(string rule) {
6750 		Specificity s;
6751 		if(rule.length == 0) { // inline
6752 		//	s.important = 2;
6753 		} else {
6754 			// FIXME
6755 		}
6756 
6757 		return s;
6758 	}
6759 
6760 	string originatingRule; ///.
6761 	Specificity originatingSpecificity; ///.
6762 
6763 	///.
6764 	union Specificity {
6765 		uint score; ///.
6766 		// version(little_endian)
6767 		///.
6768 		struct {
6769 			ubyte tags; ///.
6770 			ubyte classes; ///.
6771 			ubyte ids; ///.
6772 			ubyte important; /// 0 = none, 1 = stylesheet author, 2 = inline style, 3 = user important
6773 		}
6774 	}
6775 
6776 	///.
6777 	struct Property {
6778 		bool givenExplicitly; /// this is false if for example the user said "padding" and this is "padding-left"
6779 		string name; ///.
6780 		string value; ///.
6781 		Specificity specificity; ///.
6782 		// do we care about the original source rule?
6783 	}
6784 
6785 	///.
6786 	Property[] properties;
6787 
6788 	///.
6789 	string opDispatch(string nameGiven)(string value = null) if(nameGiven != "popFront") {
6790 		string name = unCamelCase(nameGiven);
6791 		if(value is null)
6792 			return getValue(name);
6793 		else
6794 			return setValue(name, value, 0x02000000 /* inline specificity */);
6795 	}
6796 
6797 	/// takes dash style name
6798 	string getValue(string name) {
6799 		foreach(property; properties)
6800 			if(property.name == name)
6801 				return property.value;
6802 		return null;
6803 	}
6804 
6805 	/// takes dash style name
6806 	string setValue(string name, string value, Specificity newSpecificity, bool explicit = true) {
6807 		value = value.replace("! important", "!important");
6808 		if(value.indexOf("!important") != -1) {
6809 			newSpecificity.important = 1; // FIXME
6810 			value = value.replace("!important", "").strip();
6811 		}
6812 
6813 		foreach(ref property; properties)
6814 			if(property.name == name) {
6815 				if(newSpecificity.score >= property.specificity.score) {
6816 					property.givenExplicitly = explicit;
6817 					expandShortForm(property, newSpecificity);
6818 					return (property.value = value);
6819 				} else {
6820 					if(name == "display")
6821 					{}//writeln("Not setting ", name, " to ", value, " because ", newSpecificity.score, " < ", property.specificity.score);
6822 					return value; // do nothing - the specificity is too low
6823 				}
6824 			}
6825 
6826 		// it's not here...
6827 
6828 		Property p;
6829 		p.givenExplicitly = true;
6830 		p.name = name;
6831 		p.value = value;
6832 		p.specificity = originatingSpecificity;
6833 
6834 		properties ~= p;
6835 		expandShortForm(p, originatingSpecificity);
6836 
6837 		return value;
6838 	}
6839 
6840 	private void expandQuadShort(string name, string value, Specificity specificity) {
6841 		auto parts = value.split(" ");
6842 		switch(parts.length) {
6843 			case 1:
6844 				setValue(name ~"-left", parts[0], specificity, false);
6845 				setValue(name ~"-right", parts[0], specificity, false);
6846 				setValue(name ~"-top", parts[0], specificity, false);
6847 				setValue(name ~"-bottom", parts[0], specificity, false);
6848 			break;
6849 			case 2:
6850 				setValue(name ~"-left", parts[1], specificity, false);
6851 				setValue(name ~"-right", parts[1], specificity, false);
6852 				setValue(name ~"-top", parts[0], specificity, false);
6853 				setValue(name ~"-bottom", parts[0], specificity, false);
6854 			break;
6855 			case 3:
6856 				setValue(name ~"-top", parts[0], specificity, false);
6857 				setValue(name ~"-right", parts[1], specificity, false);
6858 				setValue(name ~"-bottom", parts[2], specificity, false);
6859 				setValue(name ~"-left", parts[2], specificity, false);
6860 
6861 			break;
6862 			case 4:
6863 				setValue(name ~"-top", parts[0], specificity, false);
6864 				setValue(name ~"-right", parts[1], specificity, false);
6865 				setValue(name ~"-bottom", parts[2], specificity, false);
6866 				setValue(name ~"-left", parts[3], specificity, false);
6867 			break;
6868 			default:
6869 				assert(0, value);
6870 		}
6871 	}
6872 
6873 	///.
6874 	void expandShortForm(Property p, Specificity specificity) {
6875 		switch(p.name) {
6876 			case "margin":
6877 			case "padding":
6878 				expandQuadShort(p.name, p.value, specificity);
6879 			break;
6880 			case "border":
6881 			case "outline":
6882 				setValue(p.name ~ "-left", p.value, specificity, false);
6883 				setValue(p.name ~ "-right", p.value, specificity, false);
6884 				setValue(p.name ~ "-top", p.value, specificity, false);
6885 				setValue(p.name ~ "-bottom", p.value, specificity, false);
6886 			break;
6887 
6888 			case "border-top":
6889 			case "border-bottom":
6890 			case "border-left":
6891 			case "border-right":
6892 			case "outline-top":
6893 			case "outline-bottom":
6894 			case "outline-left":
6895 			case "outline-right":
6896 
6897 			default: {}
6898 		}
6899 	}
6900 
6901 	///.
6902 	override string toString() {
6903 		string ret;
6904 		if(originatingRule.length)
6905 			ret = originatingRule ~ " {";
6906 
6907 		foreach(property; properties) {
6908 			if(!property.givenExplicitly)
6909 				continue; // skip the inferred shit
6910 
6911 			if(originatingRule.length)
6912 				ret ~= "\n\t";
6913 			else
6914 				ret ~= " ";
6915 
6916 			ret ~= property.name ~ ": " ~ property.value ~ ";";
6917 		}
6918 
6919 		if(originatingRule.length)
6920 			ret ~= "\n}\n";
6921 
6922 		return ret;
6923 	}
6924 }
6925 
6926 string cssUrl(string url) {
6927 	return "url(\"" ~ url ~ "\")";
6928 }
6929 
6930 /// This probably isn't useful, unless you're writing a browser or something like that.
6931 /// You might want to look at arsd.html for css macro, nesting, etc., or just use standard css
6932 /// as text.
6933 ///
6934 /// The idea, however, is to represent a kind of CSS object model, complete with specificity,
6935 /// that you can apply to your documents to build the complete computedStyle object.
6936 class StyleSheet {
6937 	///.
6938 	CssStyle[] rules;
6939 
6940 	///.
6941 	this(string source) {
6942 		// FIXME: handle @ rules and probably could improve lexer
6943 		// add nesting?
6944 		int state;
6945 		string currentRule;
6946 		string currentValue;
6947 
6948 		string* currentThing = &currentRule;
6949 		foreach(c; source) {
6950 			handle: switch(state) {
6951 				default: assert(0);
6952 				case 0: // starting - we assume we're reading a rule
6953 					switch(c) {
6954 						case '@':
6955 							state = 4;
6956 						break;
6957 						case '/':
6958 							state = 1;
6959 						break;
6960 						case '{':
6961 							currentThing = &currentValue;
6962 						break;
6963 						case '}':
6964 							if(currentThing is &currentValue) {
6965 								rules ~= new CssStyle(currentRule, currentValue);
6966 
6967 								currentRule = "";
6968 								currentValue = "";
6969 
6970 								currentThing = &currentRule;
6971 							} else {
6972 								// idk what is going on here.
6973 								// check sveit.com to reproduce
6974 								currentRule = "";
6975 								currentValue = "";
6976 							}
6977 						break;
6978 						default:
6979 							(*currentThing) ~= c;
6980 					}
6981 				break;
6982 				case 1: // expecting *
6983 					if(c == '*')
6984 						state = 2;
6985 					else {
6986 						state = 0;
6987 						(*currentThing) ~= "/" ~ c;
6988 					}
6989 				break;
6990 				case 2: // inside comment
6991 					if(c == '*')
6992 						state = 3;
6993 				break;
6994 				case 3: // expecting / to end comment
6995 					if(c == '/')
6996 						state = 0;
6997 					else
6998 						state = 2; // it's just a comment so no need to append
6999 				break;
7000 				case 4:
7001 					if(c == '{')
7002 						state = 5;
7003 					if(c == ';')
7004 						state = 0; // just skipping import
7005 				break;
7006 				case 5:
7007 					if(c == '}')
7008 						state = 0; // skipping font face probably
7009 			}
7010 		}
7011 	}
7012 
7013 	/// Run through the document and apply this stylesheet to it. The computedStyle member will be accurate after this call
7014 	void apply(Document document) {
7015 		foreach(rule; rules) {
7016 			if(rule.originatingRule.length == 0)
7017 				continue; // this shouldn't happen here in a stylesheet
7018 			foreach(element; document.querySelectorAll(rule.originatingRule)) {
7019 				// note: this should be a different object than the inline style
7020 				// since givenExplicitly is likely destroyed here
7021 				auto current = element.computedStyle;
7022 
7023 				foreach(item; rule.properties)
7024 					current.setValue(item.name, item.value, item.specificity);
7025 			}
7026 		}
7027 	}
7028 }
7029 
7030 
7031 /// This is kinda private; just a little utility container for use by the ElementStream class.
7032 final class Stack(T) {
7033 	this() {
7034 		internalLength = 0;
7035 		arr = initialBuffer[];
7036 	}
7037 
7038 	///.
7039 	void push(T t) {
7040 		if(internalLength >= arr.length) {
7041 			auto oldarr = arr;
7042 			if(arr.length < 4096)
7043 				arr = new T[arr.length * 2];
7044 			else
7045 				arr = new T[arr.length + 4096];
7046 			arr[0 .. oldarr.length] = oldarr[];
7047 		}
7048 
7049 		arr[internalLength] = t;
7050 		internalLength++;
7051 	}
7052 
7053 	///.
7054 	T pop() {
7055 		assert(internalLength);
7056 		internalLength--;
7057 		return arr[internalLength];
7058 	}
7059 
7060 	///.
7061 	T peek() {
7062 		assert(internalLength);
7063 		return arr[internalLength - 1];
7064 	}
7065 
7066 	///.
7067 	@property bool empty() {
7068 		return internalLength ? false : true;
7069 	}
7070 
7071 	///.
7072 	private T[] arr;
7073 	private size_t internalLength;
7074 	private T[64] initialBuffer;
7075 	// the static array is allocated with this object, so if we have a small stack (which we prolly do; dom trees usually aren't insanely deep),
7076 	// using this saves us a bunch of trips to the GC. In my last profiling, I got about a 50x improvement in the push()
7077 	// function thanks to this, and push() was actually one of the slowest individual functions in the code!
7078 }
7079 
7080 /// This is the lazy range that walks the tree for you. It tries to go in the lexical order of the source: node, then children from first to last, each recursively.
7081 final class ElementStream {
7082 
7083 	///.
7084 	@property Element front() {
7085 		return current.element;
7086 	}
7087 
7088 	/// Use Element.tree instead.
7089 	this(Element start) {
7090 		current.element = start;
7091 		current.childPosition = -1;
7092 		isEmpty = false;
7093 		stack = new Stack!(Current);
7094 	}
7095 
7096 	/*
7097 		Handle it
7098 		handle its children
7099 
7100 	*/
7101 
7102 	///.
7103 	void popFront() {
7104 	    more:
7105 	    	if(isEmpty) return;
7106 
7107 		// FIXME: the profiler says this function is somewhat slow (noticeable because it can be called a lot of times)
7108 
7109 		current.childPosition++;
7110 		if(current.childPosition >= current.element.children.length) {
7111 			if(stack.empty())
7112 				isEmpty = true;
7113 			else {
7114 				current = stack.pop();
7115 				goto more;
7116 			}
7117 		} else {
7118 			stack.push(current);
7119 			current.element = current.element.children[current.childPosition];
7120 			current.childPosition = -1;
7121 		}
7122 	}
7123 
7124 	/// You should call this when you remove an element from the tree. It then doesn't recurse into that node and adjusts the current position, keeping the range stable.
7125 	void currentKilled() {
7126 		if(stack.empty) // should never happen
7127 			isEmpty = true;
7128 		else {
7129 			current = stack.pop();
7130 			current.childPosition--; // when it is killed, the parent is brought back a lil so when we popFront, this is then right
7131 		}
7132 	}
7133 
7134 	///.
7135 	@property bool empty() {
7136 		return isEmpty;
7137 	}
7138 
7139 	private:
7140 
7141 	struct Current {
7142 		Element element;
7143 		int childPosition;
7144 	}
7145 
7146 	Current current;
7147 
7148 	Stack!(Current) stack;
7149 
7150 	bool isEmpty;
7151 }
7152 
7153 
7154 
7155 // unbelievable.
7156 // Don't use any of these in your own code. Instead, try to use phobos or roll your own, as I might kill these at any time.
7157 sizediff_t indexOfBytes(immutable(ubyte)[] haystack, immutable(ubyte)[] needle) {
7158 	static import std.algorithm;
7159 	auto found = std.algorithm.find(haystack, needle);
7160 	if(found.length == 0)
7161 		return -1;
7162 	return haystack.length - found.length;
7163 }
7164 
7165 private T[] insertAfter(T)(T[] arr, int position, T[] what) {
7166 	assert(position < arr.length);
7167 	T[] ret;
7168 	ret.length = arr.length + what.length;
7169 	int a = 0;
7170 	foreach(i; arr[0..position+1])
7171 		ret[a++] = i;
7172 
7173 	foreach(i; what)
7174 		ret[a++] = i;
7175 
7176 	foreach(i; arr[position+1..$])
7177 		ret[a++] = i;
7178 
7179 	return ret;
7180 }
7181 
7182 package bool isInArray(T)(T item, T[] arr) {
7183 	foreach(i; arr)
7184 		if(item == i)
7185 			return true;
7186 	return false;
7187 }
7188 
7189 private string[string] aadup(in string[string] arr) {
7190 	string[string] ret;
7191 	foreach(k, v; arr)
7192 		ret[k] = v;
7193 	return ret;
7194 }
7195 
7196 
7197 
7198 
7199 
7200 
7201 
7202 
7203 
7204 
7205 
7206 
7207 
7208 
7209 
7210 // These MUST be sorted. See generatedomcases.d for a program to generate it if you need to add more than a few (otherwise maybe you can work it in yourself but yikes)
7211 
7212 immutable string[] availableEntities =
7213 ["AElig", "AElig", "AMP", "AMP", "Aacute", "Aacute", "Abreve", "Abreve", "Acirc", "Acirc", "Acy", "Acy", "Afr", "Afr", "Agrave", "Agrave", "Alpha", "Alpha", "Amacr", "Amacr", "And", "And", "Aogon", "Aogon", "Aopf", "Aopf", "ApplyFunction", "ApplyFunction", "Aring", "Aring", "Ascr", "Ascr", "Assign", "Assign", "Atilde", 
7214 "Atilde", "Auml", "Auml", "Backslash", "Backslash", "Barv", "Barv", "Barwed", "Barwed", "Bcy", "Bcy", "Because", "Because", "Bernoullis", "Bernoullis", "Beta", "Beta", "Bfr", "Bfr", "Bopf", "Bopf", "Breve", "Breve", "Bscr", "Bscr", "Bumpeq", "Bumpeq", "CHcy", "CHcy", "COPY", "COPY", "Cacute", "Cacute", "Cap", "Cap", "CapitalDifferentialD", 
7215 "CapitalDifferentialD", "Cayleys", "Cayleys", "Ccaron", "Ccaron", "Ccedil", "Ccedil", "Ccirc", "Ccirc", "Cconint", "Cconint", "Cdot", "Cdot", "Cedilla", "Cedilla", "CenterDot", "CenterDot", "Cfr", "Cfr", "Chi", "Chi", "CircleDot", "CircleDot", "CircleMinus", "CircleMinus", "CirclePlus", "CirclePlus", "CircleTimes", "CircleTimes", 
7216 "ClockwiseContourIntegral", "ClockwiseContourIntegral", "CloseCurlyDoubleQuote", "CloseCurlyDoubleQuote", "CloseCurlyQuote", "CloseCurlyQuote", "Colon", "Colon", "Colone", "Colone", "Congruent", "Congruent", "Conint", "Conint", "ContourIntegral", "ContourIntegral", "Copf", "Copf", "Coproduct", "Coproduct", "CounterClockwiseContourIntegral", 
7217 "CounterClockwiseContourIntegral", "Cross", "Cross", "Cscr", "Cscr", "Cup", "Cup", "CupCap", "CupCap", "DD", "DD", "DDotrahd", "DDotrahd", "DJcy", "DJcy", "DScy", "DScy", "DZcy", "DZcy", "Dagger", "Dagger", "Darr", "Darr", "Dashv", "Dashv", "Dcaron", "Dcaron", "Dcy", "Dcy", "Del", "Del", "Delta", "Delta", "Dfr", "Dfr", 
7218 "DiacriticalAcute", "DiacriticalAcute", "DiacriticalDot", "DiacriticalDot", "DiacriticalDoubleAcute", "DiacriticalDoubleAcute", "DiacriticalGrave", "DiacriticalGrave", "DiacriticalTilde", "DiacriticalTilde", "Diamond", "Diamond", "DifferentialD", "DifferentialD", "Dopf", "Dopf", "Dot", "Dot", "DotDot", "DotDot", "DotEqual", 
7219 "DotEqual", "DoubleContourIntegral", "DoubleContourIntegral", "DoubleDot", "DoubleDot", "DoubleDownArrow", "DoubleDownArrow", "DoubleLeftArrow", "DoubleLeftArrow", "DoubleLeftRightArrow", "DoubleLeftRightArrow", "DoubleLeftTee", "DoubleLeftTee", "DoubleLongLeftArrow", "DoubleLongLeftArrow", "DoubleLongLeftRightArrow", 
7220 "DoubleLongLeftRightArrow", "DoubleLongRightArrow", "DoubleLongRightArrow", "DoubleRightArrow", "DoubleRightArrow", "DoubleRightTee", "DoubleRightTee", "DoubleUpArrow", "DoubleUpArrow", "DoubleUpDownArrow", "DoubleUpDownArrow", "DoubleVerticalBar", "DoubleVerticalBar", "DownArrow", "DownArrow", "DownArrowBar", "DownArrowBar", 
7221 "DownArrowUpArrow", "DownArrowUpArrow", "DownBreve", "DownBreve", "DownLeftRightVector", "DownLeftRightVector", "DownLeftTeeVector", "DownLeftTeeVector", "DownLeftVector", "DownLeftVector", "DownLeftVectorBar", "DownLeftVectorBar", "DownRightTeeVector", "DownRightTeeVector", "DownRightVector", "DownRightVector", "DownRightVectorBar", 
7222 "DownRightVectorBar", "DownTee", "DownTee", "DownTeeArrow", "DownTeeArrow", "Downarrow", "Downarrow", "Dscr", "Dscr", "Dstrok", "Dstrok", "ENG", "ENG", "ETH", "ETH", "Eacute", "Eacute", "Ecaron", "Ecaron", "Ecirc", "Ecirc", "Ecy", "Ecy", "Edot", "Edot", "Efr", "Efr", "Egrave", "Egrave", "Element", "Element", "Emacr", "Emacr", 
7223 "EmptySmallSquare", "EmptySmallSquare", "EmptyVerySmallSquare", "EmptyVerySmallSquare", "Eogon", "Eogon", "Eopf", "Eopf", "Epsilon", "Epsilon", "Equal", "Equal", "EqualTilde", "EqualTilde", "Equilibrium", "Equilibrium", "Escr", "Escr", "Esim", "Esim", "Eta", "Eta", "Euml", "Euml", "Exists", "Exists", "ExponentialE", "ExponentialE", 
7224 "Fcy", "Fcy", "Ffr", "Ffr", "FilledSmallSquare", "FilledSmallSquare", "FilledVerySmallSquare", "FilledVerySmallSquare", "Fopf", "Fopf", "ForAll", "ForAll", "Fouriertrf", "Fouriertrf", "Fscr", "Fscr", "GJcy", "GJcy", "GT", "GT", "Gamma", "Gamma", "Gammad", "Gammad", "Gbreve", "Gbreve", "Gcedil", "Gcedil", "Gcirc", "Gcirc", 
7225 "Gcy", "Gcy", "Gdot", "Gdot", "Gfr", "Gfr", "Gg", "Gg", "Gopf", "Gopf", "GreaterEqual", "GreaterEqual", "GreaterEqualLess", "GreaterEqualLess", "GreaterFullEqual", "GreaterFullEqual", "GreaterGreater", "GreaterGreater", "GreaterLess", "GreaterLess", "GreaterSlantEqual", "GreaterSlantEqual", "GreaterTilde", "GreaterTilde", 
7226 "Gscr", "Gscr", "Gt", "Gt", "HARDcy", "HARDcy", "Hacek", "Hacek", "Hat", "Hat", "Hcirc", "Hcirc", "Hfr", "Hfr", "HilbertSpace", "HilbertSpace", "Hopf", "Hopf", "HorizontalLine", "HorizontalLine", "Hscr", "Hscr", "Hstrok", "Hstrok", "HumpDownHump", "HumpDownHump", "HumpEqual", "HumpEqual", "IEcy", "IEcy", "IJlig", "IJlig", 
7227 "IOcy", "IOcy", "Iacute", "Iacute", "Icirc", "Icirc", "Icy", "Icy", "Idot", "Idot", "Ifr", "Ifr", "Igrave", "Igrave", "Im", "Im", "Imacr", "Imacr", "ImaginaryI", "ImaginaryI", "Implies", "Implies", "Int", "Int", "Integral", "Integral", "Intersection", "Intersection", "InvisibleComma", "InvisibleComma", "InvisibleTimes", 
7228 "InvisibleTimes", "Iogon", "Iogon", "Iopf", "Iopf", "Iota", "Iota", "Iscr", "Iscr", "Itilde", "Itilde", "Iukcy", "Iukcy", "Iuml", "Iuml", "Jcirc", "Jcirc", "Jcy", "Jcy", "Jfr", "Jfr", "Jopf", "Jopf", "Jscr", "Jscr", "Jsercy", "Jsercy", "Jukcy", "Jukcy", "KHcy", "KHcy", "KJcy", "KJcy", "Kappa", "Kappa", "Kcedil", "Kcedil", 
7229 "Kcy", "Kcy", "Kfr", "Kfr", "Kopf", "Kopf", "Kscr", "Kscr", "LJcy", "LJcy", "LT", "LT", "Lacute", "Lacute", "Lambda", "Lambda", "Lang", "Lang", "Laplacetrf", "Laplacetrf", "Larr", "Larr", "Lcaron", "Lcaron", "Lcedil", "Lcedil", "Lcy", "Lcy", "LeftAngleBracket", "LeftAngleBracket", "LeftArrow", "LeftArrow", "LeftArrowBar", 
7230 "LeftArrowBar", "LeftArrowRightArrow", "LeftArrowRightArrow", "LeftCeiling", "LeftCeiling", "LeftDoubleBracket", "LeftDoubleBracket", "LeftDownTeeVector", "LeftDownTeeVector", "LeftDownVector", "LeftDownVector", "LeftDownVectorBar", "LeftDownVectorBar", "LeftFloor", "LeftFloor", "LeftRightArrow", "LeftRightArrow", "LeftRightVector", 
7231 "LeftRightVector", "LeftTee", "LeftTee", "LeftTeeArrow", "LeftTeeArrow", "LeftTeeVector", "LeftTeeVector", "LeftTriangle", "LeftTriangle", "LeftTriangleBar", "LeftTriangleBar", "LeftTriangleEqual", "LeftTriangleEqual", "LeftUpDownVector", "LeftUpDownVector", "LeftUpTeeVector", "LeftUpTeeVector", "LeftUpVector", "LeftUpVector", 
7232 "LeftUpVectorBar", "LeftUpVectorBar", "LeftVector", "LeftVector", "LeftVectorBar", "LeftVectorBar", "Leftarrow", "Leftarrow", "Leftrightarrow", "Leftrightarrow", "LessEqualGreater", "LessEqualGreater", "LessFullEqual", "LessFullEqual", "LessGreater", "LessGreater", "LessLess", "LessLess", "LessSlantEqual", "LessSlantEqual", 
7233 "LessTilde", "LessTilde", "Lfr", "Lfr", "Ll", "Ll", "Lleftarrow", "Lleftarrow", "Lmidot", "Lmidot", "LongLeftArrow", "LongLeftArrow", "LongLeftRightArrow", "LongLeftRightArrow", "LongRightArrow", "LongRightArrow", "Longleftarrow", "Longleftarrow", "Longleftrightarrow", "Longleftrightarrow", "Longrightarrow", "Longrightarrow", 
7234 "Lopf", "Lopf", "LowerLeftArrow", "LowerLeftArrow", "LowerRightArrow", "LowerRightArrow", "Lscr", "Lscr", "Lsh", "Lsh", "Lstrok", "Lstrok", "Lt", "Lt", "Map", "Map", "Mcy", "Mcy", "MediumSpace", "MediumSpace", "Mellintrf", "Mellintrf", "Mfr", "Mfr", "MinusPlus", "MinusPlus", "Mopf", "Mopf", "Mscr", "Mscr", "Mu", "Mu", 
7235 "NJcy", "NJcy", "Nacute", "Nacute", "Ncaron", "Ncaron", "Ncedil", "Ncedil", "Ncy", "Ncy", "NegativeMediumSpace", "NegativeMediumSpace", "NegativeThickSpace", "NegativeThickSpace", "NegativeThinSpace", "NegativeThinSpace", "NegativeVeryThinSpace", "NegativeVeryThinSpace", "NestedGreaterGreater", "NestedGreaterGreater", 
7236 "NestedLessLess", "NestedLessLess", "NewLine", "NewLine", "Nfr", "Nfr", "NoBreak", "NoBreak", "NonBreakingSpace", "NonBreakingSpace", "Nopf", "Nopf", "Not", "Not", "NotCongruent", "NotCongruent", "NotCupCap", "NotCupCap", "NotDoubleVerticalBar", "NotDoubleVerticalBar", "NotElement", "NotElement", "NotEqual", "NotEqual", 
7237 "NotExists", "NotExists", "NotGreater", "NotGreater", "NotGreaterEqual", "NotGreaterEqual", "NotGreaterLess", "NotGreaterLess", "NotGreaterTilde", "NotGreaterTilde", "NotLeftTriangle", "NotLeftTriangle", "NotLeftTriangleEqual", "NotLeftTriangleEqual", "NotLess", "NotLess", "NotLessEqual", "NotLessEqual", "NotLessGreater", 
7238 "NotLessGreater", "NotLessTilde", "NotLessTilde", "NotPrecedes", "NotPrecedes", "NotPrecedesSlantEqual", "NotPrecedesSlantEqual", "NotReverseElement", "NotReverseElement", "NotRightTriangle", "NotRightTriangle", "NotRightTriangleEqual", "NotRightTriangleEqual", "NotSquareSubsetEqual", "NotSquareSubsetEqual", "NotSquareSupersetEqual", 
7239 "NotSquareSupersetEqual", "NotSubsetEqual", "NotSubsetEqual", "NotSucceeds", "NotSucceeds", "NotSucceedsSlantEqual", "NotSucceedsSlantEqual", "NotSupersetEqual", "NotSupersetEqual", "NotTilde", "NotTilde", "NotTildeEqual", "NotTildeEqual", "NotTildeFullEqual", "NotTildeFullEqual", "NotTildeTilde", "NotTildeTilde", "NotVerticalBar", 
7240 "NotVerticalBar", "Nscr", "Nscr", "Ntilde", "Ntilde", "Nu", "Nu", "OElig", "OElig", "Oacute", "Oacute", "Ocirc", "Ocirc", "Ocy", "Ocy", "Odblac", "Odblac", "Ofr", "Ofr", "Ograve", "Ograve", "Omacr", "Omacr", "Omega", "Omega", "Omicron", "Omicron", "Oopf", "Oopf", "OpenCurlyDoubleQuote", "OpenCurlyDoubleQuote", "OpenCurlyQuote", 
7241 "OpenCurlyQuote", "Or", "Or", "Oscr", "Oscr", "Oslash", "Oslash", "Otilde", "Otilde", "Otimes", "Otimes", "Ouml", "Ouml", "OverBar", "OverBar", "OverBrace", "OverBrace", "OverBracket", "OverBracket", "OverParenthesis", "OverParenthesis", "PartialD", "PartialD", "Pcy", "Pcy", "Pfr", "Pfr", "Phi", "Phi", "Pi", "Pi", "PlusMinus", 
7242 "PlusMinus", "Poincareplane", "Poincareplane", "Popf", "Popf", "Pr", "Pr", "Precedes", "Precedes", "PrecedesEqual", "PrecedesEqual", "PrecedesSlantEqual", "PrecedesSlantEqual", "PrecedesTilde", "PrecedesTilde", "Prime", "Prime", "Product", "Product", "Proportion", "Proportion", "Proportional", "Proportional", "Pscr", "Pscr", 
7243 "Psi", "Psi", "QUOT", "QUOT", "Qfr", "Qfr", "Qopf", "Qopf", "Qscr", "Qscr", "RBarr", "RBarr", "REG", "REG", "Racute", "Racute", "Rang", "Rang", "Rarr", "Rarr", "Rarrtl", "Rarrtl", "Rcaron", "Rcaron", "Rcedil", "Rcedil", "Rcy", "Rcy", "Re", "Re", "ReverseElement", "ReverseElement", "ReverseEquilibrium", "ReverseEquilibrium", 
7244 "ReverseUpEquilibrium", "ReverseUpEquilibrium", "Rfr", "Rfr", "Rho", "Rho", "RightAngleBracket", "RightAngleBracket", "RightArrow", "RightArrow", "RightArrowBar", "RightArrowBar", "RightArrowLeftArrow", "RightArrowLeftArrow", "RightCeiling", "RightCeiling", "RightDoubleBracket", "RightDoubleBracket", "RightDownTeeVector", 
7245 "RightDownTeeVector", "RightDownVector", "RightDownVector", "RightDownVectorBar", "RightDownVectorBar", "RightFloor", "RightFloor", "RightTee", "RightTee", "RightTeeArrow", "RightTeeArrow", "RightTeeVector", "RightTeeVector", "RightTriangle", "RightTriangle", "RightTriangleBar", "RightTriangleBar", "RightTriangleEqual", 
7246 "RightTriangleEqual", "RightUpDownVector", "RightUpDownVector", "RightUpTeeVector", "RightUpTeeVector", "RightUpVector", "RightUpVector", "RightUpVectorBar", "RightUpVectorBar", "RightVector", "RightVector", "RightVectorBar", "RightVectorBar", "Rightarrow", "Rightarrow", "Ropf", "Ropf", "RoundImplies", "RoundImplies", 
7247 "Rrightarrow", "Rrightarrow", "Rscr", "Rscr", "Rsh", "Rsh", "RuleDelayed", "RuleDelayed", "SHCHcy", "SHCHcy", "SHcy", "SHcy", "SOFTcy", "SOFTcy", "Sacute", "Sacute", "Sc", "Sc", "Scaron", "Scaron", "Scedil", "Scedil", "Scirc", "Scirc", "Scy", "Scy", "Sfr", "Sfr", "ShortDownArrow", "ShortDownArrow", "ShortLeftArrow", "ShortLeftArrow", 
7248 "ShortRightArrow", "ShortRightArrow", "ShortUpArrow", "ShortUpArrow", "Sigma", "Sigma", "SmallCircle", "SmallCircle", "Sopf", "Sopf", "Sqrt", "Sqrt", "Square", "Square", "SquareIntersection", "SquareIntersection", "SquareSubset", "SquareSubset", "SquareSubsetEqual", "SquareSubsetEqual", "SquareSuperset", "SquareSuperset", 
7249 "SquareSupersetEqual", "SquareSupersetEqual", "SquareUnion", "SquareUnion", "Sscr", "Sscr", "Star", "Star", "Sub", "Sub", "Subset", "Subset", "SubsetEqual", "SubsetEqual", "Succeeds", "Succeeds", "SucceedsEqual", "SucceedsEqual", "SucceedsSlantEqual", "SucceedsSlantEqual", "SucceedsTilde", "SucceedsTilde", "SuchThat", 
7250 "SuchThat", "Sum", "Sum", "Sup", "Sup", "Superset", "Superset", "SupersetEqual", "SupersetEqual", "Supset", "Supset", "THORN", "THORN", "TRADE", "TRADE", "TSHcy", "TSHcy", "TScy", "TScy", "Tab", "Tab", "Tau", "Tau", "Tcaron", "Tcaron", "Tcedil", "Tcedil", "Tcy", "Tcy", "Tfr", "Tfr", "Therefore", "Therefore", "Theta", "Theta", 
7251 "ThinSpace", "ThinSpace", "Tilde", "Tilde", "TildeEqual", "TildeEqual", "TildeFullEqual", "TildeFullEqual", "TildeTilde", "TildeTilde", "Topf", "Topf", "TripleDot", "TripleDot", "Tscr", "Tscr", "Tstrok", "Tstrok", "Uacute", "Uacute", "Uarr", "Uarr", "Uarrocir", "Uarrocir", "Ubrcy", "Ubrcy", "Ubreve", "Ubreve", "Ucirc", 
7252 "Ucirc", "Ucy", "Ucy", "Udblac", "Udblac", "Ufr", "Ufr", "Ugrave", "Ugrave", "Umacr", "Umacr", "UnderBar", "UnderBar", "UnderBrace", "UnderBrace", "UnderBracket", "UnderBracket", "UnderParenthesis", "UnderParenthesis", "Union", "Union", "UnionPlus", "UnionPlus", "Uogon", "Uogon", "Uopf", "Uopf", "UpArrow", "UpArrow", "UpArrowBar", 
7253 "UpArrowBar", "UpArrowDownArrow", "UpArrowDownArrow", "UpDownArrow", "UpDownArrow", "UpEquilibrium", "UpEquilibrium", "UpTee", "UpTee", "UpTeeArrow", "UpTeeArrow", "Uparrow", "Uparrow", "Updownarrow", "Updownarrow", "UpperLeftArrow", "UpperLeftArrow", "UpperRightArrow", "UpperRightArrow", "Upsi", "Upsi", "Upsilon", "Upsilon", 
7254 "Uring", "Uring", "Uscr", "Uscr", "Utilde", "Utilde", "Uuml", "Uuml", "VDash", "VDash", "Vbar", "Vbar", "Vcy", "Vcy", "Vdash", "Vdash", "Vdashl", "Vdashl", "Vee", "Vee", "Verbar", "Verbar", "Vert", "Vert", "VerticalBar", "VerticalBar", "VerticalLine", "VerticalLine", "VerticalSeparator", "VerticalSeparator", "VerticalTilde", 
7255 "VerticalTilde", "VeryThinSpace", "VeryThinSpace", "Vfr", "Vfr", "Vopf", "Vopf", "Vscr", "Vscr", "Vvdash", "Vvdash", "Wcirc", "Wcirc", "Wedge", "Wedge", "Wfr", "Wfr", "Wopf", "Wopf", "Wscr", "Wscr", "Xfr", "Xfr", "Xi", "Xi", "Xopf", "Xopf", "Xscr", "Xscr", "YAcy", "YAcy", "YIcy", "YIcy", "YUcy", "YUcy", "Yacute", "Yacute", 
7256 "Ycirc", "Ycirc", "Ycy", "Ycy", "Yfr", "Yfr", "Yopf", "Yopf", "Yscr", "Yscr", "Yuml", "Yuml", "ZHcy", "ZHcy", "Zacute", "Zacute", "Zcaron", "Zcaron", "Zcy", "Zcy", "Zdot", "Zdot", "ZeroWidthSpace", "ZeroWidthSpace", "Zeta", "Zeta", "Zfr", "Zfr", "Zopf", "Zopf", "Zscr", "Zscr", "aacute", "aacute", "abreve", "abreve", "ac", 
7257 "ac", "acd", "acd", "acirc", "acirc", "acute", "acute", "acy", "acy", "aelig", "aelig", "af", "af", "afr", "afr", "agrave", "agrave", "alefsym", "alefsym", "aleph", "aleph", "alpha", "alpha", "amacr", "amacr", "amalg", "amalg", "and", "and", "andand", "andand", "andd", "andd", "andslope", "andslope", "andv", "andv", "ang", 
7258 "ang", "ange", "ange", "angle", "angle", "angmsd", "angmsd", "angmsdaa", "angmsdaa", "angmsdab", "angmsdab", "angmsdac", "angmsdac", "angmsdad", "angmsdad", "angmsdae", "angmsdae", "angmsdaf", "angmsdaf", "angmsdag", "angmsdag", "angmsdah", "angmsdah", "angrt", "angrt", "angrtvb", "angrtvb", "angrtvbd", "angrtvbd", "angsph", 
7259 "angsph", "angst", "angst", "angzarr", "angzarr", "aogon", "aogon", "aopf", "aopf", "ap", "ap", "apE", "apE", "apacir", "apacir", "ape", "ape", "apid", "apid", "approx", "approx", "approxeq", "approxeq", "aring", "aring", "ascr", "ascr", "ast", "ast", "asymp", "asymp", "asympeq", "asympeq", "atilde", "atilde", "auml", 
7260 "auml", "awconint", "awconint", "awint", "awint", "bNot", "bNot", "backcong", "backcong", "backepsilon", "backepsilon", "backprime", "backprime", "backsim", "backsim", "backsimeq", "backsimeq", "barvee", "barvee", "barwed", "barwed", "barwedge", "barwedge", "bbrk", "bbrk", "bbrktbrk", "bbrktbrk", "bcong", "bcong", "bcy", 
7261 "bcy", "bdquo", "bdquo", "becaus", "becaus", "because", "because", "bemptyv", "bemptyv", "bepsi", "bepsi", "bernou", "bernou", "beta", "beta", "beth", "beth", "between", "between", "bfr", "bfr", "bigcap", "bigcap", "bigcirc", "bigcirc", "bigcup", "bigcup", "bigodot", "bigodot", "bigoplus", "bigoplus", "bigotimes", "bigotimes", 
7262 "bigsqcup", "bigsqcup", "bigstar", "bigstar", "bigtriangledown", "bigtriangledown", "bigtriangleup", "bigtriangleup", "biguplus", "biguplus", "bigvee", "bigvee", "bigwedge", "bigwedge", "bkarow", "bkarow", "blacklozenge", "blacklozenge", "blacksquare", "blacksquare", "blacktriangle", "blacktriangle", "blacktriangledown", 
7263 "blacktriangledown", "blacktriangleleft", "blacktriangleleft", "blacktriangleright", "blacktriangleright", "blank", "blank", "blk12", "blk12", "blk14", "blk14", "blk34", "blk34", "block", "block", "bnot", "bnot", "bopf", "bopf", "bot", "bot", "bottom", "bottom", "bowtie", "bowtie", "boxDL", "boxDL", "boxDR", "boxDR", "boxDl", 
7264 "boxDl", "boxDr", "boxDr", "boxH", "boxH", "boxHD", "boxHD", "boxHU", "boxHU", "boxHd", "boxHd", "boxHu", "boxHu", "boxUL", "boxUL", "boxUR", "boxUR", "boxUl", "boxUl", "boxUr", "boxUr", "boxV", "boxV", "boxVH", "boxVH", "boxVL", "boxVL", "boxVR", "boxVR", "boxVh", "boxVh", "boxVl", "boxVl", "boxVr", "boxVr", "boxbox", 
7265 "boxbox", "boxdL", "boxdL", "boxdR", "boxdR", "boxdl", "boxdl", "boxdr", "boxdr", "boxh", "boxh", "boxhD", "boxhD", "boxhU", "boxhU", "boxhd", "boxhd", "boxhu", "boxhu", "boxminus", "boxminus", "boxplus", "boxplus", "boxtimes", "boxtimes", "boxuL", "boxuL", "boxuR", "boxuR", "boxul", "boxul", "boxur", "boxur", "boxv", 
7266 "boxv", "boxvH", "boxvH", "boxvL", "boxvL", "boxvR", "boxvR", "boxvh", "boxvh", "boxvl", "boxvl", "boxvr", "boxvr", "bprime", "bprime", "breve", "breve", "brvbar", "brvbar", "bscr", "bscr", "bsemi", "bsemi", "bsim", "bsim", "bsime", "bsime", "bsol", "bsol", "bsolb", "bsolb", "bsolhsub", "bsolhsub", "bull", "bull", "bullet", 
7267 "bullet", "bump", "bump", "bumpE", "bumpE", "bumpe", "bumpe", "bumpeq", "bumpeq", "cacute", "cacute", "cap", "cap", "capand", "capand", "capbrcup", "capbrcup", "capcap", "capcap", "capcup", "capcup", "capdot", "capdot", "caret", "caret", "caron", "caron", "ccaps", "ccaps", "ccaron", "ccaron", "ccedil", "ccedil", "ccirc", 
7268 "ccirc", "ccups", "ccups", "ccupssm", "ccupssm", "cdot", "cdot", "cedil", "cedil", "cemptyv", "cemptyv", "cent", "cent", "centerdot", "centerdot", "cfr", "cfr", "chcy", "chcy", "check", "check", "checkmark", "checkmark", "chi", "chi", "cir", "cir", "cirE", "cirE", "circ", "circ", "circeq", "circeq", "circlearrowleft", 
7269 "circlearrowleft", "circlearrowright", "circlearrowright", "circledR", "circledR", "circledS", "circledS", "circledast", "circledast", "circledcirc", "circledcirc", "circleddash", "circleddash", "cire", "cire", "cirfnint", "cirfnint", "cirmid", "cirmid", "cirscir", "cirscir", "clubs", "clubs", "clubsuit", "clubsuit", "colon", 
7270 "colon", "colone", "colone", "coloneq", "coloneq", "comma", "comma", "commat", "commat", "comp", "comp", "compfn", "compfn", "complement", "complement", "complexes", "complexes", "cong", "cong", "congdot", "congdot", "conint", "conint", "copf", "copf", "coprod", "coprod", "copy", "copy", "copysr", "copysr", "crarr", "crarr", 
7271 "cross", "cross", "cscr", "cscr", "csub", "csub", "csube", "csube", "csup", "csup", "csupe", "csupe", "ctdot", "ctdot", "cudarrl", "cudarrl", "cudarrr", "cudarrr", "cuepr", "cuepr", "cuesc", "cuesc", "cularr", "cularr", "cularrp", "cularrp", "cup", "cup", "cupbrcap", "cupbrcap", "cupcap", "cupcap", "cupcup", "cupcup", 
7272 "cupdot", "cupdot", "cupor", "cupor", "curarr", "curarr", "curarrm", "curarrm", "curlyeqprec", "curlyeqprec", "curlyeqsucc", "curlyeqsucc", "curlyvee", "curlyvee", "curlywedge", "curlywedge", "curren", "curren", "curvearrowleft", "curvearrowleft", "curvearrowright", "curvearrowright", "cuvee", "cuvee", "cuwed", "cuwed", 
7273 "cwconint", "cwconint", "cwint", "cwint", "cylcty", "cylcty", "dArr", "dArr", "dHar", "dHar", "dagger", "dagger", "daleth", "daleth", "darr", "darr", "dash", "dash", "dashv", "dashv", "dbkarow", "dbkarow", "dblac", "dblac", "dcaron", "dcaron", "dcy", "dcy", "dd", "dd", "ddagger", "ddagger", "ddarr", "ddarr", "ddotseq", 
7274 "ddotseq", "deg", "deg", "delta", "delta", "demptyv", "demptyv", "dfisht", "dfisht", "dfr", "dfr", "dharl", "dharl", "dharr", "dharr", "diam", "diam", "diamond", "diamond", "diamondsuit", "diamondsuit", "diams", "diams", "die", "die", "digamma", "digamma", "disin", "disin", "div", "div", "divide", "divide", "divideontimes", 
7275 "divideontimes", "divonx", "divonx", "djcy", "djcy", "dlcorn", "dlcorn", "dlcrop", "dlcrop", "dollar", "dollar", "dopf", "dopf", "dot", "dot", "doteq", "doteq", "doteqdot", "doteqdot", "dotminus", "dotminus", "dotplus", "dotplus", "dotsquare", "dotsquare", "doublebarwedge", "doublebarwedge", "downarrow", "downarrow", "downdownarrows", 
7276 "downdownarrows", "downharpoonleft", "downharpoonleft", "downharpoonright", "downharpoonright", "drbkarow", "drbkarow", "drcorn", "drcorn", "drcrop", "drcrop", "dscr", "dscr", "dscy", "dscy", "dsol", "dsol", "dstrok", "dstrok", "dtdot", "dtdot", "dtri", "dtri", "dtrif", "dtrif", "duarr", "duarr", "duhar", "duhar", "dwangle", 
7277 "dwangle", "dzcy", "dzcy", "dzigrarr", "dzigrarr", "eDDot", "eDDot", "eDot", "eDot", "eacute", "eacute", "easter", "easter", "ecaron", "ecaron", "ecir", "ecir", "ecirc", "ecirc", "ecolon", "ecolon", "ecy", "ecy", "edot", "edot", "ee", "ee", "efDot", "efDot", "efr", "efr", "eg", "eg", "egrave", "egrave", "egs", "egs", "egsdot", 
7278 "egsdot", "el", "el", "elinters", "elinters", "ell", "ell", "els", "els", "elsdot", "elsdot", "emacr", "emacr", "empty", "empty", "emptyset", "emptyset", "emptyv", "emptyv", "emsp", "emsp", "emsp13", "emsp13", "emsp14", "emsp14", "eng", "eng", "ensp", "ensp", "eogon", "eogon", "eopf", "eopf", "epar", "epar", "eparsl", 
7279 "eparsl", "eplus", "eplus", "epsi", "epsi", "epsilon", "epsilon", "epsiv", "epsiv", "eqcirc", "eqcirc", "eqcolon", "eqcolon", "eqsim", "eqsim", "eqslantgtr", "eqslantgtr", "eqslantless", "eqslantless", "equals", "equals", "equest", "equest", "equiv", "equiv", "equivDD", "equivDD", "eqvparsl", "eqvparsl", "erDot", "erDot", 
7280 "erarr", "erarr", "escr", "escr", "esdot", "esdot", "esim", "esim", "eta", "eta", "eth", "eth", "euml", "euml", "euro", "euro", "excl", "excl", "exist", "exist", "expectation", "expectation", "exponentiale", "exponentiale", "fallingdotseq", "fallingdotseq", "fcy", "fcy", "female", "female", "ffilig", "ffilig", "fflig", 
7281 "fflig", "ffllig", "ffllig", "ffr", "ffr", "filig", "filig", "flat", "flat", "fllig", "fllig", "fltns", "fltns", "fnof", "fnof", "fopf", "fopf", "forall", "forall", "fork", "fork", "forkv", "forkv", "fpartint", "fpartint", "frac12", "frac12", "frac13", "frac13", "frac14", "frac14", "frac15", "frac15", "frac16", "frac16", 
7282 "frac18", "frac18", "frac23", "frac23", "frac25", "frac25", "frac34", "frac34", "frac35", "frac35", "frac38", "frac38", "frac45", "frac45", "frac56", "frac56", "frac58", "frac58", "frac78", "frac78", "frasl", "frasl", "frown", "frown", "fscr", "fscr", "gE", "gE", "gEl", "gEl", "gacute", "gacute", "gamma", "gamma", "gammad", 
7283 "gammad", "gap", "gap", "gbreve", "gbreve", "gcirc", "gcirc", "gcy", "gcy", "gdot", "gdot", "ge", "ge", "gel", "gel", "geq", "geq", "geqq", "geqq", "geqslant", "geqslant", "ges", "ges", "gescc", "gescc", "gesdot", "gesdot", "gesdoto", "gesdoto", "gesdotol", "gesdotol", "gesles", "gesles", "gfr", "gfr", "gg", "gg", "ggg", 
7284 "ggg", "gimel", "gimel", "gjcy", "gjcy", "gl", "gl", "glE", "glE", "gla", "gla", "glj", "glj", "gnE", "gnE", "gnap", "gnap", "gnapprox", "gnapprox", "gne", "gne", "gneq", "gneq", "gneqq", "gneqq", "gnsim", "gnsim", "gopf", "gopf", "grave", "grave", "gscr", "gscr", "gsim", "gsim", "gsime", "gsime", "gsiml", "gsiml", "gtcc", 
7285 "gtcc", "gtcir", "gtcir", "gtdot", "gtdot", "gtlPar", "gtlPar", "gtquest", "gtquest", "gtrapprox", "gtrapprox", "gtrarr", "gtrarr", "gtrdot", "gtrdot", "gtreqless", "gtreqless", "gtreqqless", "gtreqqless", "gtrless", "gtrless", "gtrsim", "gtrsim", "hArr", "hArr", "hairsp", "hairsp", "half", "half", "hamilt", "hamilt", 
7286 "hardcy", "hardcy", "harr", "harr", "harrcir", "harrcir", "harrw", "harrw", "hbar", "hbar", "hcirc", "hcirc", "hearts", "hearts", "heartsuit", "heartsuit", "hellip", "hellip", "hercon", "hercon", "hfr", "hfr", "hksearow", "hksearow", "hkswarow", "hkswarow", "hoarr", "hoarr", "homtht", "homtht", "hookleftarrow", "hookleftarrow", 
7287 "hookrightarrow", "hookrightarrow", "hopf", "hopf", "horbar", "horbar", "hscr", "hscr", "hslash", "hslash", "hstrok", "hstrok", "hybull", "hybull", "hyphen", "hyphen", "iacute", "iacute", "ic", "ic", "icirc", "icirc", "icy", "icy", "iecy", "iecy", "iexcl", "iexcl", "iff", "iff", "ifr", "ifr", "igrave", "igrave", "ii", 
7288 "ii", "iiiint", "iiiint", "iiint", "iiint", "iinfin", "iinfin", "iiota", "iiota", "ijlig", "ijlig", "imacr", "imacr", "image", "image", "imagline", "imagline", "imagpart", "imagpart", "imath", "imath", "imof", "imof", "imped", "imped", "in", "in", "incare", "incare", "infin", "infin", "infintie", "infintie", "inodot", 
7289 "inodot", "int", "int", "intcal", "intcal", "integers", "integers", "intercal", "intercal", "intlarhk", "intlarhk", "intprod", "intprod", "iocy", "iocy", "iogon", "iogon", "iopf", "iopf", "iota", "iota", "iprod", "iprod", "iquest", "iquest", "iscr", "iscr", "isin", "isin", "isinE", "isinE", "isindot", "isindot", "isins", 
7290 "isins", "isinsv", "isinsv", "isinv", "isinv", "it", "it", "itilde", "itilde", "iukcy", "iukcy", "iuml", "iuml", "jcirc", "jcirc", "jcy", "jcy", "jfr", "jfr", "jmath", "jmath", "jopf", "jopf", "jscr", "jscr", "jsercy", "jsercy", "jukcy", "jukcy", "kappa", "kappa", "kappav", "kappav", "kcedil", "kcedil", "kcy", "kcy", "kfr", 
7291 "kfr", "kgreen", "kgreen", "khcy", "khcy", "kjcy", "kjcy", "kopf", "kopf", "kscr", "kscr", "lAarr", "lAarr", "lArr", "lArr", "lAtail", "lAtail", "lBarr", "lBarr", "lE", "lE", "lEg", "lEg", "lHar", "lHar", "lacute", "lacute", "laemptyv", "laemptyv", "lagran", "lagran", "lambda", "lambda", "lang", "lang", "langd", "langd", 
7292 "langle", "langle", "lap", "lap", "laquo", "laquo", "larr", "larr", "larrb", "larrb", "larrbfs", "larrbfs", "larrfs", "larrfs", "larrhk", "larrhk", "larrlp", "larrlp", "larrpl", "larrpl", "larrsim", "larrsim", "larrtl", "larrtl", "lat", "lat", "latail", "latail", "late", "late", "lbarr", "lbarr", "lbbrk", "lbbrk", "lbrace", 
7293 "lbrace", "lbrack", "lbrack", "lbrke", "lbrke", "lbrksld", "lbrksld", "lbrkslu", "lbrkslu", "lcaron", "lcaron", "lcedil", "lcedil", "lceil", "lceil", "lcub", "lcub", "lcy", "lcy", "ldca", "ldca", "ldquo", "ldquo", "ldquor", "ldquor", "ldrdhar", "ldrdhar", "ldrushar", "ldrushar", "ldsh", "ldsh", "le", "le", "leftarrow", 
7294 "leftarrow", "leftarrowtail", "leftarrowtail", "leftharpoondown", "leftharpoondown", "leftharpoonup", "leftharpoonup", "leftleftarrows", "leftleftarrows", "leftrightarrow", "leftrightarrow", "leftrightarrows", "leftrightarrows", "leftrightharpoons", "leftrightharpoons", "leftrightsquigarrow", "leftrightsquigarrow", "leftthreetimes", 
7295 "leftthreetimes", "leg", "leg", "leq", "leq", "leqq", "leqq", "leqslant", "leqslant", "les", "les", "lescc", "lescc", "lesdot", "lesdot", "lesdoto", "lesdoto", "lesdotor", "lesdotor", "lesges", "lesges", "lessapprox", "lessapprox", "lessdot", "lessdot", "lesseqgtr", "lesseqgtr", "lesseqqgtr", "lesseqqgtr", "lessgtr", "lessgtr", 
7296 "lesssim", "lesssim", "lfisht", "lfisht", "lfloor", "lfloor", "lfr", "lfr", "lg", "lg", "lgE", "lgE", "lhard", "lhard", "lharu", "lharu", "lharul", "lharul", "lhblk", "lhblk", "ljcy", "ljcy", "ll", "ll", "llarr", "llarr", "llcorner", "llcorner", "llhard", "llhard", "lltri", "lltri", "lmidot", "lmidot", "lmoust", "lmoust", 
7297 "lmoustache", "lmoustache", "lnE", "lnE", "lnap", "lnap", "lnapprox", "lnapprox", "lne", "lne", "lneq", "lneq", "lneqq", "lneqq", "lnsim", "lnsim", "loang", "loang", "loarr", "loarr", "lobrk", "lobrk", "longleftarrow", "longleftarrow", "longleftrightarrow", "longleftrightarrow", "longmapsto", "longmapsto", "longrightarrow", 
7298 "longrightarrow", "looparrowleft", "looparrowleft", "looparrowright", "looparrowright", "lopar", "lopar", "lopf", "lopf", "loplus", "loplus", "lotimes", "lotimes", "lowast", "lowast", "lowbar", "lowbar", "loz", "loz", "lozenge", "lozenge", "lozf", "lozf", "lpar", "lpar", "lparlt", "lparlt", "lrarr", "lrarr", "lrcorner", 
7299 "lrcorner", "lrhar", "lrhar", "lrhard", "lrhard", "lrm", "lrm", "lrtri", "lrtri", "lsaquo", "lsaquo", "lscr", "lscr", "lsh", "lsh", "lsim", "lsim", "lsime", "lsime", "lsimg", "lsimg", "lsqb", "lsqb", "lsquo", "lsquo", "lsquor", "lsquor", "lstrok", "lstrok", "ltcc", "ltcc", "ltcir", "ltcir", "ltdot", "ltdot", "lthree", 
7300 "lthree", "ltimes", "ltimes", "ltlarr", "ltlarr", "ltquest", "ltquest", "ltrPar", "ltrPar", "ltri", "ltri", "ltrie", "ltrie", "ltrif", "ltrif", "lurdshar", "lurdshar", "luruhar", "luruhar", "mDDot", "mDDot", "macr", "macr", "male", "male", "malt", "malt", "maltese", "maltese", "map", "map", "mapsto", "mapsto", "mapstodown", 
7301 "mapstodown", "mapstoleft", "mapstoleft", "mapstoup", "mapstoup", "marker", "marker", "mcomma", "mcomma", "mcy", "mcy", "mdash", "mdash", "measuredangle", "measuredangle", "mfr", "mfr", "mho", "mho", "micro", "micro", "mid", "mid", "midast", "midast", "midcir", "midcir", "middot", "middot", "minus", "minus", "minusb", 
7302 "minusb", "minusd", "minusd", "minusdu", "minusdu", "mlcp", "mlcp", "mldr", "mldr", "mnplus", "mnplus", "models", "models", "mopf", "mopf", "mp", "mp", "mscr", "mscr", "mstpos", "mstpos", "mu", "mu", "multimap", "multimap", "mumap", "mumap", "nLeftarrow", "nLeftarrow", "nLeftrightarrow", "nLeftrightarrow", "nRightarrow", 
7303 "nRightarrow", "nVDash", "nVDash", "nVdash", "nVdash", "nabla", "nabla", "nacute", "nacute", "nap", "nap", "napos", "napos", "napprox", "napprox", "natur", "natur", "natural", "natural", "naturals", "naturals", "nbsp", "nbsp", "ncap", "ncap", "ncaron", "ncaron", "ncedil", "ncedil", "ncong", "ncong", "ncup", "ncup", "ncy", 
7304 "ncy", "ndash", "ndash", "ne", "ne", "neArr", "neArr", "nearhk", "nearhk", "nearr", "nearr", "nearrow", "nearrow", "nequiv", "nequiv", "nesear", "nesear", "nexist", "nexist", "nexists", "nexists", "nfr", "nfr", "nge", "nge", "ngeq", "ngeq", "ngsim", "ngsim", "ngt", "ngt", "ngtr", "ngtr", "nhArr", "nhArr", "nharr", "nharr", 
7305 "nhpar", "nhpar", "ni", "ni", "nis", "nis", "nisd", "nisd", "niv", "niv", "njcy", "njcy", "nlArr", "nlArr", "nlarr", "nlarr", "nldr", "nldr", "nle", "nle", "nleftarrow", "nleftarrow", "nleftrightarrow", "nleftrightarrow", "nleq", "nleq", "nless", "nless", "nlsim", "nlsim", "nlt", "nlt", "nltri", "nltri", "nltrie", "nltrie", 
7306 "nmid", "nmid", "nopf", "nopf", "not", "not", "notin", "notin", "notinva", "notinva", "notinvb", "notinvb", "notinvc", "notinvc", "notni", "notni", "notniva", "notniva", "notnivb", "notnivb", "notnivc", "notnivc", "npar", "npar", "nparallel", "nparallel", "npolint", "npolint", "npr", "npr", "nprcue", "nprcue", "nprec", 
7307 "nprec", "nrArr", "nrArr", "nrarr", "nrarr", "nrightarrow", "nrightarrow", "nrtri", "nrtri", "nrtrie", "nrtrie", "nsc", "nsc", "nsccue", "nsccue", "nscr", "nscr", "nshortmid", "nshortmid", "nshortparallel", "nshortparallel", "nsim", "nsim", "nsime", "nsime", "nsimeq", "nsimeq", "nsmid", "nsmid", "nspar", "nspar", "nsqsube", 
7308 "nsqsube", "nsqsupe", "nsqsupe", "nsub", "nsub", "nsube", "nsube", "nsubseteq", "nsubseteq", "nsucc", "nsucc", "nsup", "nsup", "nsupe", "nsupe", "nsupseteq", "nsupseteq", "ntgl", "ntgl", "ntilde", "ntilde", "ntlg", "ntlg", "ntriangleleft", "ntriangleleft", "ntrianglelefteq", "ntrianglelefteq", "ntriangleright", "ntriangleright", 
7309 "ntrianglerighteq", "ntrianglerighteq", "nu", "nu", "num", "num", "numero", "numero", "numsp", "numsp", "nvDash", "nvDash", "nvHarr", "nvHarr", "nvdash", "nvdash", "nvinfin", "nvinfin", "nvlArr", "nvlArr", "nvrArr", "nvrArr", "nwArr", "nwArr", "nwarhk", "nwarhk", "nwarr", "nwarr", "nwarrow", "nwarrow", "nwnear", "nwnear", 
7310 "oS", "oS", "oacute", "oacute", "oast", "oast", "ocir", "ocir", "ocirc", "ocirc", "ocy", "ocy", "odash", "odash", "odblac", "odblac", "odiv", "odiv", "odot", "odot", "odsold", "odsold", "oelig", "oelig", "ofcir", "ofcir", "ofr", "ofr", "ogon", "ogon", "ograve", "ograve", "ogt", "ogt", "ohbar", "ohbar", "ohm", "ohm", "oint", 
7311 "oint", "olarr", "olarr", "olcir", "olcir", "olcross", "olcross", "oline", "oline", "olt", "olt", "omacr", "omacr", "omega", "omega", "omicron", "omicron", "omid", "omid", "ominus", "ominus", "oopf", "oopf", "opar", "opar", "operp", "operp", "oplus", "oplus", "or", "or", "orarr", "orarr", "ord", "ord", "order", "order", 
7312 "orderof", "orderof", "ordf", "ordf", "ordm", "ordm", "origof", "origof", "oror", "oror", "orslope", "orslope", "orv", "orv", "oscr", "oscr", "oslash", "oslash", "osol", "osol", "otilde", "otilde", "otimes", "otimes", "otimesas", "otimesas", "ouml", "ouml", "ovbar", "ovbar", "par", "par", "para", "para", "parallel", "parallel", 
7313 "parsim", "parsim", "parsl", "parsl", "part", "part", "pcy", "pcy", "percnt", "percnt", "period", "period", "permil", "permil", "perp", "perp", "pertenk", "pertenk", "pfr", "pfr", "phi", "phi", "phiv", "phiv", "phmmat", "phmmat", "phone", "phone", "pi", "pi", "pitchfork", "pitchfork", "piv", "piv", "planck", "planck", 
7314 "planckh", "planckh", "plankv", "plankv", "plus", "plus", "plusacir", "plusacir", "plusb", "plusb", "pluscir", "pluscir", "plusdo", "plusdo", "plusdu", "plusdu", "pluse", "pluse", "plusmn", "plusmn", "plussim", "plussim", "plustwo", "plustwo", "pm", "pm", "pointint", "pointint", "popf", "popf", "pound", "pound", "pr", 
7315 "pr", "prE", "prE", "prap", "prap", "prcue", "prcue", "pre", "pre", "prec", "prec", "precapprox", "precapprox", "preccurlyeq", "preccurlyeq", "preceq", "preceq", "precnapprox", "precnapprox", "precneqq", "precneqq", "precnsim", "precnsim", "precsim", "precsim", "prime", "prime", "primes", "primes", "prnE", "prnE", "prnap", 
7316 "prnap", "prnsim", "prnsim", "prod", "prod", "profalar", "profalar", "profline", "profline", "profsurf", "profsurf", "prop", "prop", "propto", "propto", "prsim", "prsim", "prurel", "prurel", "pscr", "pscr", "psi", "psi", "puncsp", "puncsp", "qfr", "qfr", "qint", "qint", "qopf", "qopf", "qprime", "qprime", "qscr", "qscr", 
7317 "quaternions", "quaternions", "quatint", "quatint", "quest", "quest", "questeq", "questeq", "rAarr", "rAarr", "rArr", "rArr", "rAtail", "rAtail", "rBarr", "rBarr", "rHar", "rHar", "racute", "racute", "radic", "radic", "raemptyv", "raemptyv", "rang", "rang", "rangd", "rangd", "range", "range", "rangle", "rangle", "raquo", 
7318 "raquo", "rarr", "rarr", "rarrap", "rarrap", "rarrb", "rarrb", "rarrbfs", "rarrbfs", "rarrc", "rarrc", "rarrfs", "rarrfs", "rarrhk", "rarrhk", "rarrlp", "rarrlp", "rarrpl", "rarrpl", "rarrsim", "rarrsim", "rarrtl", "rarrtl", "rarrw", "rarrw", "ratail", "ratail", "ratio", "ratio", "rationals", "rationals", "rbarr", "rbarr", 
7319 "rbbrk", "rbbrk", "rbrace", "rbrace", "rbrack", "rbrack", "rbrke", "rbrke", "rbrksld", "rbrksld", "rbrkslu", "rbrkslu", "rcaron", "rcaron", "rcedil", "rcedil", "rceil", "rceil", "rcub", "rcub", "rcy", "rcy", "rdca", "rdca", "rdldhar", "rdldhar", "rdquo", "rdquo", "rdquor", "rdquor", "rdsh", "rdsh", "real", "real", "realine", 
7320 "realine", "realpart", "realpart", "reals", "reals", "rect", "rect", "reg", "reg", "rfisht", "rfisht", "rfloor", "rfloor", "rfr", "rfr", "rhard", "rhard", "rharu", "rharu", "rharul", "rharul", "rho", "rho", "rhov", "rhov", "rightarrow", "rightarrow", "rightarrowtail", "rightarrowtail", "rightharpoondown", "rightharpoondown", 
7321 "rightharpoonup", "rightharpoonup", "rightleftarrows", "rightleftarrows", "rightleftharpoons", "rightleftharpoons", "rightrightarrows", "rightrightarrows", "rightsquigarrow", "rightsquigarrow", "rightthreetimes", "rightthreetimes", "ring", "ring", "risingdotseq", "risingdotseq", "rlarr", "rlarr", "rlhar", "rlhar", "rlm", 
7322 "rlm", "rmoust", "rmoust", "rmoustache", "rmoustache", "rnmid", "rnmid", "roang", "roang", "roarr", "roarr", "robrk", "robrk", "ropar", "ropar", "ropf", "ropf", "roplus", "roplus", "rotimes", "rotimes", "rpar", "rpar", "rpargt", "rpargt", "rppolint", "rppolint", "rrarr", "rrarr", "rsaquo", "rsaquo", "rscr", "rscr", "rsh", 
7323 "rsh", "rsqb", "rsqb", "rsquo", "rsquo", "rsquor", "rsquor", "rthree", "rthree", "rtimes", "rtimes", "rtri", "rtri", "rtrie", "rtrie", "rtrif", "rtrif", "rtriltri", "rtriltri", "ruluhar", "ruluhar", "rx", "rx", "sacute", "sacute", "sbquo", "sbquo", "sc", "sc", "scE", "scE", "scap", "scap", "scaron", "scaron", "sccue", 
7324 "sccue", "sce", "sce", "scedil", "scedil", "scirc", "scirc", "scnE", "scnE", "scnap", "scnap", "scnsim", "scnsim", "scpolint", "scpolint", "scsim", "scsim", "scy", "scy", "sdot", "sdot", "sdotb", "sdotb", "sdote", "sdote", "seArr", "seArr", "searhk", "searhk", "searr", "searr", "searrow", "searrow", "sect", "sect", "semi", 
7325 "semi", "seswar", "seswar", "setminus", "setminus", "setmn", "setmn", "sext", "sext", "sfr", "sfr", "sfrown", "sfrown", "sharp", "sharp", "shchcy", "shchcy", "shcy", "shcy", "shortmid", "shortmid", "shortparallel", "shortparallel", "shy", "shy", "sigma", "sigma", "sigmaf", "sigmaf", "sigmav", "sigmav", "sim", "sim", "simdot", 
7326 "simdot", "sime", "sime", "simeq", "simeq", "simg", "simg", "simgE", "simgE", "siml", "siml", "simlE", "simlE", "simne", "simne", "simplus", "simplus", "simrarr", "simrarr", "slarr", "slarr", "smallsetminus", "smallsetminus", "smashp", "smashp", "smeparsl", "smeparsl", "smid", "smid", "smile", "smile", "smt", "smt", "smte", 
7327 "smte", "softcy", "softcy", "sol", "sol", "solb", "solb", "solbar", "solbar", "sopf", "sopf", "spades", "spades", "spadesuit", "spadesuit", "spar", "spar", "sqcap", "sqcap", "sqcup", "sqcup", "sqsub", "sqsub", "sqsube", "sqsube", "sqsubset", "sqsubset", "sqsubseteq", "sqsubseteq", "sqsup", "sqsup", "sqsupe", "sqsupe", 
7328 "sqsupset", "sqsupset", "sqsupseteq", "sqsupseteq", "squ", "squ", "square", "square", "squarf", "squarf", "squf", "squf", "srarr", "srarr", "sscr", "sscr", "ssetmn", "ssetmn", "ssmile", "ssmile", "sstarf", "sstarf", "star", "star", "starf", "starf", "straightepsilon", "straightepsilon", "straightphi", "straightphi", "strns", 
7329 "strns", "sub", "sub", "subE", "subE", "subdot", "subdot", "sube", "sube", "subedot", "subedot", "submult", "submult", "subnE", "subnE", "subne", "subne", "subplus", "subplus", "subrarr", "subrarr", "subset", "subset", "subseteq", "subseteq", "subseteqq", "subseteqq", "subsetneq", "subsetneq", "subsetneqq", "subsetneqq", 
7330 "subsim", "subsim", "subsub", "subsub", "subsup", "subsup", "succ", "succ", "succapprox", "succapprox", "succcurlyeq", "succcurlyeq", "succeq", "succeq", "succnapprox", "succnapprox", "succneqq", "succneqq", "succnsim", "succnsim", "succsim", "succsim", "sum", "sum", "sung", "sung", "sup", "sup", "sup1", "sup1", "sup2", 
7331 "sup2", "sup3", "sup3", "supE", "supE", "supdot", "supdot", "supdsub", "supdsub", "supe", "supe", "supedot", "supedot", "suphsol", "suphsol", "suphsub", "suphsub", "suplarr", "suplarr", "supmult", "supmult", "supnE", "supnE", "supne", "supne", "supplus", "supplus", "supset", "supset", "supseteq", "supseteq", "supseteqq", 
7332 "supseteqq", "supsetneq", "supsetneq", "supsetneqq", "supsetneqq", "supsim", "supsim", "supsub", "supsub", "supsup", "supsup", "swArr", "swArr", "swarhk", "swarhk", "swarr", "swarr", "swarrow", "swarrow", "swnwar", "swnwar", "szlig", "szlig", "target", "target", "tau", "tau", "tbrk", "tbrk", "tcaron", "tcaron", "tcedil", 
7333 "tcedil", "tcy", "tcy", "tdot", "tdot", "telrec", "telrec", "tfr", "tfr", "there4", "there4", "therefore", "therefore", "theta", "theta", "thetasym", "thetasym", "thetav", "thetav", "thickapprox", "thickapprox", "thicksim", "thicksim", "thinsp", "thinsp", "thkap", "thkap", "thksim", "thksim", "thorn", "thorn", "tilde", 
7334 "tilde", "times", "times", "timesb", "timesb", "timesbar", "timesbar", "timesd", "timesd", "tint", "tint", "toea", "toea", "top", "top", "topbot", "topbot", "topcir", "topcir", "topf", "topf", "topfork", "topfork", "tosa", "tosa", "tprime", "tprime", "trade", "trade", "triangle", "triangle", "triangledown", "triangledown", 
7335 "triangleleft", "triangleleft", "trianglelefteq", "trianglelefteq", "triangleq", "triangleq", "triangleright", "triangleright", "trianglerighteq", "trianglerighteq", "tridot", "tridot", "trie", "trie", "triminus", "triminus", "triplus", "triplus", "trisb", "trisb", "tritime", "tritime", "trpezium", "trpezium", "tscr", 
7336 "tscr", "tscy", "tscy", "tshcy", "tshcy", "tstrok", "tstrok", "twixt", "twixt", "twoheadleftarrow", "twoheadleftarrow", "twoheadrightarrow", "twoheadrightarrow", "uArr", "uArr", "uHar", "uHar", "uacute", "uacute", "uarr", "uarr", "ubrcy", "ubrcy", "ubreve", "ubreve", "ucirc", "ucirc", "ucy", "ucy", "udarr", "udarr", "udblac", 
7337 "udblac", "udhar", "udhar", "ufisht", "ufisht", "ufr", "ufr", "ugrave", "ugrave", "uharl", "uharl", "uharr", "uharr", "uhblk", "uhblk", "ulcorn", "ulcorn", "ulcorner", "ulcorner", "ulcrop", "ulcrop", "ultri", "ultri", "umacr", "umacr", "uml", "uml", "uogon", "uogon", "uopf", "uopf", "uparrow", "uparrow", "updownarrow", 
7338 "updownarrow", "upharpoonleft", "upharpoonleft", "upharpoonright", "upharpoonright", "uplus", "uplus", "upsi", "upsi", "upsih", "upsih", "upsilon", "upsilon", "upuparrows", "upuparrows", "urcorn", "urcorn", "urcorner", "urcorner", "urcrop", "urcrop", "uring", "uring", "urtri", "urtri", "uscr", "uscr", "utdot", "utdot", 
7339 "utilde", "utilde", "utri", "utri", "utrif", "utrif", "uuarr", "uuarr", "uuml", "uuml", "uwangle", "uwangle", "vArr", "vArr", "vBar", "vBar", "vBarv", "vBarv", "vDash", "vDash", "vangrt", "vangrt", "varepsilon", "varepsilon", "varkappa", "varkappa", "varnothing", "varnothing", "varphi", "varphi", "varpi", "varpi", "varpropto", 
7340 "varpropto", "varr", "varr", "varrho", "varrho", "varsigma", "varsigma", "vartheta", "vartheta", "vartriangleleft", "vartriangleleft", "vartriangleright", "vartriangleright", "vcy", "vcy", "vdash", "vdash", "vee", "vee", "veebar", "veebar", "veeeq", "veeeq", "vellip", "vellip", "verbar", "verbar", "vert", "vert", "vfr", 
7341 "vfr", "vltri", "vltri", "vopf", "vopf", "vprop", "vprop", "vrtri", "vrtri", "vscr", "vscr", "vzigzag", "vzigzag", "wcirc", "wcirc", "wedbar", "wedbar", "wedge", "wedge", "wedgeq", "wedgeq", "weierp", "weierp", "wfr", "wfr", "wopf", "wopf", "wp", "wp", "wr", "wr", "wreath", "wreath", "wscr", "wscr", "xcap", "xcap", "xcirc", 
7342 "xcirc", "xcup", "xcup", "xdtri", "xdtri", "xfr", "xfr", "xhArr", "xhArr", "xharr", "xharr", "xi", "xi", "xlArr", "xlArr", "xlarr", "xlarr", "xmap", "xmap", "xnis", "xnis", "xodot", "xodot", "xopf", "xopf", "xoplus", "xoplus", "xotime", "xotime", "xrArr", "xrArr", "xrarr", "xrarr", "xscr", "xscr", "xsqcup", "xsqcup", "xuplus", 
7343 "xuplus", "xutri", "xutri", "xvee", "xvee", "xwedge", "xwedge", "yacute", "yacute", "yacy", "yacy", "ycirc", "ycirc", "ycy", "ycy", "yen", "yen", "yfr", "yfr", "yicy", "yicy", "yopf", "yopf", "yscr", "yscr", "yucy", "yucy", "yuml", "yuml", "zacute", "zacute", "zcaron", "zcaron", "zcy", "zcy", "zdot", "zdot", "zeetrf", 
7344 "zeetrf", "zeta", "zeta", "zfr", "zfr", "zhcy", "zhcy", "zigrarr", "zigrarr", "zopf", "zopf", "zscr", "zscr", "zwj", "zwj", "zwnj", "zwnj", ];
7345 
7346 immutable dchar[] availableEntitiesValues =
7347 ['\u00c6', '\u00c6', '\u0026', '\u0026', '\u00c1', '\u00c1', '\u0102', '\u0102', '\u00c2', '\u00c2', '\u0410', '\u0410', '\U0001d504', '\U0001d504', '\u00c0', '\u00c0', '\u0391', '\u0391', '\u0100', '\u0100', '\u2a53', '\u2a53', '\u0104', '\u0104', '\U0001d538', '\U0001d538', '\u2061', '\u2061', '\u00c5', '\u00c5', '\U0001d49c', '\U0001d49c', '\u2254', '\u2254', '\u00c3', 
7348 '\u00c3', '\u00c4', '\u00c4', '\u2216', '\u2216', '\u2ae7', '\u2ae7', '\u2306', '\u2306', '\u0411', '\u0411', '\u2235', '\u2235', '\u212c', '\u212c', '\u0392', '\u0392', '\U0001d505', '\U0001d505', '\U0001d539', '\U0001d539', '\u02d8', '\u02d8', '\u212c', '\u212c', '\u224e', '\u224e', '\u0427', '\u0427', '\u00a9', '\u00a9', '\u0106', '\u0106', '\u22d2', '\u22d2', '\u2145', 
7349 '\u2145', '\u212d', '\u212d', '\u010c', '\u010c', '\u00c7', '\u00c7', '\u0108', '\u0108', '\u2230', '\u2230', '\u010a', '\u010a', '\u00b8', '\u00b8', '\u00b7', '\u00b7', '\u212d', '\u212d', '\u03a7', '\u03a7', '\u2299', '\u2299', '\u2296', '\u2296', '\u2295', '\u2295', '\u2297', '\u2297', 
7350 '\u2232', '\u2232', '\u201d', '\u201d', '\u2019', '\u2019', '\u2237', '\u2237', '\u2a74', '\u2a74', '\u2261', '\u2261', '\u222f', '\u222f', '\u222e', '\u222e', '\u2102', '\u2102', '\u2210', '\u2210', '\u2233', 
7351 '\u2233', '\u2a2f', '\u2a2f', '\U0001d49e', '\U0001d49e', '\u22d3', '\u22d3', '\u224d', '\u224d', '\u2145', '\u2145', '\u2911', '\u2911', '\u0402', '\u0402', '\u0405', '\u0405', '\u040f', '\u040f', '\u2021', '\u2021', '\u21a1', '\u21a1', '\u2ae4', '\u2ae4', '\u010e', '\u010e', '\u0414', '\u0414', '\u2207', '\u2207', '\u0394', '\u0394', '\U0001d507', '\U0001d507', 
7352 '\u00b4', '\u00b4', '\u02d9', '\u02d9', '\u02dd', '\u02dd', '\u0060', '\u0060', '\u02dc', '\u02dc', '\u22c4', '\u22c4', '\u2146', '\u2146', '\U0001d53b', '\U0001d53b', '\u00a8', '\u00a8', '\u20dc', '\u20dc', '\u2250', 
7353 '\u2250', '\u222f', '\u222f', '\u00a8', '\u00a8', '\u21d3', '\u21d3', '\u21d0', '\u21d0', '\u21d4', '\u21d4', '\u2ae4', '\u2ae4', '\u27f8', '\u27f8', '\u27fa', 
7354 '\u27fa', '\u27f9', '\u27f9', '\u21d2', '\u21d2', '\u22a8', '\u22a8', '\u21d1', '\u21d1', '\u21d5', '\u21d5', '\u2225', '\u2225', '\u2193', '\u2193', '\u2913', '\u2913', 
7355 '\u21f5', '\u21f5', '\u0311', '\u0311', '\u2950', '\u2950', '\u295e', '\u295e', '\u21bd', '\u21bd', '\u2956', '\u2956', '\u295f', '\u295f', '\u21c1', '\u21c1', '\u2957', 
7356 '\u2957', '\u22a4', '\u22a4', '\u21a7', '\u21a7', '\u21d3', '\u21d3', '\U0001d49f', '\U0001d49f', '\u0110', '\u0110', '\u014a', '\u014a', '\u00d0', '\u00d0', '\u00c9', '\u00c9', '\u011a', '\u011a', '\u00ca', '\u00ca', '\u042d', '\u042d', '\u0116', '\u0116', '\U0001d508', '\U0001d508', '\u00c8', '\u00c8', '\u2208', '\u2208', '\u0112', '\u0112', 
7357 '\u25fb', '\u25fb', '\u25ab', '\u25ab', '\u0118', '\u0118', '\U0001d53c', '\U0001d53c', '\u0395', '\u0395', '\u2a75', '\u2a75', '\u2242', '\u2242', '\u21cc', '\u21cc', '\u2130', '\u2130', '\u2a73', '\u2a73', '\u0397', '\u0397', '\u00cb', '\u00cb', '\u2203', '\u2203', '\u2147', '\u2147', 
7358 '\u0424', '\u0424', '\U0001d509', '\U0001d509', '\u25fc', '\u25fc', '\u25aa', '\u25aa', '\U0001d53d', '\U0001d53d', '\u2200', '\u2200', '\u2131', '\u2131', '\u2131', '\u2131', '\u0403', '\u0403', '\u003e', '\u003e', '\u0393', '\u0393', '\u03dc', '\u03dc', '\u011e', '\u011e', '\u0122', '\u0122', '\u011c', '\u011c', 
7359 '\u0413', '\u0413', '\u0120', '\u0120', '\U0001d50a', '\U0001d50a', '\u22d9', '\u22d9', '\U0001d53e', '\U0001d53e', '\u2265', '\u2265', '\u22db', '\u22db', '\u2267', '\u2267', '\u2aa2', '\u2aa2', '\u2277', '\u2277', '\u2a7e', '\u2a7e', '\u2273', '\u2273', 
7360 '\U0001d4a2', '\U0001d4a2', '\u226b', '\u226b', '\u042a', '\u042a', '\u02c7', '\u02c7', '\u005e', '\u005e', '\u0124', '\u0124', '\u210c', '\u210c', '\u210b', '\u210b', '\u210d', '\u210d', '\u2500', '\u2500', '\u210b', '\u210b', '\u0126', '\u0126', '\u224e', '\u224e', '\u224f', '\u224f', '\u0415', '\u0415', '\u0132', '\u0132', 
7361 '\u0401', '\u0401', '\u00cd', '\u00cd', '\u00ce', '\u00ce', '\u0418', '\u0418', '\u0130', '\u0130', '\u2111', '\u2111', '\u00cc', '\u00cc', '\u2111', '\u2111', '\u012a', '\u012a', '\u2148', '\u2148', '\u21d2', '\u21d2', '\u222c', '\u222c', '\u222b', '\u222b', '\u22c2', '\u22c2', '\u2063', '\u2063', '\u2062', 
7362 '\u2062', '\u012e', '\u012e', '\U0001d540', '\U0001d540', '\u0399', '\u0399', '\u2110', '\u2110', '\u0128', '\u0128', '\u0406', '\u0406', '\u00cf', '\u00cf', '\u0134', '\u0134', '\u0419', '\u0419', '\U0001d50d', '\U0001d50d', '\U0001d541', '\U0001d541', '\U0001d4a5', '\U0001d4a5', '\u0408', '\u0408', '\u0404', '\u0404', '\u0425', '\u0425', '\u040c', '\u040c', '\u039a', '\u039a', '\u0136', '\u0136', 
7363 '\u041a', '\u041a', '\U0001d50e', '\U0001d50e', '\U0001d542', '\U0001d542', '\U0001d4a6', '\U0001d4a6', '\u0409', '\u0409', '\u003c', '\u003c', '\u0139', '\u0139', '\u039b', '\u039b', '\u27ea', '\u27ea', '\u2112', '\u2112', '\u219e', '\u219e', '\u013d', '\u013d', '\u013b', '\u013b', '\u041b', '\u041b', '\u27e8', '\u27e8', '\u2190', '\u2190', '\u21e4', 
7364 '\u21e4', '\u21c6', '\u21c6', '\u2308', '\u2308', '\u27e6', '\u27e6', '\u2961', '\u2961', '\u21c3', '\u21c3', '\u2959', '\u2959', '\u230a', '\u230a', '\u2194', '\u2194', '\u294e', 
7365 '\u294e', '\u22a3', '\u22a3', '\u21a4', '\u21a4', '\u295a', '\u295a', '\u22b2', '\u22b2', '\u29cf', '\u29cf', '\u22b4', '\u22b4', '\u2951', '\u2951', '\u2960', '\u2960', '\u21bf', '\u21bf', 
7366 '\u2958', '\u2958', '\u21bc', '\u21bc', '\u2952', '\u2952', '\u21d0', '\u21d0', '\u21d4', '\u21d4', '\u22da', '\u22da', '\u2266', '\u2266', '\u2276', '\u2276', '\u2aa1', '\u2aa1', '\u2a7d', '\u2a7d', 
7367 '\u2272', '\u2272', '\U0001d50f', '\U0001d50f', '\u22d8', '\u22d8', '\u21da', '\u21da', '\u013f', '\u013f', '\u27f5', '\u27f5', '\u27f7', '\u27f7', '\u27f6', '\u27f6', '\u27f8', '\u27f8', '\u27fa', '\u27fa', '\u27f9', '\u27f9', 
7368 '\U0001d543', '\U0001d543', '\u2199', '\u2199', '\u2198', '\u2198', '\u2112', '\u2112', '\u21b0', '\u21b0', '\u0141', '\u0141', '\u226a', '\u226a', '\u2905', '\u2905', '\u041c', '\u041c', '\u205f', '\u205f', '\u2133', '\u2133', '\U0001d510', '\U0001d510', '\u2213', '\u2213', '\U0001d544', '\U0001d544', '\u2133', '\u2133', '\u039c', '\u039c', 
7369 '\u040a', '\u040a', '\u0143', '\u0143', '\u0147', '\u0147', '\u0145', '\u0145', '\u041d', '\u041d', '\u200b', '\u200b', '\u200b', '\u200b', '\u200b', '\u200b', '\u200b', '\u200b', '\u226b', '\u226b', 
7370 '\u226a', '\u226a', '\u000a', '\u000a', '\U0001d511', '\U0001d511', '\u2060', '\u2060', '\u00a0', '\u00a0', '\u2115', '\u2115', '\u2aec', '\u2aec', '\u2262', '\u2262', '\u226d', '\u226d', '\u2226', '\u2226', '\u2209', '\u2209', '\u2260', '\u2260', 
7371 '\u2204', '\u2204', '\u226f', '\u226f', '\u2271', '\u2271', '\u2279', '\u2279', '\u2275', '\u2275', '\u22ea', '\u22ea', '\u22ec', '\u22ec', '\u226e', '\u226e', '\u2270', '\u2270', '\u2278', 
7372 '\u2278', '\u2274', '\u2274', '\u2280', '\u2280', '\u22e0', '\u22e0', '\u220c', '\u220c', '\u22eb', '\u22eb', '\u22ed', '\u22ed', '\u22e2', '\u22e2', '\u22e3', 
7373 '\u22e3', '\u2288', '\u2288', '\u2281', '\u2281', '\u22e1', '\u22e1', '\u2289', '\u2289', '\u2241', '\u2241', '\u2244', '\u2244', '\u2247', '\u2247', '\u2249', '\u2249', '\u2224', 
7374 '\u2224', '\U0001d4a9', '\U0001d4a9', '\u00d1', '\u00d1', '\u039d', '\u039d', '\u0152', '\u0152', '\u00d3', '\u00d3', '\u00d4', '\u00d4', '\u041e', '\u041e', '\u0150', '\u0150', '\U0001d512', '\U0001d512', '\u00d2', '\u00d2', '\u014c', '\u014c', '\u03a9', '\u03a9', '\u039f', '\u039f', '\U0001d546', '\U0001d546', '\u201c', '\u201c', '\u2018', 
7375 '\u2018', '\u2a54', '\u2a54', '\U0001d4aa', '\U0001d4aa', '\u00d8', '\u00d8', '\u00d5', '\u00d5', '\u2a37', '\u2a37', '\u00d6', '\u00d6', '\u203e', '\u203e', '\u23de', '\u23de', '\u23b4', '\u23b4', '\u23dc', '\u23dc', '\u2202', '\u2202', '\u041f', '\u041f', '\U0001d513', '\U0001d513', '\u03a6', '\u03a6', '\u03a0', '\u03a0', '\u00b1', 
7376 '\u00b1', '\u210c', '\u210c', '\u2119', '\u2119', '\u2abb', '\u2abb', '\u227a', '\u227a', '\u2aaf', '\u2aaf', '\u227c', '\u227c', '\u227e', '\u227e', '\u2033', '\u2033', '\u220f', '\u220f', '\u2237', '\u2237', '\u221d', '\u221d', '\U0001d4ab', '\U0001d4ab', 
7377 '\u03a8', '\u03a8', '\u0022', '\u0022', '\U0001d514', '\U0001d514', '\u211a', '\u211a', '\U0001d4ac', '\U0001d4ac', '\u2910', '\u2910', '\u00ae', '\u00ae', '\u0154', '\u0154', '\u27eb', '\u27eb', '\u21a0', '\u21a0', '\u2916', '\u2916', '\u0158', '\u0158', '\u0156', '\u0156', '\u0420', '\u0420', '\u211c', '\u211c', '\u220b', '\u220b', '\u21cb', '\u21cb', 
7378 '\u296f', '\u296f', '\u211c', '\u211c', '\u03a1', '\u03a1', '\u27e9', '\u27e9', '\u2192', '\u2192', '\u21e5', '\u21e5', '\u21c4', '\u21c4', '\u2309', '\u2309', '\u27e7', '\u27e7', '\u295d', 
7379 '\u295d', '\u21c2', '\u21c2', '\u2955', '\u2955', '\u230b', '\u230b', '\u22a2', '\u22a2', '\u21a6', '\u21a6', '\u295b', '\u295b', '\u22b3', '\u22b3', '\u29d0', '\u29d0', '\u22b5', 
7380 '\u22b5', '\u294f', '\u294f', '\u295c', '\u295c', '\u21be', '\u21be', '\u2954', '\u2954', '\u21c0', '\u21c0', '\u2953', '\u2953', '\u21d2', '\u21d2', '\u211d', '\u211d', '\u2970', '\u2970', 
7381 '\u21db', '\u21db', '\u211b', '\u211b', '\u21b1', '\u21b1', '\u29f4', '\u29f4', '\u0429', '\u0429', '\u0428', '\u0428', '\u042c', '\u042c', '\u015a', '\u015a', '\u2abc', '\u2abc', '\u0160', '\u0160', '\u015e', '\u015e', '\u015c', '\u015c', '\u0421', '\u0421', '\U0001d516', '\U0001d516', '\u2193', '\u2193', '\u2190', '\u2190', 
7382 '\u2192', '\u2192', '\u2191', '\u2191', '\u03a3', '\u03a3', '\u2218', '\u2218', '\U0001d54a', '\U0001d54a', '\u221a', '\u221a', '\u25a1', '\u25a1', '\u2293', '\u2293', '\u228f', '\u228f', '\u2291', '\u2291', '\u2290', '\u2290', 
7383 '\u2292', '\u2292', '\u2294', '\u2294', '\U0001d4ae', '\U0001d4ae', '\u22c6', '\u22c6', '\u22d0', '\u22d0', '\u22d0', '\u22d0', '\u2286', '\u2286', '\u227b', '\u227b', '\u2ab0', '\u2ab0', '\u227d', '\u227d', '\u227f', '\u227f', '\u220b', 
7384 '\u220b', '\u2211', '\u2211', '\u22d1', '\u22d1', '\u2283', '\u2283', '\u2287', '\u2287', '\u22d1', '\u22d1', '\u00de', '\u00de', '\u2122', '\u2122', '\u040b', '\u040b', '\u0426', '\u0426', '\u0009', '\u0009', '\u03a4', '\u03a4', '\u0164', '\u0164', '\u0162', '\u0162', '\u0422', '\u0422', '\U0001d517', '\U0001d517', '\u2234', '\u2234', '\u0398', '\u0398', 
7385 '\u2009', '\u2009', '\u223c', '\u223c', '\u2243', '\u2243', '\u2245', '\u2245', '\u2248', '\u2248', '\U0001d54b', '\U0001d54b', '\u20db', '\u20db', '\U0001d4af', '\U0001d4af', '\u0166', '\u0166', '\u00da', '\u00da', '\u219f', '\u219f', '\u2949', '\u2949', '\u040e', '\u040e', '\u016c', '\u016c', '\u00db', 
7386 '\u00db', '\u0423', '\u0423', '\u0170', '\u0170', '\U0001d518', '\U0001d518', '\u00d9', '\u00d9', '\u016a', '\u016a', '\u005f', '\u005f', '\u23df', '\u23df', '\u23b5', '\u23b5', '\u23dd', '\u23dd', '\u22c3', '\u22c3', '\u228e', '\u228e', '\u0172', '\u0172', '\U0001d54c', '\U0001d54c', '\u2191', '\u2191', '\u2912', 
7387 '\u2912', '\u21c5', '\u21c5', '\u2195', '\u2195', '\u296e', '\u296e', '\u22a5', '\u22a5', '\u21a5', '\u21a5', '\u21d1', '\u21d1', '\u21d5', '\u21d5', '\u2196', '\u2196', '\u2197', '\u2197', '\u03d2', '\u03d2', '\u03a5', '\u03a5', 
7388 '\u016e', '\u016e', '\U0001d4b0', '\U0001d4b0', '\u0168', '\u0168', '\u00dc', '\u00dc', '\u22ab', '\u22ab', '\u2aeb', '\u2aeb', '\u0412', '\u0412', '\u22a9', '\u22a9', '\u2ae6', '\u2ae6', '\u22c1', '\u22c1', '\u2016', '\u2016', '\u2016', '\u2016', '\u2223', '\u2223', '\u007c', '\u007c', '\u2758', '\u2758', '\u2240', 
7389 '\u2240', '\u200a', '\u200a', '\U0001d519', '\U0001d519', '\U0001d54d', '\U0001d54d', '\U0001d4b1', '\U0001d4b1', '\u22aa', '\u22aa', '\u0174', '\u0174', '\u22c0', '\u22c0', '\U0001d51a', '\U0001d51a', '\U0001d54e', '\U0001d54e', '\U0001d4b2', '\U0001d4b2', '\U0001d51b', '\U0001d51b', '\u039e', '\u039e', '\U0001d54f', '\U0001d54f', '\U0001d4b3', '\U0001d4b3', '\u042f', '\u042f', '\u0407', '\u0407', '\u042e', '\u042e', '\u00dd', '\u00dd', 
7390 '\u0176', '\u0176', '\u042b', '\u042b', '\U0001d51c', '\U0001d51c', '\U0001d550', '\U0001d550', '\U0001d4b4', '\U0001d4b4', '\u0178', '\u0178', '\u0416', '\u0416', '\u0179', '\u0179', '\u017d', '\u017d', '\u0417', '\u0417', '\u017b', '\u017b', '\u200b', '\u200b', '\u0396', '\u0396', '\u2128', '\u2128', '\u2124', '\u2124', '\U0001d4b5', '\U0001d4b5', '\u00e1', '\u00e1', '\u0103', '\u0103', '\u223e', 
7391 '\u223e', '\u223f', '\u223f', '\u00e2', '\u00e2', '\u00b4', '\u00b4', '\u0430', '\u0430', '\u00e6', '\u00e6', '\u2061', '\u2061', '\U0001d51e', '\U0001d51e', '\u00e0', '\u00e0', '\u2135', '\u2135', '\u2135', '\u2135', '\u03b1', '\u03b1', '\u0101', '\u0101', '\u2a3f', '\u2a3f', '\u2227', '\u2227', '\u2a55', '\u2a55', '\u2a5c', '\u2a5c', '\u2a58', '\u2a58', '\u2a5a', '\u2a5a', '\u2220', 
7392 '\u2220', '\u29a4', '\u29a4', '\u2220', '\u2220', '\u2221', '\u2221', '\u29a8', '\u29a8', '\u29a9', '\u29a9', '\u29aa', '\u29aa', '\u29ab', '\u29ab', '\u29ac', '\u29ac', '\u29ad', '\u29ad', '\u29ae', '\u29ae', '\u29af', '\u29af', '\u221f', '\u221f', '\u22be', '\u22be', '\u299d', '\u299d', '\u2222', 
7393 '\u2222', '\u00c5', '\u00c5', '\u237c', '\u237c', '\u0105', '\u0105', '\U0001d552', '\U0001d552', '\u2248', '\u2248', '\u2a70', '\u2a70', '\u2a6f', '\u2a6f', '\u224a', '\u224a', '\u224b', '\u224b', '\u2248', '\u2248', '\u224a', '\u224a', '\u00e5', '\u00e5', '\U0001d4b6', '\U0001d4b6', '\u002a', '\u002a', '\u2248', '\u2248', '\u224d', '\u224d', '\u00e3', '\u00e3', '\u00e4', 
7394 '\u00e4', '\u2233', '\u2233', '\u2a11', '\u2a11', '\u2aed', '\u2aed', '\u224c', '\u224c', '\u03f6', '\u03f6', '\u2035', '\u2035', '\u223d', '\u223d', '\u22cd', '\u22cd', '\u22bd', '\u22bd', '\u2305', '\u2305', '\u2305', '\u2305', '\u23b5', '\u23b5', '\u23b6', '\u23b6', '\u224c', '\u224c', '\u0431', 
7395 '\u0431', '\u201e', '\u201e', '\u2235', '\u2235', '\u2235', '\u2235', '\u29b0', '\u29b0', '\u03f6', '\u03f6', '\u212c', '\u212c', '\u03b2', '\u03b2', '\u2136', '\u2136', '\u226c', '\u226c', '\U0001d51f', '\U0001d51f', '\u22c2', '\u22c2', '\u25ef', '\u25ef', '\u22c3', '\u22c3', '\u2a00', '\u2a00', '\u2a01', '\u2a01', '\u2a02', '\u2a02', 
7396 '\u2a06', '\u2a06', '\u2605', '\u2605', '\u25bd', '\u25bd', '\u25b3', '\u25b3', '\u2a04', '\u2a04', '\u22c1', '\u22c1', '\u22c0', '\u22c0', '\u290d', '\u290d', '\u29eb', '\u29eb', '\u25aa', '\u25aa', '\u25b4', '\u25b4', '\u25be', 
7397 '\u25be', '\u25c2', '\u25c2', '\u25b8', '\u25b8', '\u2423', '\u2423', '\u2592', '\u2592', '\u2591', '\u2591', '\u2593', '\u2593', '\u2588', '\u2588', '\u2310', '\u2310', '\U0001d553', '\U0001d553', '\u22a5', '\u22a5', '\u22a5', '\u22a5', '\u22c8', '\u22c8', '\u2557', '\u2557', '\u2554', '\u2554', '\u2556', 
7398 '\u2556', '\u2553', '\u2553', '\u2550', '\u2550', '\u2566', '\u2566', '\u2569', '\u2569', '\u2564', '\u2564', '\u2567', '\u2567', '\u255d', '\u255d', '\u255a', '\u255a', '\u255c', '\u255c', '\u2559', '\u2559', '\u2551', '\u2551', '\u256c', '\u256c', '\u2563', '\u2563', '\u2560', '\u2560', '\u256b', '\u256b', '\u2562', '\u2562', '\u255f', '\u255f', '\u29c9', 
7399 '\u29c9', '\u2555', '\u2555', '\u2552', '\u2552', '\u2510', '\u2510', '\u250c', '\u250c', '\u2500', '\u2500', '\u2565', '\u2565', '\u2568', '\u2568', '\u252c', '\u252c', '\u2534', '\u2534', '\u229f', '\u229f', '\u229e', '\u229e', '\u22a0', '\u22a0', '\u255b', '\u255b', '\u2558', '\u2558', '\u2518', '\u2518', '\u2514', '\u2514', '\u2502', 
7400 '\u2502', '\u256a', '\u256a', '\u2561', '\u2561', '\u255e', '\u255e', '\u253c', '\u253c', '\u2524', '\u2524', '\u251c', '\u251c', '\u2035', '\u2035', '\u02d8', '\u02d8', '\u00a6', '\u00a6', '\U0001d4b7', '\U0001d4b7', '\u204f', '\u204f', '\u223d', '\u223d', '\u22cd', '\u22cd', '\u005c', '\u005c', '\u29c5', '\u29c5', '\u27c8', '\u27c8', '\u2022', '\u2022', '\u2022', 
7401 '\u2022', '\u224e', '\u224e', '\u2aae', '\u2aae', '\u224f', '\u224f', '\u224f', '\u224f', '\u0107', '\u0107', '\u2229', '\u2229', '\u2a44', '\u2a44', '\u2a49', '\u2a49', '\u2a4b', '\u2a4b', '\u2a47', '\u2a47', '\u2a40', '\u2a40', '\u2041', '\u2041', '\u02c7', '\u02c7', '\u2a4d', '\u2a4d', '\u010d', '\u010d', '\u00e7', '\u00e7', '\u0109', 
7402 '\u0109', '\u2a4c', '\u2a4c', '\u2a50', '\u2a50', '\u010b', '\u010b', '\u00b8', '\u00b8', '\u29b2', '\u29b2', '\u00a2', '\u00a2', '\u00b7', '\u00b7', '\U0001d520', '\U0001d520', '\u0447', '\u0447', '\u2713', '\u2713', '\u2713', '\u2713', '\u03c7', '\u03c7', '\u25cb', '\u25cb', '\u29c3', '\u29c3', '\u02c6', '\u02c6', '\u2257', '\u2257', '\u21ba', 
7403 '\u21ba', '\u21bb', '\u21bb', '\u00ae', '\u00ae', '\u24c8', '\u24c8', '\u229b', '\u229b', '\u229a', '\u229a', '\u229d', '\u229d', '\u2257', '\u2257', '\u2a10', '\u2a10', '\u2aef', '\u2aef', '\u29c2', '\u29c2', '\u2663', '\u2663', '\u2663', '\u2663', '\u003a', 
7404 '\u003a', '\u2254', '\u2254', '\u2254', '\u2254', '\u002c', '\u002c', '\u0040', '\u0040', '\u2201', '\u2201', '\u2218', '\u2218', '\u2201', '\u2201', '\u2102', '\u2102', '\u2245', '\u2245', '\u2a6d', '\u2a6d', '\u222e', '\u222e', '\U0001d554', '\U0001d554', '\u2210', '\u2210', '\u00a9', '\u00a9', '\u2117', '\u2117', '\u21b5', '\u21b5', 
7405 '\u2717', '\u2717', '\U0001d4b8', '\U0001d4b8', '\u2acf', '\u2acf', '\u2ad1', '\u2ad1', '\u2ad0', '\u2ad0', '\u2ad2', '\u2ad2', '\u22ef', '\u22ef', '\u2938', '\u2938', '\u2935', '\u2935', '\u22de', '\u22de', '\u22df', '\u22df', '\u21b6', '\u21b6', '\u293d', '\u293d', '\u222a', '\u222a', '\u2a48', '\u2a48', '\u2a46', '\u2a46', '\u2a4a', '\u2a4a', 
7406 '\u228d', '\u228d', '\u2a45', '\u2a45', '\u21b7', '\u21b7', '\u293c', '\u293c', '\u22de', '\u22de', '\u22df', '\u22df', '\u22ce', '\u22ce', '\u22cf', '\u22cf', '\u00a4', '\u00a4', '\u21b6', '\u21b6', '\u21b7', '\u21b7', '\u22ce', '\u22ce', '\u22cf', '\u22cf', 
7407 '\u2232', '\u2232', '\u2231', '\u2231', '\u232d', '\u232d', '\u21d3', '\u21d3', '\u2965', '\u2965', '\u2020', '\u2020', '\u2138', '\u2138', '\u2193', '\u2193', '\u2010', '\u2010', '\u22a3', '\u22a3', '\u290f', '\u290f', '\u02dd', '\u02dd', '\u010f', '\u010f', '\u0434', '\u0434', '\u2146', '\u2146', '\u2021', '\u2021', '\u21ca', '\u21ca', '\u2a77', 
7408 '\u2a77', '\u00b0', '\u00b0', '\u03b4', '\u03b4', '\u29b1', '\u29b1', '\u297f', '\u297f', '\U0001d521', '\U0001d521', '\u21c3', '\u21c3', '\u21c2', '\u21c2', '\u22c4', '\u22c4', '\u22c4', '\u22c4', '\u2666', '\u2666', '\u2666', '\u2666', '\u00a8', '\u00a8', '\u03dd', '\u03dd', '\u22f2', '\u22f2', '\u00f7', '\u00f7', '\u00f7', '\u00f7', '\u22c7', 
7409 '\u22c7', '\u22c7', '\u22c7', '\u0452', '\u0452', '\u231e', '\u231e', '\u230d', '\u230d', '\u0024', '\u0024', '\U0001d555', '\U0001d555', '\u02d9', '\u02d9', '\u2250', '\u2250', '\u2251', '\u2251', '\u2238', '\u2238', '\u2214', '\u2214', '\u22a1', '\u22a1', '\u2306', '\u2306', '\u2193', '\u2193', '\u21ca', 
7410 '\u21ca', '\u21c3', '\u21c3', '\u21c2', '\u21c2', '\u2910', '\u2910', '\u231f', '\u231f', '\u230c', '\u230c', '\U0001d4b9', '\U0001d4b9', '\u0455', '\u0455', '\u29f6', '\u29f6', '\u0111', '\u0111', '\u22f1', '\u22f1', '\u25bf', '\u25bf', '\u25be', '\u25be', '\u21f5', '\u21f5', '\u296f', '\u296f', '\u29a6', 
7411 '\u29a6', '\u045f', '\u045f', '\u27ff', '\u27ff', '\u2a77', '\u2a77', '\u2251', '\u2251', '\u00e9', '\u00e9', '\u2a6e', '\u2a6e', '\u011b', '\u011b', '\u2256', '\u2256', '\u00ea', '\u00ea', '\u2255', '\u2255', '\u044d', '\u044d', '\u0117', '\u0117', '\u2147', '\u2147', '\u2252', '\u2252', '\U0001d522', '\U0001d522', '\u2a9a', '\u2a9a', '\u00e8', '\u00e8', '\u2a96', '\u2a96', '\u2a98', 
7412 '\u2a98', '\u2a99', '\u2a99', '\u23e7', '\u23e7', '\u2113', '\u2113', '\u2a95', '\u2a95', '\u2a97', '\u2a97', '\u0113', '\u0113', '\u2205', '\u2205', '\u2205', '\u2205', '\u2205', '\u2205', '\u2003', '\u2003', '\u2004', '\u2004', '\u2005', '\u2005', '\u014b', '\u014b', '\u2002', '\u2002', '\u0119', '\u0119', '\U0001d556', '\U0001d556', '\u22d5', '\u22d5', '\u29e3', 
7413 '\u29e3', '\u2a71', '\u2a71', '\u03b5', '\u03b5', '\u03b5', '\u03b5', '\u03f5', '\u03f5', '\u2256', '\u2256', '\u2255', '\u2255', '\u2242', '\u2242', '\u2a96', '\u2a96', '\u2a95', '\u2a95', '\u003d', '\u003d', '\u225f', '\u225f', '\u2261', '\u2261', '\u2a78', '\u2a78', '\u29e5', '\u29e5', '\u2253', '\u2253', 
7414 '\u2971', '\u2971', '\u212f', '\u212f', '\u2250', '\u2250', '\u2242', '\u2242', '\u03b7', '\u03b7', '\u00f0', '\u00f0', '\u00eb', '\u00eb', '\u20ac', '\u20ac', '\u0021', '\u0021', '\u2203', '\u2203', '\u2130', '\u2130', '\u2147', '\u2147', '\u2252', '\u2252', '\u0444', '\u0444', '\u2640', '\u2640', '\ufb03', '\ufb03', '\ufb00', 
7415 '\ufb00', '\ufb04', '\ufb04', '\U0001d523', '\U0001d523', '\ufb01', '\ufb01', '\u266d', '\u266d', '\ufb02', '\ufb02', '\u25b1', '\u25b1', '\u0192', '\u0192', '\U0001d557', '\U0001d557', '\u2200', '\u2200', '\u22d4', '\u22d4', '\u2ad9', '\u2ad9', '\u2a0d', '\u2a0d', '\u00bd', '\u00bd', '\u2153', '\u2153', '\u00bc', '\u00bc', '\u2155', '\u2155', '\u2159', '\u2159', 
7416 '\u215b', '\u215b', '\u2154', '\u2154', '\u2156', '\u2156', '\u00be', '\u00be', '\u2157', '\u2157', '\u215c', '\u215c', '\u2158', '\u2158', '\u215a', '\u215a', '\u215d', '\u215d', '\u215e', '\u215e', '\u2044', '\u2044', '\u2322', '\u2322', '\U0001d4bb', '\U0001d4bb', '\u2267', '\u2267', '\u2a8c', '\u2a8c', '\u01f5', '\u01f5', '\u03b3', '\u03b3', '\u03dd', 
7417 '\u03dd', '\u2a86', '\u2a86', '\u011f', '\u011f', '\u011d', '\u011d', '\u0433', '\u0433', '\u0121', '\u0121', '\u2265', '\u2265', '\u22db', '\u22db', '\u2265', '\u2265', '\u2267', '\u2267', '\u2a7e', '\u2a7e', '\u2a7e', '\u2a7e', '\u2aa9', '\u2aa9', '\u2a80', '\u2a80', '\u2a82', '\u2a82', '\u2a84', '\u2a84', '\u2a94', '\u2a94', '\U0001d524', '\U0001d524', '\u226b', '\u226b', '\u22d9', 
7418 '\u22d9', '\u2137', '\u2137', '\u0453', '\u0453', '\u2277', '\u2277', '\u2a92', '\u2a92', '\u2aa5', '\u2aa5', '\u2aa4', '\u2aa4', '\u2269', '\u2269', '\u2a8a', '\u2a8a', '\u2a8a', '\u2a8a', '\u2a88', '\u2a88', '\u2a88', '\u2a88', '\u2269', '\u2269', '\u22e7', '\u22e7', '\U0001d558', '\U0001d558', '\u0060', '\u0060', '\u210a', '\u210a', '\u2273', '\u2273', '\u2a8e', '\u2a8e', '\u2a90', '\u2a90', '\u2aa7', 
7419 '\u2aa7', '\u2a7a', '\u2a7a', '\u22d7', '\u22d7', '\u2995', '\u2995', '\u2a7c', '\u2a7c', '\u2a86', '\u2a86', '\u2978', '\u2978', '\u22d7', '\u22d7', '\u22db', '\u22db', '\u2a8c', '\u2a8c', '\u2277', '\u2277', '\u2273', '\u2273', '\u21d4', '\u21d4', '\u200a', '\u200a', '\u00bd', '\u00bd', '\u210b', '\u210b', 
7420 '\u044a', '\u044a', '\u2194', '\u2194', '\u2948', '\u2948', '\u21ad', '\u21ad', '\u210f', '\u210f', '\u0125', '\u0125', '\u2665', '\u2665', '\u2665', '\u2665', '\u2026', '\u2026', '\u22b9', '\u22b9', '\U0001d525', '\U0001d525', '\u2925', '\u2925', '\u2926', '\u2926', '\u21ff', '\u21ff', '\u223b', '\u223b', '\u21a9', '\u21a9', 
7421 '\u21aa', '\u21aa', '\U0001d559', '\U0001d559', '\u2015', '\u2015', '\U0001d4bd', '\U0001d4bd', '\u210f', '\u210f', '\u0127', '\u0127', '\u2043', '\u2043', '\u2010', '\u2010', '\u00ed', '\u00ed', '\u2063', '\u2063', '\u00ee', '\u00ee', '\u0438', '\u0438', '\u0435', '\u0435', '\u00a1', '\u00a1', '\u21d4', '\u21d4', '\U0001d526', '\U0001d526', '\u00ec', '\u00ec', '\u2148', 
7422 '\u2148', '\u2a0c', '\u2a0c', '\u222d', '\u222d', '\u29dc', '\u29dc', '\u2129', '\u2129', '\u0133', '\u0133', '\u012b', '\u012b', '\u2111', '\u2111', '\u2110', '\u2110', '\u2111', '\u2111', '\u0131', '\u0131', '\u22b7', '\u22b7', '\u01b5', '\u01b5', '\u2208', '\u2208', '\u2105', '\u2105', '\u221e', '\u221e', '\u29dd', '\u29dd', '\u0131', 
7423 '\u0131', '\u222b', '\u222b', '\u22ba', '\u22ba', '\u2124', '\u2124', '\u22ba', '\u22ba', '\u2a17', '\u2a17', '\u2a3c', '\u2a3c', '\u0451', '\u0451', '\u012f', '\u012f', '\U0001d55a', '\U0001d55a', '\u03b9', '\u03b9', '\u2a3c', '\u2a3c', '\u00bf', '\u00bf', '\U0001d4be', '\U0001d4be', '\u2208', '\u2208', '\u22f9', '\u22f9', '\u22f5', '\u22f5', '\u22f4', 
7424 '\u22f4', '\u22f3', '\u22f3', '\u2208', '\u2208', '\u2062', '\u2062', '\u0129', '\u0129', '\u0456', '\u0456', '\u00ef', '\u00ef', '\u0135', '\u0135', '\u0439', '\u0439', '\U0001d527', '\U0001d527', '\u0237', '\u0237', '\U0001d55b', '\U0001d55b', '\U0001d4bf', '\U0001d4bf', '\u0458', '\u0458', '\u0454', '\u0454', '\u03ba', '\u03ba', '\u03f0', '\u03f0', '\u0137', '\u0137', '\u043a', '\u043a', '\U0001d528', 
7425 '\U0001d528', '\u0138', '\u0138', '\u0445', '\u0445', '\u045c', '\u045c', '\U0001d55c', '\U0001d55c', '\U0001d4c0', '\U0001d4c0', '\u21da', '\u21da', '\u21d0', '\u21d0', '\u291b', '\u291b', '\u290e', '\u290e', '\u2266', '\u2266', '\u2a8b', '\u2a8b', '\u2962', '\u2962', '\u013a', '\u013a', '\u29b4', '\u29b4', '\u2112', '\u2112', '\u03bb', '\u03bb', '\u27e8', '\u27e8', '\u2991', '\u2991', 
7426 '\u27e8', '\u27e8', '\u2a85', '\u2a85', '\u00ab', '\u00ab', '\u2190', '\u2190', '\u21e4', '\u21e4', '\u291f', '\u291f', '\u291d', '\u291d', '\u21a9', '\u21a9', '\u21ab', '\u21ab', '\u2939', '\u2939', '\u2973', '\u2973', '\u21a2', '\u21a2', '\u2aab', '\u2aab', '\u2919', '\u2919', '\u2aad', '\u2aad', '\u290c', '\u290c', '\u2772', '\u2772', '\u007b', 
7427 '\u007b', '\u005b', '\u005b', '\u298b', '\u298b', '\u298f', '\u298f', '\u298d', '\u298d', '\u013e', '\u013e', '\u013c', '\u013c', '\u2308', '\u2308', '\u007b', '\u007b', '\u043b', '\u043b', '\u2936', '\u2936', '\u201c', '\u201c', '\u201e', '\u201e', '\u2967', '\u2967', '\u294b', '\u294b', '\u21b2', '\u21b2', '\u2264', '\u2264', '\u2190', 
7428 '\u2190', '\u21a2', '\u21a2', '\u21bd', '\u21bd', '\u21bc', '\u21bc', '\u21c7', '\u21c7', '\u2194', '\u2194', '\u21c6', '\u21c6', '\u21cb', '\u21cb', '\u21ad', '\u21ad', '\u22cb', 
7429 '\u22cb', '\u22da', '\u22da', '\u2264', '\u2264', '\u2266', '\u2266', '\u2a7d', '\u2a7d', '\u2a7d', '\u2a7d', '\u2aa8', '\u2aa8', '\u2a7f', '\u2a7f', '\u2a81', '\u2a81', '\u2a83', '\u2a83', '\u2a93', '\u2a93', '\u2a85', '\u2a85', '\u22d6', '\u22d6', '\u22da', '\u22da', '\u2a8b', '\u2a8b', '\u2276', '\u2276', 
7430 '\u2272', '\u2272', '\u297c', '\u297c', '\u230a', '\u230a', '\U0001d529', '\U0001d529', '\u2276', '\u2276', '\u2a91', '\u2a91', '\u21bd', '\u21bd', '\u21bc', '\u21bc', '\u296a', '\u296a', '\u2584', '\u2584', '\u0459', '\u0459', '\u226a', '\u226a', '\u21c7', '\u21c7', '\u231e', '\u231e', '\u296b', '\u296b', '\u25fa', '\u25fa', '\u0140', '\u0140', '\u23b0', '\u23b0', 
7431 '\u23b0', '\u23b0', '\u2268', '\u2268', '\u2a89', '\u2a89', '\u2a89', '\u2a89', '\u2a87', '\u2a87', '\u2a87', '\u2a87', '\u2268', '\u2268', '\u22e6', '\u22e6', '\u27ec', '\u27ec', '\u21fd', '\u21fd', '\u27e6', '\u27e6', '\u27f5', '\u27f5', '\u27f7', '\u27f7', '\u27fc', '\u27fc', '\u27f6', 
7432 '\u27f6', '\u21ab', '\u21ab', '\u21ac', '\u21ac', '\u2985', '\u2985', '\U0001d55d', '\U0001d55d', '\u2a2d', '\u2a2d', '\u2a34', '\u2a34', '\u2217', '\u2217', '\u005f', '\u005f', '\u25ca', '\u25ca', '\u25ca', '\u25ca', '\u29eb', '\u29eb', '\u0028', '\u0028', '\u2993', '\u2993', '\u21c6', '\u21c6', '\u231f', 
7433 '\u231f', '\u21cb', '\u21cb', '\u296d', '\u296d', '\u200e', '\u200e', '\u22bf', '\u22bf', '\u2039', '\u2039', '\U0001d4c1', '\U0001d4c1', '\u21b0', '\u21b0', '\u2272', '\u2272', '\u2a8d', '\u2a8d', '\u2a8f', '\u2a8f', '\u005b', '\u005b', '\u2018', '\u2018', '\u201a', '\u201a', '\u0142', '\u0142', '\u2aa6', '\u2aa6', '\u2a79', '\u2a79', '\u22d6', '\u22d6', '\u22cb', 
7434 '\u22cb', '\u22c9', '\u22c9', '\u2976', '\u2976', '\u2a7b', '\u2a7b', '\u2996', '\u2996', '\u25c3', '\u25c3', '\u22b4', '\u22b4', '\u25c2', '\u25c2', '\u294a', '\u294a', '\u2966', '\u2966', '\u223a', '\u223a', '\u00af', '\u00af', '\u2642', '\u2642', '\u2720', '\u2720', '\u2720', '\u2720', '\u21a6', '\u21a6', '\u21a6', '\u21a6', '\u21a7', 
7435 '\u21a7', '\u21a4', '\u21a4', '\u21a5', '\u21a5', '\u25ae', '\u25ae', '\u2a29', '\u2a29', '\u043c', '\u043c', '\u2014', '\u2014', '\u2221', '\u2221', '\U0001d52a', '\U0001d52a', '\u2127', '\u2127', '\u00b5', '\u00b5', '\u2223', '\u2223', '\u002a', '\u002a', '\u2af0', '\u2af0', '\u00b7', '\u00b7', '\u2212', '\u2212', '\u229f', 
7436 '\u229f', '\u2238', '\u2238', '\u2a2a', '\u2a2a', '\u2adb', '\u2adb', '\u2026', '\u2026', '\u2213', '\u2213', '\u22a7', '\u22a7', '\U0001d55e', '\U0001d55e', '\u2213', '\u2213', '\U0001d4c2', '\U0001d4c2', '\u223e', '\u223e', '\u03bc', '\u03bc', '\u22b8', '\u22b8', '\u22b8', '\u22b8', '\u21cd', '\u21cd', '\u21ce', '\u21ce', '\u21cf', 
7437 '\u21cf', '\u22af', '\u22af', '\u22ae', '\u22ae', '\u2207', '\u2207', '\u0144', '\u0144', '\u2249', '\u2249', '\u0149', '\u0149', '\u2249', '\u2249', '\u266e', '\u266e', '\u266e', '\u266e', '\u2115', '\u2115', '\u00a0', '\u00a0', '\u2a43', '\u2a43', '\u0148', '\u0148', '\u0146', '\u0146', '\u2247', '\u2247', '\u2a42', '\u2a42', '\u043d', 
7438 '\u043d', '\u2013', '\u2013', '\u2260', '\u2260', '\u21d7', '\u21d7', '\u2924', '\u2924', '\u2197', '\u2197', '\u2197', '\u2197', '\u2262', '\u2262', '\u2928', '\u2928', '\u2204', '\u2204', '\u2204', '\u2204', '\U0001d52b', '\U0001d52b', '\u2271', '\u2271', '\u2271', '\u2271', '\u2275', '\u2275', '\u226f', '\u226f', '\u226f', '\u226f', '\u21ce', '\u21ce', '\u21ae', '\u21ae', 
7439 '\u2af2', '\u2af2', '\u220b', '\u220b', '\u22fc', '\u22fc', '\u22fa', '\u22fa', '\u220b', '\u220b', '\u045a', '\u045a', '\u21cd', '\u21cd', '\u219a', '\u219a', '\u2025', '\u2025', '\u2270', '\u2270', '\u219a', '\u219a', '\u21ae', '\u21ae', '\u2270', '\u2270', '\u226e', '\u226e', '\u2274', '\u2274', '\u226e', '\u226e', '\u22ea', '\u22ea', '\u22ec', '\u22ec', 
7440 '\u2224', '\u2224', '\U0001d55f', '\U0001d55f', '\u00ac', '\u00ac', '\u2209', '\u2209', '\u2209', '\u2209', '\u22f7', '\u22f7', '\u22f6', '\u22f6', '\u220c', '\u220c', '\u220c', '\u220c', '\u22fe', '\u22fe', '\u22fd', '\u22fd', '\u2226', '\u2226', '\u2226', '\u2226', '\u2a14', '\u2a14', '\u2280', '\u2280', '\u22e0', '\u22e0', '\u2280', 
7441 '\u2280', '\u21cf', '\u21cf', '\u219b', '\u219b', '\u219b', '\u219b', '\u22eb', '\u22eb', '\u22ed', '\u22ed', '\u2281', '\u2281', '\u22e1', '\u22e1', '\U0001d4c3', '\U0001d4c3', '\u2224', '\u2224', '\u2226', '\u2226', '\u2241', '\u2241', '\u2244', '\u2244', '\u2244', '\u2244', '\u2224', '\u2224', '\u2226', '\u2226', '\u22e2', 
7442 '\u22e2', '\u22e3', '\u22e3', '\u2284', '\u2284', '\u2288', '\u2288', '\u2288', '\u2288', '\u2281', '\u2281', '\u2285', '\u2285', '\u2289', '\u2289', '\u2289', '\u2289', '\u2279', '\u2279', '\u00f1', '\u00f1', '\u2278', '\u2278', '\u22ea', '\u22ea', '\u22ec', '\u22ec', '\u22eb', '\u22eb', 
7443 '\u22ed', '\u22ed', '\u03bd', '\u03bd', '\u0023', '\u0023', '\u2116', '\u2116', '\u2007', '\u2007', '\u22ad', '\u22ad', '\u2904', '\u2904', '\u22ac', '\u22ac', '\u29de', '\u29de', '\u2902', '\u2902', '\u2903', '\u2903', '\u21d6', '\u21d6', '\u2923', '\u2923', '\u2196', '\u2196', '\u2196', '\u2196', '\u2927', '\u2927', 
7444 '\u24c8', '\u24c8', '\u00f3', '\u00f3', '\u229b', '\u229b', '\u229a', '\u229a', '\u00f4', '\u00f4', '\u043e', '\u043e', '\u229d', '\u229d', '\u0151', '\u0151', '\u2a38', '\u2a38', '\u2299', '\u2299', '\u29bc', '\u29bc', '\u0153', '\u0153', '\u29bf', '\u29bf', '\U0001d52c', '\U0001d52c', '\u02db', '\u02db', '\u00f2', '\u00f2', '\u29c1', '\u29c1', '\u29b5', '\u29b5', '\u03a9', '\u03a9', '\u222e', 
7445 '\u222e', '\u21ba', '\u21ba', '\u29be', '\u29be', '\u29bb', '\u29bb', '\u203e', '\u203e', '\u29c0', '\u29c0', '\u014d', '\u014d', '\u03c9', '\u03c9', '\u03bf', '\u03bf', '\u29b6', '\u29b6', '\u2296', '\u2296', '\U0001d560', '\U0001d560', '\u29b7', '\u29b7', '\u29b9', '\u29b9', '\u2295', '\u2295', '\u2228', '\u2228', '\u21bb', '\u21bb', '\u2a5d', '\u2a5d', '\u2134', '\u2134', 
7446 '\u2134', '\u2134', '\u00aa', '\u00aa', '\u00ba', '\u00ba', '\u22b6', '\u22b6', '\u2a56', '\u2a56', '\u2a57', '\u2a57', '\u2a5b', '\u2a5b', '\u2134', '\u2134', '\u00f8', '\u00f8', '\u2298', '\u2298', '\u00f5', '\u00f5', '\u2297', '\u2297', '\u2a36', '\u2a36', '\u00f6', '\u00f6', '\u233d', '\u233d', '\u2225', '\u2225', '\u00b6', '\u00b6', '\u2225', '\u2225', 
7447 '\u2af3', '\u2af3', '\u2afd', '\u2afd', '\u2202', '\u2202', '\u043f', '\u043f', '\u0025', '\u0025', '\u002e', '\u002e', '\u2030', '\u2030', '\u22a5', '\u22a5', '\u2031', '\u2031', '\U0001d52d', '\U0001d52d', '\u03c6', '\u03c6', '\u03d5', '\u03d5', '\u2133', '\u2133', '\u260e', '\u260e', '\u03c0', '\u03c0', '\u22d4', '\u22d4', '\u03d6', '\u03d6', '\u210f', '\u210f', 
7448 '\u210e', '\u210e', '\u210f', '\u210f', '\u002b', '\u002b', '\u2a23', '\u2a23', '\u229e', '\u229e', '\u2a22', '\u2a22', '\u2214', '\u2214', '\u2a25', '\u2a25', '\u2a72', '\u2a72', '\u00b1', '\u00b1', '\u2a26', '\u2a26', '\u2a27', '\u2a27', '\u00b1', '\u00b1', '\u2a15', '\u2a15', '\U0001d561', '\U0001d561', '\u00a3', '\u00a3', '\u227a', 
7449 '\u227a', '\u2ab3', '\u2ab3', '\u2ab7', '\u2ab7', '\u227c', '\u227c', '\u2aaf', '\u2aaf', '\u227a', '\u227a', '\u2ab7', '\u2ab7', '\u227c', '\u227c', '\u2aaf', '\u2aaf', '\u2ab9', '\u2ab9', '\u2ab5', '\u2ab5', '\u22e8', '\u22e8', '\u227e', '\u227e', '\u2032', '\u2032', '\u2119', '\u2119', '\u2ab5', '\u2ab5', '\u2ab9', 
7450 '\u2ab9', '\u22e8', '\u22e8', '\u220f', '\u220f', '\u232e', '\u232e', '\u2312', '\u2312', '\u2313', '\u2313', '\u221d', '\u221d', '\u221d', '\u221d', '\u227e', '\u227e', '\u22b0', '\u22b0', '\U0001d4c5', '\U0001d4c5', '\u03c8', '\u03c8', '\u2008', '\u2008', '\U0001d52e', '\U0001d52e', '\u2a0c', '\u2a0c', '\U0001d562', '\U0001d562', '\u2057', '\u2057', '\U0001d4c6', '\U0001d4c6', 
7451 '\u210d', '\u210d', '\u2a16', '\u2a16', '\u003f', '\u003f', '\u225f', '\u225f', '\u21db', '\u21db', '\u21d2', '\u21d2', '\u291c', '\u291c', '\u290f', '\u290f', '\u2964', '\u2964', '\u0155', '\u0155', '\u221a', '\u221a', '\u29b3', '\u29b3', '\u27e9', '\u27e9', '\u2992', '\u2992', '\u29a5', '\u29a5', '\u27e9', '\u27e9', '\u00bb', 
7452 '\u00bb', '\u2192', '\u2192', '\u2975', '\u2975', '\u21e5', '\u21e5', '\u2920', '\u2920', '\u2933', '\u2933', '\u291e', '\u291e', '\u21aa', '\u21aa', '\u21ac', '\u21ac', '\u2945', '\u2945', '\u2974', '\u2974', '\u21a3', '\u21a3', '\u219d', '\u219d', '\u291a', '\u291a', '\u2236', '\u2236', '\u211a', '\u211a', '\u290d', '\u290d', 
7453 '\u2773', '\u2773', '\u007d', '\u007d', '\u005d', '\u005d', '\u298c', '\u298c', '\u298e', '\u298e', '\u2990', '\u2990', '\u0159', '\u0159', '\u0157', '\u0157', '\u2309', '\u2309', '\u007d', '\u007d', '\u0440', '\u0440', '\u2937', '\u2937', '\u2969', '\u2969', '\u201d', '\u201d', '\u201d', '\u201d', '\u21b3', '\u21b3', '\u211c', '\u211c', '\u211b', 
7454 '\u211b', '\u211c', '\u211c', '\u211d', '\u211d', '\u25ad', '\u25ad', '\u00ae', '\u00ae', '\u297d', '\u297d', '\u230b', '\u230b', '\U0001d52f', '\U0001d52f', '\u21c1', '\u21c1', '\u21c0', '\u21c0', '\u296c', '\u296c', '\u03c1', '\u03c1', '\u03f1', '\u03f1', '\u2192', '\u2192', '\u21a3', '\u21a3', '\u21c1', '\u21c1', 
7455 '\u21c0', '\u21c0', '\u21c4', '\u21c4', '\u21cc', '\u21cc', '\u21c9', '\u21c9', '\u219d', '\u219d', '\u22cc', '\u22cc', '\u02da', '\u02da', '\u2253', '\u2253', '\u21c4', '\u21c4', '\u21cc', '\u21cc', '\u200f', 
7456 '\u200f', '\u23b1', '\u23b1', '\u23b1', '\u23b1', '\u2aee', '\u2aee', '\u27ed', '\u27ed', '\u21fe', '\u21fe', '\u27e7', '\u27e7', '\u2986', '\u2986', '\U0001d563', '\U0001d563', '\u2a2e', '\u2a2e', '\u2a35', '\u2a35', '\u0029', '\u0029', '\u2994', '\u2994', '\u2a12', '\u2a12', '\u21c9', '\u21c9', '\u203a', '\u203a', '\U0001d4c7', '\U0001d4c7', '\u21b1', 
7457 '\u21b1', '\u005d', '\u005d', '\u2019', '\u2019', '\u2019', '\u2019', '\u22cc', '\u22cc', '\u22ca', '\u22ca', '\u25b9', '\u25b9', '\u22b5', '\u22b5', '\u25b8', '\u25b8', '\u29ce', '\u29ce', '\u2968', '\u2968', '\u211e', '\u211e', '\u015b', '\u015b', '\u201a', '\u201a', '\u227b', '\u227b', '\u2ab4', '\u2ab4', '\u2ab8', '\u2ab8', '\u0161', '\u0161', '\u227d', 
7458 '\u227d', '\u2ab0', '\u2ab0', '\u015f', '\u015f', '\u015d', '\u015d', '\u2ab6', '\u2ab6', '\u2aba', '\u2aba', '\u22e9', '\u22e9', '\u2a13', '\u2a13', '\u227f', '\u227f', '\u0441', '\u0441', '\u22c5', '\u22c5', '\u22a1', '\u22a1', '\u2a66', '\u2a66', '\u21d8', '\u21d8', '\u2925', '\u2925', '\u2198', '\u2198', '\u2198', '\u2198', '\u00a7', '\u00a7', '\u003b', 
7459 '\u003b', '\u2929', '\u2929', '\u2216', '\u2216', '\u2216', '\u2216', '\u2736', '\u2736', '\U0001d530', '\U0001d530', '\u2322', '\u2322', '\u266f', '\u266f', '\u0449', '\u0449', '\u0448', '\u0448', '\u2223', '\u2223', '\u2225', '\u2225', '\u00ad', '\u00ad', '\u03c3', '\u03c3', '\u03c2', '\u03c2', '\u03c2', '\u03c2', '\u223c', '\u223c', '\u2a6a', 
7460 '\u2a6a', '\u2243', '\u2243', '\u2243', '\u2243', '\u2a9e', '\u2a9e', '\u2aa0', '\u2aa0', '\u2a9d', '\u2a9d', '\u2a9f', '\u2a9f', '\u2246', '\u2246', '\u2a24', '\u2a24', '\u2972', '\u2972', '\u2190', '\u2190', '\u2216', '\u2216', '\u2a33', '\u2a33', '\u29e4', '\u29e4', '\u2223', '\u2223', '\u2323', '\u2323', '\u2aaa', '\u2aaa', '\u2aac', 
7461 '\u2aac', '\u044c', '\u044c', '\u002f', '\u002f', '\u29c4', '\u29c4', '\u233f', '\u233f', '\U0001d564', '\U0001d564', '\u2660', '\u2660', '\u2660', '\u2660', '\u2225', '\u2225', '\u2293', '\u2293', '\u2294', '\u2294', '\u228f', '\u228f', '\u2291', '\u2291', '\u228f', '\u228f', '\u2291', '\u2291', '\u2290', '\u2290', '\u2292', '\u2292', 
7462 '\u2290', '\u2290', '\u2292', '\u2292', '\u25a1', '\u25a1', '\u25a1', '\u25a1', '\u25aa', '\u25aa', '\u25aa', '\u25aa', '\u2192', '\u2192', '\U0001d4c8', '\U0001d4c8', '\u2216', '\u2216', '\u2323', '\u2323', '\u22c6', '\u22c6', '\u2606', '\u2606', '\u2605', '\u2605', '\u03f5', '\u03f5', '\u03d5', '\u03d5', '\u00af', 
7463 '\u00af', '\u2282', '\u2282', '\u2ac5', '\u2ac5', '\u2abd', '\u2abd', '\u2286', '\u2286', '\u2ac3', '\u2ac3', '\u2ac1', '\u2ac1', '\u2acb', '\u2acb', '\u228a', '\u228a', '\u2abf', '\u2abf', '\u2979', '\u2979', '\u2282', '\u2282', '\u2286', '\u2286', '\u2ac5', '\u2ac5', '\u228a', '\u228a', '\u2acb', '\u2acb', 
7464 '\u2ac7', '\u2ac7', '\u2ad5', '\u2ad5', '\u2ad3', '\u2ad3', '\u227b', '\u227b', '\u2ab8', '\u2ab8', '\u227d', '\u227d', '\u2ab0', '\u2ab0', '\u2aba', '\u2aba', '\u2ab6', '\u2ab6', '\u22e9', '\u22e9', '\u227f', '\u227f', '\u2211', '\u2211', '\u266a', '\u266a', '\u2283', '\u2283', '\u00b9', '\u00b9', '\u00b2', 
7465 '\u00b2', '\u00b3', '\u00b3', '\u2ac6', '\u2ac6', '\u2abe', '\u2abe', '\u2ad8', '\u2ad8', '\u2287', '\u2287', '\u2ac4', '\u2ac4', '\u27c9', '\u27c9', '\u2ad7', '\u2ad7', '\u297b', '\u297b', '\u2ac2', '\u2ac2', '\u2acc', '\u2acc', '\u228b', '\u228b', '\u2ac0', '\u2ac0', '\u2283', '\u2283', '\u2287', '\u2287', '\u2ac6', 
7466 '\u2ac6', '\u228b', '\u228b', '\u2acc', '\u2acc', '\u2ac8', '\u2ac8', '\u2ad4', '\u2ad4', '\u2ad6', '\u2ad6', '\u21d9', '\u21d9', '\u2926', '\u2926', '\u2199', '\u2199', '\u2199', '\u2199', '\u292a', '\u292a', '\u00df', '\u00df', '\u2316', '\u2316', '\u03c4', '\u03c4', '\u23b4', '\u23b4', '\u0165', '\u0165', '\u0163', 
7467 '\u0163', '\u0442', '\u0442', '\u20db', '\u20db', '\u2315', '\u2315', '\U0001d531', '\U0001d531', '\u2234', '\u2234', '\u2234', '\u2234', '\u03b8', '\u03b8', '\u03d1', '\u03d1', '\u03d1', '\u03d1', '\u2248', '\u2248', '\u223c', '\u223c', '\u2009', '\u2009', '\u2248', '\u2248', '\u223c', '\u223c', '\u00fe', '\u00fe', '\u02dc', 
7468 '\u02dc', '\u00d7', '\u00d7', '\u22a0', '\u22a0', '\u2a31', '\u2a31', '\u2a30', '\u2a30', '\u222d', '\u222d', '\u2928', '\u2928', '\u22a4', '\u22a4', '\u2336', '\u2336', '\u2af1', '\u2af1', '\U0001d565', '\U0001d565', '\u2ada', '\u2ada', '\u2929', '\u2929', '\u2034', '\u2034', '\u2122', '\u2122', '\u25b5', '\u25b5', '\u25bf', '\u25bf', 
7469 '\u25c3', '\u25c3', '\u22b4', '\u22b4', '\u225c', '\u225c', '\u25b9', '\u25b9', '\u22b5', '\u22b5', '\u25ec', '\u25ec', '\u225c', '\u225c', '\u2a3a', '\u2a3a', '\u2a39', '\u2a39', '\u29cd', '\u29cd', '\u2a3b', '\u2a3b', '\u23e2', '\u23e2', '\U0001d4c9', 
7470 '\U0001d4c9', '\u0446', '\u0446', '\u045b', '\u045b', '\u0167', '\u0167', '\u226c', '\u226c', '\u219e', '\u219e', '\u21a0', '\u21a0', '\u21d1', '\u21d1', '\u2963', '\u2963', '\u00fa', '\u00fa', '\u2191', '\u2191', '\u045e', '\u045e', '\u016d', '\u016d', '\u00fb', '\u00fb', '\u0443', '\u0443', '\u21c5', '\u21c5', '\u0171', 
7471 '\u0171', '\u296e', '\u296e', '\u297e', '\u297e', '\U0001d532', '\U0001d532', '\u00f9', '\u00f9', '\u21bf', '\u21bf', '\u21be', '\u21be', '\u2580', '\u2580', '\u231c', '\u231c', '\u231c', '\u231c', '\u230f', '\u230f', '\u25f8', '\u25f8', '\u016b', '\u016b', '\u00a8', '\u00a8', '\u0173', '\u0173', '\U0001d566', '\U0001d566', '\u2191', '\u2191', '\u2195', 
7472 '\u2195', '\u21bf', '\u21bf', '\u21be', '\u21be', '\u228e', '\u228e', '\u03c5', '\u03c5', '\u03d2', '\u03d2', '\u03c5', '\u03c5', '\u21c8', '\u21c8', '\u231d', '\u231d', '\u231d', '\u231d', '\u230e', '\u230e', '\u016f', '\u016f', '\u25f9', '\u25f9', '\U0001d4ca', '\U0001d4ca', '\u22f0', '\u22f0', 
7473 '\u0169', '\u0169', '\u25b5', '\u25b5', '\u25b4', '\u25b4', '\u21c8', '\u21c8', '\u00fc', '\u00fc', '\u29a7', '\u29a7', '\u21d5', '\u21d5', '\u2ae8', '\u2ae8', '\u2ae9', '\u2ae9', '\u22a8', '\u22a8', '\u299c', '\u299c', '\u03f5', '\u03f5', '\u03f0', '\u03f0', '\u2205', '\u2205', '\u03d5', '\u03d5', '\u03d6', '\u03d6', '\u221d', 
7474 '\u221d', '\u2195', '\u2195', '\u03f1', '\u03f1', '\u03c2', '\u03c2', '\u03d1', '\u03d1', '\u22b2', '\u22b2', '\u22b3', '\u22b3', '\u0432', '\u0432', '\u22a2', '\u22a2', '\u2228', '\u2228', '\u22bb', '\u22bb', '\u225a', '\u225a', '\u22ee', '\u22ee', '\u007c', '\u007c', '\u007c', '\u007c', '\U0001d533', 
7475 '\U0001d533', '\u22b2', '\u22b2', '\U0001d567', '\U0001d567', '\u221d', '\u221d', '\u22b3', '\u22b3', '\U0001d4cb', '\U0001d4cb', '\u299a', '\u299a', '\u0175', '\u0175', '\u2a5f', '\u2a5f', '\u2227', '\u2227', '\u2259', '\u2259', '\u2118', '\u2118', '\U0001d534', '\U0001d534', '\U0001d568', '\U0001d568', '\u2118', '\u2118', '\u2240', '\u2240', '\u2240', '\u2240', '\U0001d4cc', '\U0001d4cc', '\u22c2', '\u22c2', '\u25ef', 
7476 '\u25ef', '\u22c3', '\u22c3', '\u25bd', '\u25bd', '\U0001d535', '\U0001d535', '\u27fa', '\u27fa', '\u27f7', '\u27f7', '\u03be', '\u03be', '\u27f8', '\u27f8', '\u27f5', '\u27f5', '\u27fc', '\u27fc', '\u22fb', '\u22fb', '\u2a00', '\u2a00', '\U0001d569', '\U0001d569', '\u2a01', '\u2a01', '\u2a02', '\u2a02', '\u27f9', '\u27f9', '\u27f6', '\u27f6', '\U0001d4cd', '\U0001d4cd', '\u2a06', '\u2a06', '\u2a04', 
7477 '\u2a04', '\u25b3', '\u25b3', '\u22c1', '\u22c1', '\u22c0', '\u22c0', '\u00fd', '\u00fd', '\u044f', '\u044f', '\u0177', '\u0177', '\u044b', '\u044b', '\u00a5', '\u00a5', '\U0001d536', '\U0001d536', '\u0457', '\u0457', '\U0001d56a', '\U0001d56a', '\U0001d4ce', '\U0001d4ce', '\u044e', '\u044e', '\u00ff', '\u00ff', '\u017a', '\u017a', '\u017e', '\u017e', '\u0437', '\u0437', '\u017c', '\u017c', '\u2128', 
7478 '\u2128', '\u03b6', '\u03b6', '\U0001d537', '\U0001d537', '\u0436', '\u0436', '\u21dd', '\u21dd', '\U0001d56b', '\U0001d56b', '\U0001d4cf', '\U0001d4cf', '\u200d', '\u200d', '\u200c', '\u200c', ];
7479 
7480 
7481 
7482 
7483 
7484 
7485 
7486 
7487 
7488 
7489 
7490 
7491 
7492 
7493 
7494 
7495 
7496 
7497 
7498 
7499 
7500 
7501 
7502 // dom event support, if you want to use it
7503 
7504 /// used for DOM events
7505 version(dom_with_events)
7506 alias EventHandler = void delegate(Element handlerAttachedTo, Event event);
7507 
7508 /// This is a DOM event, like in javascript. Note that this library never fires events - it is only here for you to use if you want it.
7509 version(dom_with_events)
7510 class Event {
7511 	this(string eventName, Element target) {
7512 		this.eventName = eventName;
7513 		this.srcElement = target;
7514 	}
7515 
7516 	/// Prevents the default event handler (if there is one) from being called
7517 	void preventDefault() {
7518 		defaultPrevented = true;
7519 	}
7520 
7521 	/// Stops the event propagation immediately.
7522 	void stopPropagation() {
7523 		propagationStopped = true;
7524 	}
7525 
7526 	bool defaultPrevented;
7527 	bool propagationStopped;
7528 	string eventName;
7529 
7530 	Element srcElement;
7531 	alias srcElement target;
7532 
7533 	Element relatedTarget;
7534 
7535 	int clientX;
7536 	int clientY;
7537 
7538 	int button;
7539 
7540 	bool isBubbling;
7541 
7542 	/// this sends it only to the target. If you want propagation, use dispatch() instead.
7543 	void send() {
7544 		if(srcElement is null)
7545 			return;
7546 
7547 		auto e = srcElement;
7548 
7549 		if(eventName in e.bubblingEventHandlers)
7550 		foreach(handler; e.bubblingEventHandlers[eventName])
7551 			handler(e, this);
7552 
7553 		if(!defaultPrevented)
7554 			if(eventName in e.defaultEventHandlers)
7555 				e.defaultEventHandlers[eventName](e, this);
7556 	}
7557 
7558 	/// this dispatches the element using the capture -> target -> bubble process
7559 	void dispatch() {
7560 		if(srcElement is null)
7561 			return;
7562 
7563 		// first capture, then bubble
7564 
7565 		Element[] chain;
7566 		Element curr = srcElement;
7567 		while(curr) {
7568 			auto l = curr;
7569 			chain ~= l;
7570 			curr = curr.parentNode;
7571 
7572 		}
7573 
7574 		isBubbling = false;
7575 
7576 		foreach(e; chain.retro()) {
7577 			if(eventName in e.capturingEventHandlers)
7578 			foreach(handler; e.capturingEventHandlers[eventName])
7579 				handler(e, this);
7580 
7581 			// the default on capture should really be to always do nothing
7582 
7583 			//if(!defaultPrevented)
7584 			//	if(eventName in e.defaultEventHandlers)
7585 			//		e.defaultEventHandlers[eventName](e.element, this);
7586 
7587 			if(propagationStopped)
7588 				break;
7589 		}
7590 
7591 		isBubbling = true;
7592 		if(!propagationStopped)
7593 		foreach(e; chain) {
7594 			if(eventName in e.bubblingEventHandlers)
7595 			foreach(handler; e.bubblingEventHandlers[eventName])
7596 				handler(e, this);
7597 
7598 			if(propagationStopped)
7599 				break;
7600 		}
7601 
7602 		if(!defaultPrevented)
7603 		foreach(e; chain) {
7604 				if(eventName in e.defaultEventHandlers)
7605 					e.defaultEventHandlers[eventName](e, this);
7606 		}
7607 	}
7608 }
7609 
7610 struct FormFieldOptions {
7611 	// usable for any
7612 
7613 	/// this is a regex pattern used to validate the field
7614 	string pattern;
7615 	/// must the field be filled in? Even with a regex, it can be submitted blank if this is false.
7616 	bool isRequired;
7617 	/// this is displayed as an example to the user
7618 	string placeholder;
7619 
7620 	// usable for numeric ones
7621 
7622 
7623 	// convenience methods to quickly get some options
7624 	@property static FormFieldOptions none() {
7625 		FormFieldOptions f;
7626 		return f;
7627 	}
7628 
7629 	static FormFieldOptions required() {
7630 		FormFieldOptions f;
7631 		f.isRequired = true;
7632 		return f;
7633 	}
7634 
7635 	static FormFieldOptions regex(string pattern, bool required = false) {
7636 		FormFieldOptions f;
7637 		f.pattern = pattern;
7638 		f.isRequired = required;
7639 		return f;
7640 	}
7641 
7642 	static FormFieldOptions fromElement(Element e) {
7643 		FormFieldOptions f;
7644 		if(e.hasAttribute("required"))
7645 			f.isRequired = true;
7646 		if(e.hasAttribute("pattern"))
7647 			f.pattern = e.pattern;
7648 		if(e.hasAttribute("placeholder"))
7649 			f.placeholder = e.placeholder;
7650 		return f;
7651 	}
7652 
7653 	Element applyToElement(Element e) {
7654 		if(this.isRequired)
7655 			e.required = "required";
7656 		if(this.pattern.length)
7657 			e.pattern = this.pattern;
7658 		if(this.placeholder.length)
7659 			e.placeholder = this.placeholder;
7660 		return e;
7661 	}
7662 }
7663 
7664 // this needs to look just like a string, but can expand as needed
7665 version(no_dom_stream)
7666 alias string Utf8Stream;
7667 else
7668 class Utf8Stream {
7669 	protected:
7670 		// these two should be overridden in subclasses to actually do the stream magic
7671 		string getMore() {
7672 			if(getMoreHelper !is null)
7673 				return getMoreHelper();
7674 			return null;
7675 		}
7676 
7677 		bool hasMore() {
7678 			if(hasMoreHelper !is null)
7679 				return hasMoreHelper();
7680 			return false;
7681 		}
7682 		// the rest should be ok
7683 
7684 	public:
7685 		this(string d) {
7686 			this.data = d;
7687 		}
7688 
7689 		this(string delegate() getMoreHelper, bool delegate() hasMoreHelper) {
7690 			this.getMoreHelper = getMoreHelper;
7691 			this.hasMoreHelper = hasMoreHelper;
7692 
7693 			if(hasMore())
7694 				this.data ~= getMore();
7695 
7696 			stdout.flush();
7697 		}
7698 
7699 		@property final size_t length() {
7700 			// the parser checks length primarily directly before accessing the next character
7701 			// so this is the place we'll hook to append more if possible and needed.
7702 			if(lastIdx + 1 >= data.length && hasMore()) {
7703 				data ~= getMore();
7704 			}
7705 			return data.length;
7706 		}
7707 
7708 		final char opIndex(size_t idx) {
7709 			if(idx > lastIdx)
7710 				lastIdx = idx;
7711 			return data[idx];
7712 		}
7713 
7714 		final string opSlice(size_t start, size_t end) {
7715 			if(end > lastIdx)
7716 				lastIdx = end;
7717 			return data[start .. end];
7718 		}
7719 
7720 		final size_t opDollar() {
7721 			return length();
7722 		}
7723 
7724 		final Utf8Stream opBinary(string op : "~")(string s) {
7725 			this.data ~= s;
7726 			return this;
7727 		}
7728 
7729 		final Utf8Stream opOpAssign(string op : "~")(string s) {
7730 			this.data ~= s;
7731 			return this;
7732 		}
7733 
7734 		final Utf8Stream opAssign(string rhs) {
7735 			this.data = rhs;
7736 			return this;
7737 		}
7738 	private:
7739 		string data;
7740 
7741 		size_t lastIdx;
7742 
7743 		bool delegate() hasMoreHelper;
7744 		string delegate() getMoreHelper;
7745 
7746 
7747 		/+
7748 		// used to maybe clear some old stuff
7749 		// you might have to remove elements parsed with it too since they can hold slices into the
7750 		// old stuff, preventing gc
7751 		void dropFront(int bytes) {
7752 			posAdjustment += bytes;
7753 			data = data[bytes .. $];
7754 		}
7755 
7756 		int posAdjustment;
7757 		+/
7758 }
7759 
7760 void fillForm(T)(Form form, T obj, string name) { 
7761 	import arsd.database; 
7762 	fillData((k, v) => form.setValue(k, v), obj, name); 
7763 } 
7764 
7765 
7766 /+
7767 /+
7768 Syntax:
7769 
7770 Tag: tagname#id.class
7771 Tree: Tag(Children, comma, separated...)
7772 Children: Tee or Variable
7773 Variable: $varname with optional |funcname following.
7774 
7775 If a variable has a tree after it, it breaks the variable down:
7776 	* if array, foreach it does the tree
7777 	* if struct, it breaks down the member variables
7778 
7779 stolen from georgy on irc, see: https://github.com/georgy7/stringplate
7780 +/
7781 struct Stringplate {
7782 	/++
7783 
7784 	+/
7785 	this(string s) {
7786 
7787 	}
7788 
7789 	/++
7790 
7791 	+/
7792 	Element expand(T...)(T vars) {
7793 		return null;
7794 	}
7795 }
7796 ///
7797 unittest {
7798 	auto stringplate = Stringplate("#bar(.foo($foo), .baz($baz))");
7799 	assert(stringplate.expand.innerHTML == `<div id="bar"><div class="foo">$foo</div><div class="baz">$baz</div></div>`);
7800 }
7801 +/
7802 
7803 bool allAreInlineHtml(const(Element)[] children, const string[] inlineElements) {
7804 	foreach(child; children) {
7805 		if(child.nodeType == NodeType.Text && child.nodeValue.strip.length) {
7806 			// cool
7807 		} else if(child.tagName.isInArray(inlineElements) && allAreInlineHtml(child.children, inlineElements)) {
7808 			// cool
7809 		} else {
7810 			// prolly block
7811 			return false;
7812 		}
7813 	}
7814 	return true;
7815 }
7816 
7817 private bool isSimpleWhite(dchar c) {
7818 	return c == ' ' || c == '\r' || c == '\n' || c == '\t';
7819 }
7820 
7821 unittest {
7822 	// Test for issue #120
7823 	string s = `<html>
7824 	<body>
7825 		<P>AN
7826 		<P>bubbles</P>
7827 		<P>giggles</P>
7828 	</body>
7829 </html>`;
7830 	auto doc = new Document();
7831 	doc.parseUtf8(s, false, false);
7832 	auto s2 = doc.toString();
7833 	assert(
7834 			s2.indexOf("bubbles") < s2.indexOf("giggles"),
7835 			"paragraph order incorrect:\n" ~ s2);
7836 }
7837 
7838 unittest {
7839 	// test for suncarpet email dec 24 2019
7840 	// arbitrary id asduiwh
7841 	auto document = new Document("<html>
7842         <head>
7843                 <meta charset=\"utf-8\"></meta>
7844                 <title>Element.querySelector Test</title>
7845         </head>
7846         <body>
7847                 <div id=\"foo\">
7848                         <div>Foo</div>
7849                         <div>Bar</div>
7850                 </div>
7851 		<div id=\"empty\"></div>
7852 		<div id=\"empty-but-text\">test</div>
7853         </body>
7854 </html>");
7855 
7856 	auto doc = document;
7857 
7858 	{
7859 	auto empty = doc.requireElementById("empty");
7860 	assert(empty.querySelector(" > *") is null, empty.querySelector(" > *").toString);
7861 	}
7862 	{
7863 	auto empty = doc.requireElementById("empty-but-text");
7864 	assert(empty.querySelector(" > *") is null, empty.querySelector(" > *").toString);
7865 	}
7866 
7867 	assert(doc.querySelectorAll("div div").length == 2);
7868 	assert(doc.querySelector("div").querySelectorAll("div").length == 2);
7869 	assert(doc.querySelectorAll("> html").length == 0);
7870 	assert(doc.querySelector("head").querySelectorAll("> title").length == 1);
7871 	assert(doc.querySelector("head").querySelectorAll("> meta[charset]").length == 1);
7872 
7873 
7874 	assert(doc.root.matches("html"));
7875 	assert(!doc.root.matches("nothtml"));
7876 	assert(doc.querySelector("#foo > div").matches("div"));
7877 	assert(doc.querySelector("body > #foo").matches("#foo"));
7878 
7879 	assert(doc.root.querySelectorAll(":root > body").length == 0); // the root has no CHILD root!
7880 	assert(doc.querySelectorAll(":root > body").length == 1); // but the DOCUMENT does
7881 	assert(doc.querySelectorAll(" > body").length == 1); //  should mean the same thing
7882 	assert(doc.root.querySelectorAll(" > body").length == 1); // the root of HTML has this
7883 	assert(doc.root.querySelectorAll(" > html").length == 0); // but not this
7884 
7885 	// also confirming the querySelector works via the mdn definition
7886 	auto foo = doc.requireSelector("#foo");
7887 	assert(foo.querySelector("#foo > div") !is null);
7888 	assert(foo.querySelector("body #foo > div") !is null);
7889 
7890 	// this is SUPPOSED to work according to the spec but never has in dom.d since it limits the scope.
7891 	// the new css :scope thing is designed to bring this in. and meh idk if i even care.
7892 	//assert(foo.querySelectorAll("#foo > div").length == 2);
7893 }
7894 
7895 unittest {
7896 	// based on https://developer.mozilla.org/en-US/docs/Web/API/Element/closest example
7897 	auto document = new Document(`<article>
7898   <div id="div-01">Here is div-01
7899     <div id="div-02">Here is div-02
7900       <div id="div-03">Here is div-03</div>
7901     </div>
7902   </div>
7903 </article>`, true, true);
7904 
7905 	auto el = document.getElementById("div-03");
7906 	assert(el.closest("#div-02").id == "div-02");
7907 	assert(el.closest("div div").id == "div-03");
7908 	assert(el.closest("article > div").id == "div-01");
7909 	assert(el.closest(":not(div)").tagName == "article");
7910 
7911 	assert(el.closest("p") is null);
7912 	assert(el.closest("p, div") is el);
7913 }
7914 
7915 unittest {
7916 	// https://developer.mozilla.org/en-US/docs/Web/CSS/:is
7917 	auto document = new Document(`<test>
7918 		<div class="foo"><p>cool</p><span>bar</span></div>
7919 		<main><p>two</p></main>
7920 	</test>`);
7921 
7922 	assert(document.querySelectorAll(":is(.foo, main) p").length == 2);
7923 	assert(document.querySelector("div:where(.foo)") !is null);
7924 }
7925 
7926 unittest {
7927 immutable string html = q{
7928 <root>
7929 <div class="roundedbox">
7930  <table>
7931   <caption class="boxheader">Recent Reviews</caption>
7932   <tr>
7933    <th>Game</th>
7934    <th>User</th>
7935    <th>Rating</th>
7936    <th>Created</th>
7937   </tr>
7938 
7939   <tr>
7940    <td>June 13, 2020 15:10</td>
7941    <td><a href="/reviews/8833">[Show]</a></td>
7942   </tr>
7943 
7944   <tr>
7945    <td>June 13, 2020 15:02</td>
7946    <td><a href="/reviews/8832">[Show]</a></td>
7947   </tr>
7948 
7949   <tr>
7950    <td>June 13, 2020 14:41</td>
7951    <td><a href="/reviews/8831">[Show]</a></td>
7952   </tr>
7953  </table>
7954 </div>
7955 </root>
7956 };
7957 
7958   auto doc = new Document(cast(string)html);
7959   // this should select the second table row, but...
7960   auto rd = doc.root.querySelector(`div.roundedbox > table > caption.boxheader + tr + tr + tr > td > a[href^=/reviews/]`);
7961   assert(rd !is null);
7962   assert(rd.href == "/reviews/8832");
7963 
7964   rd = doc.querySelector(`div.roundedbox > table > caption.boxheader + tr + tr + tr > td > a[href^=/reviews/]`);
7965   assert(rd !is null);
7966   assert(rd.href == "/reviews/8832");
7967 }
7968 
7969 unittest {
7970 	try {
7971 		auto doc = new XmlDocument("<testxmlns:foo=\"/\"></test>");
7972 		assert(0);
7973 	} catch(Exception e) {
7974 		// good; it should throw an exception, not an error.
7975 	}
7976 }
7977 
7978 unittest {
7979 	// toPrettyString is not stable, but these are some best-effort attempts
7980 	// despite these being in a test, I might change these anyway!
7981 	assert(Element.make("a").toPrettyString == "<a></a>");
7982 	assert(Element.make("a", "b").toPrettyString == "<a>b</a>");
7983 	assert(Element.make("a", "b").toPrettyString(false, 0, "") == "<a>b</a>");
7984 
7985 	{
7986 	auto document = new Document("<html><body><p>hello <a href=\"world\">world</a></p></body></html>");
7987 	auto pretty = document.toPrettyString(false, 0, "  ");
7988 	assert(pretty ==
7989 `<!DOCTYPE html>
7990 <html>
7991   <body>
7992     <p>hello <a href="world">world</a></p>
7993   </body>
7994 </html>`, pretty);
7995 	}
7996 
7997 	{
7998 	auto document = new XmlDocument("<html><body><p>hello <a href=\"world\">world</a></p></body></html>");
7999 	auto pretty = document.toPrettyString(false, 0, "  ");
8000 	assert(pretty ==
8001 `<?xml version="1.0" encoding="UTF-8"?>
8002 <html>
8003   <body>
8004     <p>hello
8005       <a href="world">world</a>
8006     </p>
8007   </body>
8008 </html>`, pretty);
8009 	}
8010 
8011 	auto document = new XmlDocument("<a att=\"http://ele\"><b><ele1>Hello</ele1>\n  <c>\n   <d>\n    <ele2>How are you?</ele2>\n   </d>\n   <e>\n    <ele3>Good &amp; you?</ele3>\n   </e>\n  </c>\n </b>\n</a>");
8012 	assert(document.root.toPrettyString(false, 0, " ") == "<a att=\"http://ele\">\n <b>\n  <ele1>Hello</ele1>\n  <c>\n   <d>\n    <ele2>How are you?</ele2>\n   </d>\n   <e>\n    <ele3>Good &amp; you?</ele3>\n   </e>\n  </c>\n </b>\n</a>");
8013 
8014 }
8015 
8016 unittest {
8017 	auto document = new Document("broken"); // just ensuring it doesn't crash
8018 }
8019 
8020 /*
8021 Copyright: Adam D. Ruppe, 2010 - 2021
8022 License:   <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
8023 Authors: Adam D. Ruppe, with contributions by Nick Sabalausky, Trass3r, and ketmar among others
8024 
8025         Copyright Adam D. Ruppe 2010-2021.
8026 Distributed under the Boost Software License, Version 1.0.
8027    (See accompanying file LICENSE_1_0.txt or copy at
8028         http://www.boost.org/LICENSE_1_0.txt)
8029 */
8030 
8031