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 		root.parent_ = this;
1137 
1138 		if(!strict) // in strict mode, we'll just ignore stuff after the xml
1139 		while(r.type != 4) {
1140 			r = readElement();
1141 			if(r.type != 4 && r.type != 2) { // if not empty and not ignored
1142 				if(r.element !is null)
1143 					piecesAfterRoot ~= r.element;
1144 			}
1145 		}
1146 
1147 		if(root is null)
1148 		{
1149 			if(strict)
1150 				assert(0, "empty document should be impossible in strict mode");
1151 			else
1152 				parseUtf8(`<html><head></head><body></body></html>`); // fill in a dummy document in loose mode since that's what browsers do
1153 		}
1154 
1155 		if(paragraphHackfixRequired) {
1156 			assert(!strict); // this should never happen in strict mode; it ought to never set the hack flag...
1157 
1158 			// in loose mode, we can see some "bad" nesting (it's valid html, but poorly formed xml).
1159 			// It's hard to handle above though because my code sucks. So, we'll fix it here.
1160 
1161 			// Where to insert based on the parent (for mixed closed/unclosed <p> tags). See #120
1162 			// Kind of inefficient because we can't detect when we recurse back out of a node.
1163 			Element[Element] insertLocations;
1164 			auto iterator = root.tree;
1165 			foreach(ele; iterator) {
1166 				if(ele.parentNode is null)
1167 					continue;
1168 
1169 				if(ele.tagName == "p" && ele.parentNode.tagName == ele.tagName) {
1170 					auto shouldBePreviousSibling = ele.parentNode;
1171 					auto holder = shouldBePreviousSibling.parentNode; // this is the two element's mutual holder...
1172 					if (auto p = holder in insertLocations) {
1173 						shouldBePreviousSibling = *p;
1174 						assert(shouldBePreviousSibling.parentNode is holder);
1175 					}
1176 					ele = holder.insertAfter(shouldBePreviousSibling, ele.removeFromTree());
1177 					insertLocations[holder] = ele;
1178 					iterator.currentKilled(); // the current branch can be skipped; we'll hit it soon anyway since it's now next up.
1179 				}
1180 			}
1181 		}
1182 	}
1183 
1184 	/* end massive parse function */
1185 
1186 	/// Gets the <title> element's innerText, if one exists
1187 	@property string title() {
1188 		bool doesItMatch(Element e) {
1189 			return (e.tagName == "title");
1190 		}
1191 
1192 		auto e = findFirst(&doesItMatch);
1193 		if(e)
1194 			return e.innerText();
1195 		return "";
1196 	}
1197 
1198 	/// Sets the title of the page, creating a <title> element if needed.
1199 	@property void title(string t) {
1200 		bool doesItMatch(Element e) {
1201 			return (e.tagName == "title");
1202 		}
1203 
1204 		auto e = findFirst(&doesItMatch);
1205 
1206 		if(!e) {
1207 			e = createElement("title");
1208 			auto heads = getElementsByTagName("head");
1209 			if(heads.length)
1210 				heads[0].appendChild(e);
1211 		}
1212 
1213 		if(e)
1214 			e.innerText = t;
1215 	}
1216 
1217 	// FIXME: would it work to alias root this; ???? might be a good idea
1218 	/// These functions all forward to the root element. See the documentation in the Element class.
1219 	Element getElementById(string id) {
1220 		return root.getElementById(id);
1221 	}
1222 
1223 	/// ditto
1224 	final SomeElementType requireElementById(SomeElementType = Element)(string id, string file = __FILE__, size_t line = __LINE__)
1225 		if( is(SomeElementType : Element))
1226 		out(ret) { assert(ret !is null); }
1227 	do {
1228 		return root.requireElementById!(SomeElementType)(id, file, line);
1229 	}
1230 
1231 	/// ditto
1232 	final SomeElementType requireSelector(SomeElementType = Element)(string selector, string file = __FILE__, size_t line = __LINE__)
1233 		if( is(SomeElementType : Element))
1234 		out(ret) { assert(ret !is null); }
1235 	do {
1236 		auto e = cast(SomeElementType) querySelector(selector);
1237 		if(e is null)
1238 			throw new ElementNotFoundException(SomeElementType.stringof, selector, this.root, file, line);
1239 		return e;
1240 	}
1241 
1242 	/// ditto
1243 	final MaybeNullElement!SomeElementType optionSelector(SomeElementType = Element)(string selector, string file = __FILE__, size_t line = __LINE__)
1244 		if(is(SomeElementType : Element))
1245 	{
1246 		auto e = cast(SomeElementType) querySelector(selector);
1247 		return MaybeNullElement!SomeElementType(e);
1248 	}
1249 
1250 	/// ditto
1251 	@scriptable
1252 	Element querySelector(string selector) {
1253 		// see comment below on Document.querySelectorAll
1254 		auto s = Selector(selector);//, !loose);
1255 		foreach(ref comp; s.components)
1256 			if(comp.parts.length && comp.parts[0].separation == 0)
1257 				comp.parts[0].separation = -1;
1258 		foreach(e; s.getMatchingElementsLazy(this.root))
1259 			return e;
1260 		return null;
1261 
1262 	}
1263 
1264 	/// ditto
1265 	@scriptable
1266 	Element[] querySelectorAll(string selector) {
1267 		// In standards-compliant code, the document is slightly magical
1268 		// in that it is a pseudoelement at top level. It should actually
1269 		// match the root as one of its children.
1270 		//
1271 		// In versions of dom.d before Dec 29 2019, this worked because
1272 		// querySelectorAll was willing to return itself. With that bug fix
1273 		// (search "arbitrary id asduiwh" in this file for associated unittest)
1274 		// this would have failed. Hence adding back the root if it matches the
1275 		// selector itself.
1276 		//
1277 		// I'd love to do this better later.
1278 
1279 		auto s = Selector(selector);//, !loose);
1280 		foreach(ref comp; s.components)
1281 			if(comp.parts.length && comp.parts[0].separation == 0)
1282 				comp.parts[0].separation = -1;
1283 		return s.getMatchingElements(this.root);
1284 	}
1285 
1286 	/// ditto
1287 	deprecated("use querySelectorAll instead")
1288 	Element[] getElementsBySelector(string selector) {
1289 		return root.getElementsBySelector(selector);
1290 	}
1291 
1292 	/// ditto
1293 	@scriptable
1294 	Element[] getElementsByTagName(string tag) {
1295 		return root.getElementsByTagName(tag);
1296 	}
1297 
1298 	/// ditto
1299 	@scriptable
1300 	Element[] getElementsByClassName(string tag) {
1301 		return root.getElementsByClassName(tag);
1302 	}
1303 
1304 	/** FIXME: btw, this could just be a lazy range...... */
1305 	Element getFirstElementByTagName(string tag) {
1306 		if(loose)
1307 			tag = tag.toLower();
1308 		bool doesItMatch(Element e) {
1309 			return e.tagName == tag;
1310 		}
1311 		return findFirst(&doesItMatch);
1312 	}
1313 
1314 	/// 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.)
1315 	Element mainBody() {
1316 		return getFirstElementByTagName("body");
1317 	}
1318 
1319 	/// this uses a weird thing... it's [name=] if no colon and
1320 	/// [property=] if colon
1321 	string getMeta(string name) {
1322 		string thing = name.indexOf(":") == -1 ? "name" : "property";
1323 		auto e = querySelector("head meta["~thing~"="~name~"]");
1324 		if(e is null)
1325 			return null;
1326 		return e.content;
1327 	}
1328 
1329 	/// 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/
1330 	void setMeta(string name, string value) {
1331 		string thing = name.indexOf(":") == -1 ? "name" : "property";
1332 		auto e = querySelector("head meta["~thing~"="~name~"]");
1333 		if(e is null) {
1334 			e = requireSelector("head").addChild("meta");
1335 			e.setAttribute(thing, name);
1336 		}
1337 
1338 		e.content = value;
1339 	}
1340 
1341 	///.
1342 	Form[] forms() {
1343 		return cast(Form[]) getElementsByTagName("form");
1344 	}
1345 
1346 	///.
1347 	Form createForm()
1348 		out(ret) {
1349 			assert(ret !is null);
1350 		}
1351 	do {
1352 		return cast(Form) createElement("form");
1353 	}
1354 
1355 	///.
1356 	Element createElement(string name) {
1357 		if(loose)
1358 			name = name.toLower();
1359 
1360 		auto e = Element.make(name, null, null, selfClosedElements);
1361 
1362 		return e;
1363 
1364 //		return new Element(this, name, null, selfClosed);
1365 	}
1366 
1367 	///.
1368 	Element createFragment() {
1369 		return new DocumentFragment(this);
1370 	}
1371 
1372 	///.
1373 	Element createTextNode(string content) {
1374 		return new TextNode(this, content);
1375 	}
1376 
1377 
1378 	///.
1379 	Element findFirst(bool delegate(Element) doesItMatch) {
1380 		if(root is null)
1381 			return null;
1382 		Element result;
1383 
1384 		bool goThroughElement(Element e) {
1385 			if(doesItMatch(e)) {
1386 				result = e;
1387 				return true;
1388 			}
1389 
1390 			foreach(child; e.children) {
1391 				if(goThroughElement(child))
1392 					return true;
1393 			}
1394 
1395 			return false;
1396 		}
1397 
1398 		goThroughElement(root);
1399 
1400 		return result;
1401 	}
1402 
1403 	///.
1404 	void clear() {
1405 		root = null;
1406 		loose = false;
1407 	}
1408 
1409 	///.
1410 	void setProlog(string d) {
1411 		_prolog = d;
1412 		prologWasSet = true;
1413 	}
1414 
1415 	///.
1416 	private string _prolog = "<!DOCTYPE html>\n";
1417 	private bool prologWasSet = false; // set to true if the user changed it
1418 
1419 	@property string prolog() const {
1420 		// if the user explicitly changed it, do what they want
1421 		// or if we didn't keep/find stuff from the document itself,
1422 		// we'll use the builtin one as a default.
1423 		if(prologWasSet || piecesBeforeRoot.length == 0)
1424 			return _prolog;
1425 
1426 		string p;
1427 		foreach(e; piecesBeforeRoot)
1428 			p ~= e.toString() ~ "\n";
1429 		return p;
1430 	}
1431 
1432 	///.
1433 	override string toString() const {
1434 		return prolog ~ root.toString();
1435 	}
1436 
1437 	/++
1438 		Writes it out with whitespace for easier eyeball debugging
1439 
1440 		Do NOT use for anything other than eyeball debugging,
1441 		because whitespace may be significant content in XML.
1442 	+/
1443 	string toPrettyString(bool insertComments = false, int indentationLevel = 0, string indentWith = "\t") const {
1444 		import std.string;
1445 		string s = prolog.strip;
1446 
1447 		/*
1448 		if(insertComments) s ~= "<!--";
1449 		s ~= "\n";
1450 		if(insertComments) s ~= "-->";
1451 		*/
1452 
1453 		s ~= root.toPrettyString(insertComments, indentationLevel, indentWith);
1454 		foreach(a; piecesAfterRoot)
1455 			s ~= a.toPrettyString(insertComments, indentationLevel, indentWith);
1456 		return s;
1457 	}
1458 
1459 	///.
1460 	Element root;
1461 
1462 	/// if these were kept, this is stuff that appeared before the root element, such as <?xml version ?> decls and <!DOCTYPE>s
1463 	Element[] piecesBeforeRoot;
1464 
1465 	/// stuff after the root, only stored in non-strict mode and not used in toString, but available in case you want it
1466 	Element[] piecesAfterRoot;
1467 
1468 	///.
1469 	bool loose;
1470 
1471 
1472 
1473 	// what follows are for mutation events that you can observe
1474 	void delegate(DomMutationEvent)[] eventObservers;
1475 
1476 	void dispatchMutationEvent(DomMutationEvent e) {
1477 		foreach(o; eventObservers)
1478 			o(e);
1479 	}
1480 }
1481 
1482 interface DomParent {
1483 	inout(Document) asDocument() inout;
1484 	inout(Element) asElement() inout;
1485 }
1486 
1487 /// This represents almost everything in the DOM.
1488 /// Group: core_functionality
1489 class Element : DomParent {
1490 	inout(Document) asDocument() inout { return null; }
1491 	inout(Element) asElement() inout { return this; }
1492 
1493 	/// Returns a collection of elements by selector.
1494 	/// See: [Document.opIndex]
1495 	ElementCollection opIndex(string selector) {
1496 		auto e = ElementCollection(this);
1497 		return e[selector];
1498 	}
1499 
1500 	/++
1501 		Returns the child node with the particular index.
1502 
1503 		Be aware that child nodes include text nodes, including
1504 		whitespace-only nodes.
1505 	+/
1506 	Element opIndex(size_t index) {
1507 		if(index >= children.length)
1508 			return null;
1509 		return this.children[index];
1510 	}
1511 
1512 	/// 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.
1513 	final SomeElementType requireElementById(SomeElementType = Element)(string id, string file = __FILE__, size_t line = __LINE__)
1514 	if(
1515 		is(SomeElementType : Element)
1516 	)
1517 	out(ret) {
1518 		assert(ret !is null);
1519 	}
1520 	do {
1521 		auto e = cast(SomeElementType) getElementById(id);
1522 		if(e is null)
1523 			throw new ElementNotFoundException(SomeElementType.stringof, "id=" ~ id, this, file, line);
1524 		return e;
1525 	}
1526 
1527 	/// ditto but with selectors instead of ids
1528 	final SomeElementType requireSelector(SomeElementType = Element)(string selector, string file = __FILE__, size_t line = __LINE__)
1529 	if(
1530 		is(SomeElementType : Element)
1531 	)
1532 	out(ret) {
1533 		assert(ret !is null);
1534 	}
1535 	do {
1536 		auto e = cast(SomeElementType) querySelector(selector);
1537 		if(e is null)
1538 			throw new ElementNotFoundException(SomeElementType.stringof, selector, this, file, line);
1539 		return e;
1540 	}
1541 
1542 
1543 	/++
1544 		If a matching selector is found, it returns that Element. Otherwise, the returned object returns null for all methods.
1545 	+/
1546 	final MaybeNullElement!SomeElementType optionSelector(SomeElementType = Element)(string selector, string file = __FILE__, size_t line = __LINE__)
1547 		if(is(SomeElementType : Element))
1548 	{
1549 		auto e = cast(SomeElementType) querySelector(selector);
1550 		return MaybeNullElement!SomeElementType(e);
1551 	}
1552 
1553 
1554 
1555 	/// get all the classes on this element
1556 	@property string[] classes() {
1557 		return split(className, " ");
1558 	}
1559 
1560 	/// Adds a string to the class attribute. The class attribute is used a lot in CSS.
1561 	@scriptable
1562 	Element addClass(string c) {
1563 		if(hasClass(c))
1564 			return this; // don't add it twice
1565 
1566 		string cn = getAttribute("class");
1567 		if(cn.length == 0) {
1568 			setAttribute("class", c);
1569 			return this;
1570 		} else {
1571 			setAttribute("class", cn ~ " " ~ c);
1572 		}
1573 
1574 		return this;
1575 	}
1576 
1577 	/// Removes a particular class name.
1578 	@scriptable
1579 	Element removeClass(string c) {
1580 		if(!hasClass(c))
1581 			return this;
1582 		string n;
1583 		foreach(name; classes) {
1584 			if(c == name)
1585 				continue; // cut it out
1586 			if(n.length)
1587 				n ~= " ";
1588 			n ~= name;
1589 		}
1590 
1591 		className = n.strip();
1592 
1593 		return this;
1594 	}
1595 
1596 	/// Returns whether the given class appears in this element.
1597 	bool hasClass(string c) {
1598 		string cn = className;
1599 
1600 		auto idx = cn.indexOf(c);
1601 		if(idx == -1)
1602 			return false;
1603 
1604 		foreach(cla; cn.split(" "))
1605 			if(cla == c)
1606 				return true;
1607 		return false;
1608 
1609 		/*
1610 		int rightSide = idx + c.length;
1611 
1612 		bool checkRight() {
1613 			if(rightSide == cn.length)
1614 				return true; // it's the only class
1615 			else if(iswhite(cn[rightSide]))
1616 				return true;
1617 			return false; // this is a substring of something else..
1618 		}
1619 
1620 		if(idx == 0) {
1621 			return checkRight();
1622 		} else {
1623 			if(!iswhite(cn[idx - 1]))
1624 				return false; // substring
1625 			return checkRight();
1626 		}
1627 
1628 		assert(0);
1629 		*/
1630 	}
1631 
1632 
1633 	/* *******************************
1634 		  DOM Mutation
1635 	*********************************/
1636 	/// convenience function to quickly add a tag with some text or
1637 	/// other relevant info (for example, it's a src for an <img> element
1638 	/// instead of inner text)
1639 	Element addChild(string tagName, string childInfo = null, string childInfo2 = null)
1640 		in {
1641 			assert(tagName !is null);
1642 		}
1643 		out(e) {
1644 			//assert(e.parentNode is this);
1645 			//assert(e.parentDocument is this.parentDocument);
1646 		}
1647 	do {
1648 		auto e = Element.make(tagName, childInfo, childInfo2);
1649 		// FIXME (maybe): if the thing is self closed, we might want to go ahead and
1650 		// return the parent. That will break existing code though.
1651 		return appendChild(e);
1652 	}
1653 
1654 	/// Another convenience function. Adds a child directly after the current one, returning
1655 	/// the new child.
1656 	///
1657 	/// Between this, addChild, and parentNode, you can build a tree as a single expression.
1658 	Element addSibling(string tagName, string childInfo = null, string childInfo2 = null)
1659 		in {
1660 			assert(tagName !is null);
1661 			assert(parentNode !is null);
1662 		}
1663 		out(e) {
1664 			assert(e.parentNode is this.parentNode);
1665 			assert(e.parentDocument is this.parentDocument);
1666 		}
1667 	do {
1668 		auto e = Element.make(tagName, childInfo, childInfo2);
1669 		return parentNode.insertAfter(this, e);
1670 	}
1671 
1672 	///
1673 	Element addSibling(Element e) {
1674 		return parentNode.insertAfter(this, e);
1675 	}
1676 
1677 	///
1678 	Element addChild(Element e) {
1679 		return this.appendChild(e);
1680 	}
1681 
1682 	/// Convenience function to append text intermixed with other children.
1683 	/// For example: div.addChildren("You can visit my website by ", new Link("mysite.com", "clicking here"), ".");
1684 	/// or div.addChildren("Hello, ", user.name, "!");
1685 
1686 	/// See also: appendHtml. This might be a bit simpler though because you don't have to think about escaping.
1687 	void addChildren(T...)(T t) {
1688 		foreach(item; t) {
1689 			static if(is(item : Element))
1690 				appendChild(item);
1691 			else static if (is(isSomeString!(item)))
1692 				appendText(to!string(item));
1693 			else static assert(0, "Cannot pass " ~ typeof(item).stringof ~ " to addChildren");
1694 		}
1695 	}
1696 
1697 	///.
1698 	Element addChild(string tagName, Element firstChild, string info2 = null)
1699 	in {
1700 		assert(firstChild !is null);
1701 	}
1702 	out(ret) {
1703 		assert(ret !is null);
1704 		assert(ret.parentNode is this);
1705 		assert(firstChild.parentNode is ret);
1706 
1707 		assert(ret.parentDocument is this.parentDocument);
1708 		//assert(firstChild.parentDocument is this.parentDocument);
1709 	}
1710 	do {
1711 		auto e = Element.make(tagName, "", info2);
1712 		e.appendChild(firstChild);
1713 		this.appendChild(e);
1714 		return e;
1715 	}
1716 
1717 	///
1718 	Element addChild(string tagName, in Html innerHtml, string info2 = null)
1719 	in {
1720 	}
1721 	out(ret) {
1722 		assert(ret !is null);
1723 		assert((cast(DocumentFragment) this !is null) || (ret.parentNode is this), ret.toString);// e.parentNode ? e.parentNode.toString : "null");
1724 		assert(ret.parentDocument is this.parentDocument);
1725 	}
1726 	do {
1727 		auto e = Element.make(tagName, "", info2);
1728 		this.appendChild(e);
1729 		e.innerHTML = innerHtml.source;
1730 		return e;
1731 	}
1732 
1733 
1734 	/// .
1735 	void appendChildren(Element[] children) {
1736 		foreach(ele; children)
1737 			appendChild(ele);
1738 	}
1739 
1740 	///.
1741 	void reparent(Element newParent)
1742 		in {
1743 			assert(newParent !is null);
1744 			assert(parentNode !is null);
1745 		}
1746 		out {
1747 			assert(this.parentNode is newParent);
1748 			//assert(isInArray(this, newParent.children));
1749 		}
1750 	do {
1751 		parentNode.removeChild(this);
1752 		newParent.appendChild(this);
1753 	}
1754 
1755 	/**
1756 		Strips this tag out of the document, putting its inner html
1757 		as children of the parent.
1758 
1759 		For example, given: `<p>hello <b>there</b></p>`, if you
1760 		call `stripOut` on the `b` element, you'll be left with
1761 		`<p>hello there<p>`.
1762 
1763 		The idea here is to make it easy to get rid of garbage
1764 		markup you aren't interested in.
1765 	*/
1766 	void stripOut()
1767 		in {
1768 			assert(parentNode !is null);
1769 		}
1770 		out {
1771 			assert(parentNode is null);
1772 			assert(children.length == 0);
1773 		}
1774 	do {
1775 		foreach(c; children)
1776 			c.parentNode = null; // remove the parent
1777 		if(children.length)
1778 			parentNode.replaceChild(this, this.children);
1779 		else
1780 			parentNode.removeChild(this);
1781 		this.children.length = 0; // we reparented them all above
1782 	}
1783 
1784 	/// shorthand for `this.parentNode.removeChild(this)` with `parentNode` `null` check
1785 	/// if the element already isn't in a tree, it does nothing.
1786 	Element removeFromTree()
1787 		in {
1788 
1789 		}
1790 		out(var) {
1791 			assert(this.parentNode is null);
1792 			assert(var is this);
1793 		}
1794 	do {
1795 		if(this.parentNode is null)
1796 			return this;
1797 
1798 		this.parentNode.removeChild(this);
1799 
1800 		return this;
1801 	}
1802 
1803 	/++
1804 		Wraps this element inside the given element.
1805 		It's like `this.replaceWith(what); what.appendchild(this);`
1806 
1807 		Given: `<b>cool</b>`, if you call `b.wrapIn(new Link("site.com", "my site is "));`
1808 		you'll end up with: `<a href="site.com">my site is <b>cool</b></a>`.
1809 	+/
1810 	Element wrapIn(Element what)
1811 		in {
1812 			assert(what !is null);
1813 		}
1814 		out(ret) {
1815 			assert(this.parentNode is what);
1816 			assert(ret is what);
1817 		}
1818 	do {
1819 		this.replaceWith(what);
1820 		what.appendChild(this);
1821 
1822 		return what;
1823 	}
1824 
1825 	/// Replaces this element with something else in the tree.
1826 	Element replaceWith(Element e)
1827 	in {
1828 		assert(this.parentNode !is null);
1829 	}
1830 	do {
1831 		e.removeFromTree();
1832 		this.parentNode.replaceChild(this, e);
1833 		return e;
1834 	}
1835 
1836 	/**
1837 		Splits the className into an array of each class given
1838 	*/
1839 	string[] classNames() const {
1840 		return className().split(" ");
1841 	}
1842 
1843 	/**
1844 		Fetches the first consecutive text nodes concatenated together.
1845 
1846 
1847 		`firstInnerText` of `<example>some text<span>more text</span></example>` is `some text`. It stops at the first child tag encountered.
1848 
1849 		See_also: [directText], [innerText]
1850 	*/
1851 	string firstInnerText() const {
1852 		string s;
1853 		foreach(child; children) {
1854 			if(child.nodeType != NodeType.Text)
1855 				break;
1856 
1857 			s ~= child.nodeValue();
1858 		}
1859 		return s;
1860 	}
1861 
1862 
1863 	/**
1864 		Returns the text directly under this element.
1865 		
1866 
1867 		Unlike [innerText], it does not recurse, and unlike [firstInnerText], it continues
1868 		past child tags. So, `<example>some <b>bold</b> text</example>`
1869 		will return `some  text` because it only gets the text, skipping non-text children.
1870 
1871 		See_also: [firstInnerText], [innerText]
1872 	*/
1873 	@property string directText() {
1874 		string ret;
1875 		foreach(e; children) {
1876 			if(e.nodeType == NodeType.Text)
1877 				ret ~= e.nodeValue();
1878 		}
1879 
1880 		return ret;
1881 	}
1882 
1883 	/**
1884 		Sets the direct text, without modifying other child nodes.
1885 
1886 
1887 		Unlike [innerText], this does *not* remove existing elements in the element.
1888 
1889 		It only replaces the first text node it sees.
1890 
1891 		If there are no text nodes, it calls [appendText].
1892 
1893 		So, given `<div><img />text here</div>`, it will keep the `<img />`, and replace the `text here`.
1894 	*/
1895 	@property void directText(string text) {
1896 		foreach(e; children) {
1897 			if(e.nodeType == NodeType.Text) {
1898 				auto it = cast(TextNode) e;
1899 				it.contents = text;
1900 				return;
1901 			}
1902 		}
1903 
1904 		appendText(text);
1905 	}
1906 
1907 	// do nothing, this is primarily a virtual hook
1908 	// for links and forms
1909 	void setValue(string field, string value) { }
1910 
1911 
1912 	// this is a thing so i can remove observer support if it gets slow
1913 	// I have not implemented all these yet
1914 	private void sendObserverEvent(DomMutationOperations operation, string s1 = null, string s2 = null, Element r = null, Element r2 = null) {
1915 		if(parentDocument is null) return;
1916 		DomMutationEvent me;
1917 		me.operation = operation;
1918 		me.target = this;
1919 		me.relatedString = s1;
1920 		me.relatedString2 = s2;
1921 		me.related = r;
1922 		me.related2 = r2;
1923 		parentDocument.dispatchMutationEvent(me);
1924 	}
1925 
1926 	// putting all the members up front
1927 
1928 	// this ought to be private. don't use it directly.
1929 	Element[] children;
1930 
1931 	/// The name of the tag. Remember, changing this doesn't change the dynamic type of the object.
1932 	string tagName;
1933 
1934 	/// This is where the attributes are actually stored. You should use getAttribute, setAttribute, and hasAttribute instead.
1935 	string[string] attributes;
1936 
1937 	/// 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.
1938 	/// Instead, this flag tells if it should be. It is based on the source document's notation and a html element list.
1939 	private bool selfClosed;
1940 
1941 	private DomParent parent_;
1942 
1943 	/// Get the parent Document object that contains this element.
1944 	/// It may be null, so remember to check for that.
1945 	@property inout(Document) parentDocument() inout {
1946 		if(this.parent_ is null)
1947 			return null;
1948 		auto p = cast() this.parent_.asElement;
1949 		auto prev = cast() this;
1950 		while(p) {
1951 			prev = p;
1952 			if(p.parent_ is null)
1953 				return null;
1954 			p = cast() p.parent_.asElement;
1955 		}
1956 		return cast(inout) prev.parent_.asDocument;
1957 	}
1958 
1959 	deprecated @property void parentDocument(Document doc) {
1960 		parent_ = doc;
1961 	}
1962 
1963 	///.
1964 	inout(Element) parentNode() inout {
1965 		if(parent_ is null)
1966 			return null;
1967 
1968 		auto p = parent_.asElement;
1969 
1970 		if(cast(DocumentFragment) p) {
1971 			if(p.parent_ is null)
1972 				return null;
1973 			else
1974 				return p.parent_.asElement;
1975 		}
1976 
1977 		return p;
1978 	}
1979 
1980 	//protected
1981 	Element parentNode(Element e) {
1982 		parent_ = e;
1983 		return e;
1984 	}
1985 
1986 	// these are here for event handlers. Don't forget that this library never fires events.
1987 	// (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.)
1988 
1989 	version(dom_with_events) {
1990 		EventHandler[][string] bubblingEventHandlers;
1991 		EventHandler[][string] capturingEventHandlers;
1992 		EventHandler[string] defaultEventHandlers;
1993 
1994 		void addEventListener(string event, EventHandler handler, bool useCapture = false) {
1995 			if(event.length > 2 && event[0..2] == "on")
1996 				event = event[2 .. $];
1997 
1998 			if(useCapture)
1999 				capturingEventHandlers[event] ~= handler;
2000 			else
2001 				bubblingEventHandlers[event] ~= handler;
2002 		}
2003 	}
2004 
2005 
2006 	// and now methods
2007 
2008 	/++
2009 		Convenience function to try to do the right thing for HTML. This is the main way I create elements.
2010 
2011 		History:
2012 			On February 8, 2021, the `selfClosedElements` parameter was added. Previously, it used a private
2013 			immutable global list for HTML. It still defaults to the same list, but you can change it now via
2014 			the parameter.
2015 	+/
2016 	static Element make(string tagName, string childInfo = null, string childInfo2 = null, const string[] selfClosedElements = htmlSelfClosedElements) {
2017 		bool selfClosed = tagName.isInArray(selfClosedElements);
2018 
2019 		Element e;
2020 		// want to create the right kind of object for the given tag...
2021 		switch(tagName) {
2022 			case "#text":
2023 				e = new TextNode(null, childInfo);
2024 				return e;
2025 			// break;
2026 			case "table":
2027 				e = new Table(null);
2028 			break;
2029 			case "a":
2030 				e = new Link(null);
2031 			break;
2032 			case "form":
2033 				e = new Form(null);
2034 			break;
2035 			case "tr":
2036 				e = new TableRow(null);
2037 			break;
2038 			case "td", "th":
2039 				e = new TableCell(null, tagName);
2040 			break;
2041 			default:
2042 				e = new Element(null, tagName, null, selfClosed); // parent document should be set elsewhere
2043 		}
2044 
2045 		// make sure all the stuff is constructed properly FIXME: should probably be in all the right constructors too
2046 		e.tagName = tagName;
2047 		e.selfClosed = selfClosed;
2048 
2049 		if(childInfo !is null)
2050 			switch(tagName) {
2051 				/* html5 convenience tags */
2052 				case "audio":
2053 					if(childInfo.length)
2054 						e.addChild("source", childInfo);
2055 					if(childInfo2 !is null)
2056 						e.appendText(childInfo2);
2057 				break;
2058 				case "source":
2059 					e.src = childInfo;
2060 					if(childInfo2 !is null)
2061 						e.type = childInfo2;
2062 				break;
2063 				/* regular html 4 stuff */
2064 				case "img":
2065 					e.src = childInfo;
2066 					if(childInfo2 !is null)
2067 						e.alt = childInfo2;
2068 				break;
2069 				case "link":
2070 					e.href = childInfo;
2071 					if(childInfo2 !is null)
2072 						e.rel = childInfo2;
2073 				break;
2074 				case "option":
2075 					e.innerText = childInfo;
2076 					if(childInfo2 !is null)
2077 						e.value = childInfo2;
2078 				break;
2079 				case "input":
2080 					e.type = "hidden";
2081 					e.name = childInfo;
2082 					if(childInfo2 !is null)
2083 						e.value = childInfo2;
2084 				break;
2085 				case "button":
2086 					e.innerText = childInfo;
2087 					if(childInfo2 !is null)
2088 						e.type = childInfo2;
2089 				break;
2090 				case "a":
2091 					e.innerText = childInfo;
2092 					if(childInfo2 !is null)
2093 						e.href = childInfo2;
2094 				break;
2095 				case "script":
2096 				case "style":
2097 					e.innerRawSource = childInfo;
2098 				break;
2099 				case "meta":
2100 					e.name = childInfo;
2101 					if(childInfo2 !is null)
2102 						e.content = childInfo2;
2103 				break;
2104 				/* generically, assume we were passed text and perhaps class */
2105 				default:
2106 					e.innerText = childInfo;
2107 					if(childInfo2.length)
2108 						e.className = childInfo2;
2109 			}
2110 
2111 		return e;
2112 	}
2113 
2114 	static Element make(string tagName, in Html innerHtml, string childInfo2 = null) {
2115 		// FIXME: childInfo2 is ignored when info1 is null
2116 		auto m = Element.make(tagName, "not null"[0..0], childInfo2);
2117 		m.innerHTML = innerHtml.source;
2118 		return m;
2119 	}
2120 
2121 	static Element make(string tagName, Element child, string childInfo2 = null) {
2122 		auto m = Element.make(tagName, cast(string) null, childInfo2);
2123 		m.appendChild(child);
2124 		return m;
2125 	}
2126 
2127 
2128 	/// Generally, you don't want to call this yourself - use Element.make or document.createElement instead.
2129 	this(Document _parentDocument, string _tagName, string[string] _attributes = null, bool _selfClosed = false) {
2130 		tagName = _tagName;
2131 		if(_attributes !is null)
2132 			attributes = _attributes;
2133 		selfClosed = _selfClosed;
2134 
2135 		version(dom_node_indexes)
2136 			this.dataset.nodeIndex = to!string(&(this.attributes));
2137 
2138 		assert(_tagName.indexOf(" ") == -1);//, "<" ~ _tagName ~ "> is invalid");
2139 	}
2140 
2141 	/++
2142 		Convenience constructor when you don't care about the parentDocument. Note this might break things on the document.
2143 		Note also that without a parent document, elements are always in strict, case-sensitive mode.
2144 
2145 		History:
2146 			On February 8, 2021, the `selfClosedElements` parameter was added. It defaults to the same behavior as
2147 			before: using the hard-coded list of HTML elements, but it can now be overridden. If you use
2148 			[Document.createElement], it will use the list set for the current document. Otherwise, you can pass
2149 			something here if you like.
2150 	+/
2151 	this(string _tagName, string[string] _attributes = null, const string[] selfClosedElements = htmlSelfClosedElements) {
2152 		tagName = _tagName;
2153 		if(_attributes !is null)
2154 			attributes = _attributes;
2155 		selfClosed = tagName.isInArray(selfClosedElements);
2156 
2157 		// this is meant to reserve some memory. It makes a small, but consistent improvement.
2158 		//children.length = 8;
2159 		//children.length = 0;
2160 
2161 		version(dom_node_indexes)
2162 			this.dataset.nodeIndex = to!string(&(this.attributes));
2163 	}
2164 
2165 	private this(Document _parentDocument) {
2166 		version(dom_node_indexes)
2167 			this.dataset.nodeIndex = to!string(&(this.attributes));
2168 	}
2169 
2170 
2171 	/* *******************************
2172 	       Navigating the DOM
2173 	*********************************/
2174 
2175 	/// Returns the first child of this element. If it has no children, returns null.
2176 	/// Remember, text nodes are children too.
2177 	@property Element firstChild() {
2178 		return children.length ? children[0] : null;
2179 	}
2180 
2181 	///
2182 	@property Element lastChild() {
2183 		return children.length ? children[$ - 1] : null;
2184 	}
2185 	
2186 	/// UNTESTED
2187 	/// the next element you would encounter if you were reading it in the source
2188 	Element nextInSource() {
2189 		auto n = firstChild;
2190 		if(n is null)
2191 			n = nextSibling();
2192 		if(n is null) {
2193 			auto p = this.parentNode;
2194 			while(p !is null && n is null) {
2195 				n = p.nextSibling;
2196 			}
2197 		}
2198 
2199 		return n;
2200 	}
2201 
2202 	/// UNTESTED
2203 	/// ditto
2204 	Element previousInSource() {
2205 		auto p = previousSibling;
2206 		if(p is null) {
2207 			auto par = parentNode;
2208 			if(par)
2209 				p = par.lastChild;
2210 			if(p is null)
2211 				p = par;
2212 		}
2213 		return p;
2214 	}
2215 
2216 	///.
2217 	@property Element previousElementSibling() {
2218 		return previousSibling("*");
2219 	}
2220 
2221 	///.
2222 	@property Element previousSibling(string tagName = null) {
2223 		if(this.parentNode is null)
2224 			return null;
2225 		Element ps = null;
2226 		foreach(e; this.parentNode.childNodes) {
2227 			if(e is this)
2228 				break;
2229 			if(tagName == "*" && e.nodeType != NodeType.Text) {
2230 				ps = e;
2231 			} else if(tagName is null || e.tagName == tagName)
2232 				ps = e;
2233 		}
2234 
2235 		return ps;
2236 	}
2237 
2238 	///.
2239 	@property Element nextElementSibling() {
2240 		return nextSibling("*");
2241 	}
2242 
2243 	///.
2244 	@property Element nextSibling(string tagName = null) {
2245 		if(this.parentNode is null)
2246 			return null;
2247 		Element ns = null;
2248 		bool mightBe = false;
2249 		foreach(e; this.parentNode.childNodes) {
2250 			if(e is this) {
2251 				mightBe = true;
2252 				continue;
2253 			}
2254 			if(mightBe) {
2255 				if(tagName == "*" && e.nodeType != NodeType.Text) {
2256 					ns = e;
2257 					break;
2258 				}
2259 				if(tagName is null || e.tagName == tagName) {
2260 					ns = e;
2261 					break;
2262 				}
2263 			}
2264 		}
2265 
2266 		return ns;
2267 	}
2268 
2269 
2270 	/// Gets the nearest node, going up the chain, with the given tagName
2271 	/// May return null or throw.
2272 	T getParent(T = Element)(string tagName = null) if(is(T : Element)) {
2273 		if(tagName is null) {
2274 			static if(is(T == Form))
2275 				tagName = "form";
2276 			else static if(is(T == Table))
2277 				tagName = "table";
2278 			else static if(is(T == Link))
2279 				tagName == "a";
2280 		}
2281 
2282 		auto par = this.parentNode;
2283 		while(par !is null) {
2284 			if(tagName is null || par.tagName == tagName)
2285 				break;
2286 			par = par.parentNode;
2287 		}
2288 
2289 		static if(!is(T == Element)) {
2290 			auto t = cast(T) par;
2291 			if(t is null)
2292 				throw new ElementNotFoundException("", tagName ~ " parent not found", this);
2293 		} else
2294 			auto t = par;
2295 
2296 		return t;
2297 	}
2298 
2299 	///.
2300 	Element getElementById(string id) {
2301 		// FIXME: I use this function a lot, and it's kinda slow
2302 		// not terribly slow, but not great.
2303 		foreach(e; tree)
2304 			if(e.id == id)
2305 				return e;
2306 		return null;
2307 	}
2308 
2309 	/++
2310 		Returns a child element that matches the given `selector`.
2311 
2312 		Note: you can give multiple selectors, separated by commas.
2313 	 	It will return the first match it finds.
2314 
2315 		Tip: to use namespaces, escape the colon in the name:
2316 
2317 		---
2318 			element.querySelector(`ns\:tag`); // the backticks are raw strings then the backslash is interpreted by querySelector
2319 		---
2320 	+/
2321 	@scriptable
2322 	Element querySelector(string selector) {
2323 		Selector s = Selector(selector);
2324 		foreach(ele; tree)
2325 			if(s.matchesElement(ele))
2326 				return ele;
2327 		return null;
2328 	}
2329 
2330 	/// a more standards-compliant alias for getElementsBySelector
2331 	@scriptable
2332 	Element[] querySelectorAll(string selector) {
2333 		return getElementsBySelector(selector);
2334 	}
2335 
2336 	/// If the element matches the given selector. Previously known as `matchesSelector`.
2337 	@scriptable
2338 	bool matches(string selector) {
2339 		/+
2340 		bool caseSensitiveTags = true;
2341 		if(parentDocument && parentDocument.loose)
2342 			caseSensitiveTags = false;
2343 		+/
2344 
2345 		Selector s = Selector(selector);
2346 		return s.matchesElement(this);
2347 	}
2348 
2349 	/// Returns itself or the closest parent that matches the given selector, or null if none found
2350 	/// See_also: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
2351 	@scriptable
2352 	Element closest(string selector) {
2353 		Element e = this;
2354 		while(e !is null) {
2355 			if(e.matches(selector))
2356 				return e;
2357 			e = e.parentNode;
2358 		}
2359 		return null;
2360 	}
2361 
2362 	/**
2363 		Returns elements that match the given CSS selector
2364 
2365 		* -- all, default if nothing else is there
2366 
2367 		tag#id.class.class.class:pseudo[attrib=what][attrib=what] OP selector
2368 
2369 		It is all additive
2370 
2371 		OP
2372 
2373 		space = descendant
2374 		>     = direct descendant
2375 		+     = sibling (E+F Matches any F element immediately preceded by a sibling element E)
2376 
2377 		[foo]        Foo is present as an attribute
2378 		[foo="warning"]   Matches any E element whose "foo" attribute value is exactly equal to "warning".
2379 		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"
2380 		E[lang|="en"] Matches any E element whose "lang" attribute has a hyphen-separated list of values beginning (from the left) with "en".
2381 
2382 		[item$=sdas] ends with
2383 		[item^-sdsad] begins with
2384 
2385 		Quotes are optional here.
2386 
2387 		Pseudos:
2388 			:first-child
2389 			:last-child
2390 			:link (same as a[href] for our purposes here)
2391 
2392 
2393 		There can be commas separating the selector. A comma separated list result is OR'd onto the main.
2394 
2395 
2396 
2397 		This ONLY cares about elements. text, etc, are ignored
2398 
2399 
2400 		There should be two functions: given element, does it match the selector? and given a selector, give me all the elements
2401 	*/
2402 	Element[] getElementsBySelector(string selector) {
2403 		// FIXME: this function could probably use some performance attention
2404 		// ... but only mildly so according to the profiler in the big scheme of things; probably negligible in a big app.
2405 
2406 
2407 		bool caseSensitiveTags = true;
2408 		if(parentDocument && parentDocument.loose)
2409 			caseSensitiveTags = false;
2410 
2411 		Element[] ret;
2412 		foreach(sel; parseSelectorString(selector, caseSensitiveTags))
2413 			ret ~= sel.getElements(this);
2414 		return ret;
2415 	}
2416 
2417 	/// .
2418 	Element[] getElementsByClassName(string cn) {
2419 		// is this correct?
2420 		return getElementsBySelector("." ~ cn);
2421 	}
2422 
2423 	///.
2424 	Element[] getElementsByTagName(string tag) {
2425 		if(parentDocument && parentDocument.loose)
2426 			tag = tag.toLower();
2427 		Element[] ret;
2428 		foreach(e; tree)
2429 			if(e.tagName == tag)
2430 				ret ~= e;
2431 		return ret;
2432 	}
2433 
2434 
2435 	/* *******************************
2436 	          Attributes
2437 	*********************************/
2438 
2439 	/**
2440 		Gets the given attribute value, or null if the
2441 		attribute is not set.
2442 
2443 		Note that the returned string is decoded, so it no longer contains any xml entities.
2444 	*/
2445 	@scriptable
2446 	string getAttribute(string name) const {
2447 		if(parentDocument && parentDocument.loose)
2448 			name = name.toLower();
2449 		auto e = name in attributes;
2450 		if(e)
2451 			return *e;
2452 		else
2453 			return null;
2454 	}
2455 
2456 	/**
2457 		Sets an attribute. Returns this for easy chaining
2458 	*/
2459 	@scriptable
2460 	Element setAttribute(string name, string value) {
2461 		if(parentDocument && parentDocument.loose)
2462 			name = name.toLower();
2463 
2464 		// I never use this shit legitimately and neither should you
2465 		auto it = name.toLower();
2466 		if(it == "href" || it == "src") {
2467 			auto v = value.strip().toLower();
2468 			if(v.startsWith("vbscript:"))
2469 				value = value[9..$];
2470 			if(v.startsWith("javascript:"))
2471 				value = value[11..$];
2472 		}
2473 
2474 		attributes[name] = value;
2475 
2476 		sendObserverEvent(DomMutationOperations.setAttribute, name, value);
2477 
2478 		return this;
2479 	}
2480 
2481 	/**
2482 		Returns if the attribute exists.
2483 	*/
2484 	@scriptable
2485 	bool hasAttribute(string name) {
2486 		if(parentDocument && parentDocument.loose)
2487 			name = name.toLower();
2488 
2489 		if(name in attributes)
2490 			return true;
2491 		else
2492 			return false;
2493 	}
2494 
2495 	/**
2496 		Removes the given attribute from the element.
2497 	*/
2498 	@scriptable
2499 	Element removeAttribute(string name)
2500 	out(ret) {
2501 		assert(ret is this);
2502 	}
2503 	do {
2504 		if(parentDocument && parentDocument.loose)
2505 			name = name.toLower();
2506 		if(name in attributes)
2507 			attributes.remove(name);
2508 
2509 		sendObserverEvent(DomMutationOperations.removeAttribute, name);
2510 		return this;
2511 	}
2512 
2513 	/**
2514 		Gets the class attribute's contents. Returns
2515 		an empty string if it has no class.
2516 	*/
2517 	@property string className() const {
2518 		auto c = getAttribute("class");
2519 		if(c is null)
2520 			return "";
2521 		return c;
2522 	}
2523 
2524 	///.
2525 	@property Element className(string c) {
2526 		setAttribute("class", c);
2527 		return this;
2528 	}
2529 
2530 	/**
2531 		Provides easy access to common HTML attributes, object style.
2532 
2533 		---
2534 		auto element = Element.make("a");
2535 		a.href = "cool.html"; // this is the same as a.setAttribute("href", "cool.html");
2536 		string where = a.href; // same as a.getAttribute("href");
2537 		---
2538 
2539 	*/
2540 	@property string opDispatch(string name)(string v = null) if(isConvenientAttribute(name)) {
2541 		if(v !is null)
2542 			setAttribute(name, v);
2543 		return getAttribute(name);
2544 	}
2545 
2546 	/**
2547 		Old access to attributes. Use [attrs] instead.
2548 
2549 		DEPRECATED: generally open opDispatch caused a lot of unforeseen trouble with compile time duck typing and UFCS extensions.
2550 		so I want to remove it. A small whitelist of attributes is still allowed, but others are not.
2551 		
2552 		Instead, use element.attrs.attribute, element.attrs["attribute"],
2553 		or element.getAttribute("attribute")/element.setAttribute("attribute").
2554 	*/
2555 	@property string opDispatch(string name)(string v = null) if(!isConvenientAttribute(name)) {
2556 		static assert(0, "Don't use " ~ name ~ " direct on Element, instead use element.attrs.attributeName");
2557 	}
2558 
2559 	/*
2560 	// this would be nice for convenience, but it broke the getter above.
2561 	@property void opDispatch(string name)(bool boolean) if(name != "popFront") {
2562 		if(boolean)
2563 			setAttribute(name, name);
2564 		else
2565 			removeAttribute(name);
2566 	}
2567 	*/
2568 
2569 	/**
2570 		Returns the element's children.
2571 	*/
2572 	@property const(Element[]) childNodes() const {
2573 		return children;
2574 	}
2575 
2576 	/// Mutable version of the same
2577 	@property Element[] childNodes() { // FIXME: the above should be inout
2578 		return children;
2579 	}
2580 
2581 	/++
2582 		HTML5's dataset property. It is an alternate view into attributes with the data- prefix.
2583 		Given `<a data-my-property="cool" />`, we get `assert(a.dataset.myProperty == "cool");`
2584 	+/
2585 	@property DataSet dataset() {
2586 		return DataSet(this);
2587 	}
2588 
2589 	/++
2590 		Gives dot/opIndex access to attributes
2591 		---
2592 		ele.attrs.largeSrc = "foo"; // same as ele.setAttribute("largeSrc", "foo")
2593 		---
2594 	+/
2595 	@property AttributeSet attrs() {
2596 		return AttributeSet(this);
2597 	}
2598 
2599 	/++
2600 		Provides both string and object style (like in Javascript) access to the style attribute.
2601 
2602 		---
2603 		element.style.color = "red"; // translates into setting `color: red;` in the `style` attribute
2604 		---
2605 	+/
2606 	@property ElementStyle style() {
2607 		return ElementStyle(this);
2608 	}
2609 
2610 	/++
2611 		This sets the style attribute with a string.
2612 	+/
2613 	@property ElementStyle style(string s) {
2614 		this.setAttribute("style", s);
2615 		return this.style;
2616 	}
2617 
2618 	private void parseAttributes(string[] whichOnes = null) {
2619 /+
2620 		if(whichOnes is null)
2621 			whichOnes = attributes.keys;
2622 		foreach(attr; whichOnes) {
2623 			switch(attr) {
2624 				case "id":
2625 
2626 				break;
2627 				case "class":
2628 
2629 				break;
2630 				case "style":
2631 
2632 				break;
2633 				default:
2634 					// we don't care about it
2635 			}
2636 		}
2637 +/
2638 	}
2639 
2640 
2641 	// 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.
2642 
2643 	// the next few methods are for implementing interactive kind of things
2644 	private CssStyle _computedStyle;
2645 
2646 	/// Don't use this.
2647 	@property CssStyle computedStyle() {
2648 		if(_computedStyle is null) {
2649 			auto style = this.getAttribute("style");
2650 		/* we'll treat shitty old html attributes as css here */
2651 			if(this.hasAttribute("width"))
2652 				style ~= "; width: " ~ this.attrs.width;
2653 			if(this.hasAttribute("height"))
2654 				style ~= "; height: " ~ this.attrs.height;
2655 			if(this.hasAttribute("bgcolor"))
2656 				style ~= "; background-color: " ~ this.attrs.bgcolor;
2657 			if(this.tagName == "body" && this.hasAttribute("text"))
2658 				style ~= "; color: " ~ this.attrs.text;
2659 			if(this.hasAttribute("color"))
2660 				style ~= "; color: " ~ this.attrs.color;
2661 		/* done */
2662 
2663 
2664 			_computedStyle = new CssStyle(null, style); // gives at least something to work with
2665 		}
2666 		return _computedStyle;
2667 	}
2668 
2669 	/// These properties are useless in most cases, but if you write a layout engine on top of this lib, they may be good
2670 	version(browser) {
2671 		void* expansionHook; ///ditto
2672 		int offsetWidth; ///ditto
2673 		int offsetHeight; ///ditto
2674 		int offsetLeft; ///ditto
2675 		int offsetTop; ///ditto
2676 		Element offsetParent; ///ditto
2677 		bool hasLayout; ///ditto
2678 		int zIndex; ///ditto
2679 
2680 		///ditto
2681 		int absoluteLeft() {
2682 			int a = offsetLeft;
2683 			auto p = offsetParent;
2684 			while(p) {
2685 				a += p.offsetLeft;
2686 				p = p.offsetParent;
2687 			}
2688 
2689 			return a;
2690 		}
2691 
2692 		///ditto
2693 		int absoluteTop() {
2694 			int a = offsetTop;
2695 			auto p = offsetParent;
2696 			while(p) {
2697 				a += p.offsetTop;
2698 				p = p.offsetParent;
2699 			}
2700 
2701 			return a;
2702 		}
2703 	}
2704 
2705 	// Back to the regular dom functions
2706 
2707     public:
2708 
2709 
2710 	/* *******************************
2711 	          DOM Mutation
2712 	*********************************/
2713 
2714 	/// Removes all inner content from the tag; all child text and elements are gone.
2715 	void removeAllChildren()
2716 		out {
2717 			assert(this.children.length == 0);
2718 		}
2719 	do {
2720 		foreach(child; children)
2721 			child.parentNode = null;
2722 		children = null;
2723 	}
2724 
2725 	/// History: added June 13, 2020
2726 	Element appendSibling(Element e) {
2727 		parentNode.insertAfter(this, e);
2728 		return e;
2729 	}
2730 
2731 	/// History: added June 13, 2020
2732 	Element prependSibling(Element e) {
2733 		parentNode.insertBefore(this, e);
2734 		return e;
2735 	}
2736 
2737 
2738     	/++
2739 		Appends the given element to this one. If it already has a parent, it is removed from that tree and moved to this one.
2740 
2741 		See_also: https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild
2742 
2743 		History:
2744 			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.
2745 	+/
2746 	Element appendChild(Element e)
2747 		in {
2748 			assert(e !is null);
2749 		}
2750 		out (ret) {
2751 			assert((cast(DocumentFragment) this !is null) || (e.parentNode is this), e.toString);// e.parentNode ? e.parentNode.toString : "null");
2752 			assert(e.parentDocument is this.parentDocument);
2753 			assert(e is ret);
2754 		}
2755 	do {
2756 		if(e.parentNode !is null)
2757 			e.parentNode.removeChild(e);
2758 
2759 		selfClosed = false;
2760 		if(auto frag = cast(DocumentFragment) e)
2761 			children ~= frag.children;
2762 		else
2763 			children ~= e;
2764 
2765 		e.parentNode = this;
2766 
2767 		/+
2768 		foreach(item; e.tree)
2769 			item.parentDocument = this.parentDocument;
2770 		+/
2771 
2772 		sendObserverEvent(DomMutationOperations.appendChild, null, null, e);
2773 
2774 		return e;
2775 	}
2776 
2777 	/// Inserts the second element to this node, right before the first param
2778 	Element insertBefore(in Element where, Element what)
2779 		in {
2780 			assert(where !is null);
2781 			assert(where.parentNode is this);
2782 			assert(what !is null);
2783 			assert(what.parentNode is null);
2784 		}
2785 		out (ret) {
2786 			assert(where.parentNode is this);
2787 			assert(what.parentNode is this);
2788 
2789 			assert(what.parentDocument is this.parentDocument);
2790 			assert(ret is what);
2791 		}
2792 	do {
2793 		foreach(i, e; children) {
2794 			if(e is where) {
2795 				if(auto frag = cast(DocumentFragment) what) {
2796 					children = children[0..i] ~ frag.children ~ children[i..$];
2797 					foreach(child; frag.children)
2798 						child.parentNode = this;
2799 				} else {
2800 					children = children[0..i] ~ what ~ children[i..$];
2801 				}
2802 				what.parentNode = this;
2803 				return what;
2804 			}
2805 		}
2806 
2807 		return what;
2808 
2809 		assert(0);
2810 	}
2811 
2812 	/++
2813 		Inserts the given element `what` as a sibling of the `this` element, after the element `where` in the parent node.
2814 	+/
2815 	Element insertAfter(in Element where, Element what)
2816 		in {
2817 			assert(where !is null);
2818 			assert(where.parentNode is this);
2819 			assert(what !is null);
2820 			assert(what.parentNode is null);
2821 		}
2822 		out (ret) {
2823 			assert(where.parentNode is this);
2824 			assert(what.parentNode is this);
2825 			assert(what.parentDocument is this.parentDocument);
2826 			assert(ret is what);
2827 		}
2828 	do {
2829 		foreach(i, e; children) {
2830 			if(e is where) {
2831 				if(auto frag = cast(DocumentFragment) what) {
2832 					children = children[0 .. i + 1] ~ what.children ~ children[i + 1 .. $];
2833 					foreach(child; frag.children)
2834 						child.parentNode = this;
2835 				} else
2836 					children = children[0 .. i + 1] ~ what ~ children[i + 1 .. $];
2837 				what.parentNode = this;
2838 				return what;
2839 			}
2840 		}
2841 
2842 		return what;
2843 
2844 		assert(0);
2845 	}
2846 
2847 	/// swaps one child for a new thing. Returns the old child which is now parentless.
2848 	Element swapNode(Element child, Element replacement)
2849 		in {
2850 			assert(child !is null);
2851 			assert(replacement !is null);
2852 			assert(child.parentNode is this);
2853 		}
2854 		out(ret) {
2855 			assert(ret is child);
2856 			assert(ret.parentNode is null);
2857 			assert(replacement.parentNode is this);
2858 			assert(replacement.parentDocument is this.parentDocument);
2859 		}
2860 	do {
2861 		foreach(ref c; this.children)
2862 			if(c is child) {
2863 				c.parentNode = null;
2864 				c = replacement;
2865 				c.parentNode = this;
2866 				return child;
2867 			}
2868 		assert(0);
2869 	}
2870 
2871 
2872 	/++
2873 		Appends the given to the node.
2874 
2875 
2876 		Calling `e.appendText(" hi")` on `<example>text <b>bold</b></example>`
2877 		yields `<example>text <b>bold</b> hi</example>`.
2878 
2879 		See_Also:
2880 			[firstInnerText], [directText], [innerText], [appendChild]
2881 	+/
2882 	@scriptable
2883 	Element appendText(string text) {
2884 		Element e = new TextNode(parentDocument, text);
2885 		appendChild(e);
2886 		return this;
2887 	}
2888 
2889 	/++
2890 		Returns child elements which are of a tag type (excludes text, comments, etc.).
2891 
2892 
2893 		childElements of `<example>text <b>bold</b></example>` is just the `<b>` tag.
2894 
2895 		Params:
2896 			tagName = filter results to only the child elements with the given tag name.
2897 	+/
2898 	@property Element[] childElements(string tagName = null) {
2899 		Element[] ret;
2900 		foreach(c; children)
2901 			if(c.nodeType == 1 && (tagName is null || c.tagName == tagName))
2902 				ret ~= c;
2903 		return ret;
2904 	}
2905 
2906 	/++
2907 		Appends the given html to the element, returning the elements appended
2908 
2909 
2910 		This is similar to `element.innerHTML += "html string";` in Javascript.
2911 	+/
2912 	@scriptable
2913 	Element[] appendHtml(string html) {
2914 		Document d = new Document("<root>" ~ html ~ "</root>");
2915 		return stealChildren(d.root);
2916 	}
2917 
2918 
2919 	///.
2920 	void insertChildAfter(Element child, Element where)
2921 		in {
2922 			assert(child !is null);
2923 			assert(where !is null);
2924 			assert(where.parentNode is this);
2925 			assert(!selfClosed);
2926 			//assert(isInArray(where, children));
2927 		}
2928 		out {
2929 			assert(child.parentNode is this);
2930 			assert(where.parentNode is this);
2931 			//assert(isInArray(where, children));
2932 			//assert(isInArray(child, children));
2933 		}
2934 	do {
2935 		foreach(ref i, c; children) {
2936 			if(c is where) {
2937 				i++;
2938 				if(auto frag = cast(DocumentFragment) child) {
2939 					children = children[0..i] ~ child.children ~ children[i..$];
2940 					//foreach(child; frag.children)
2941 						//child.parentNode = this;
2942 				} else
2943 					children = children[0..i] ~ child ~ children[i..$];
2944 				child.parentNode = this;
2945 				break;
2946 			}
2947 		}
2948 	}
2949 
2950 	/++
2951 		Reparents all the child elements of `e` to `this`, leaving `e` childless.
2952 
2953 		Params:
2954 			e = the element whose children you want to steal
2955 			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.
2956 	+/
2957 	Element[] stealChildren(Element e, Element position = null)
2958 		in {
2959 			assert(!selfClosed);
2960 			assert(e !is null);
2961 			//if(position !is null)
2962 				//assert(isInArray(position, children));
2963 		}
2964 		out (ret) {
2965 			assert(e.children.length == 0);
2966 			// all the parentNode is this checks fail because DocumentFragments do not appear in the parent tree, they are invisible...
2967 			version(none)
2968 			debug foreach(child; ret) {
2969 				assert(child.parentNode is this);
2970 				assert(child.parentDocument is this.parentDocument);
2971 			}
2972 		}
2973 	do {
2974 		foreach(c; e.children) {
2975 			c.parentNode = this;
2976 		}
2977 		if(position is null)
2978 			children ~= e.children;
2979 		else {
2980 			foreach(i, child; children) {
2981 				if(child is position) {
2982 					children = children[0..i] ~
2983 						e.children ~
2984 						children[i..$];
2985 					break;
2986 				}
2987 			}
2988 		}
2989 
2990 		auto ret = e.children[];
2991 		e.children.length = 0;
2992 
2993 		return ret;
2994 	}
2995 
2996     	/// Puts the current element first in our children list. The given element must not have a parent already.
2997 	Element prependChild(Element e)
2998 		in {
2999 			assert(e.parentNode is null);
3000 			assert(!selfClosed);
3001 		}
3002 		out {
3003 			assert(e.parentNode is this);
3004 			assert(e.parentDocument is this.parentDocument);
3005 			assert(children[0] is e);
3006 		}
3007 	do {
3008 		if(auto frag = cast(DocumentFragment) e) {
3009 			children = e.children ~ children;
3010 			foreach(child; frag.children)
3011 				child.parentNode = this;
3012 		} else
3013 			children = e ~ children;
3014 		e.parentNode = this;
3015 		return e;
3016 	}
3017 
3018 
3019 	/**
3020 		Returns a string containing all child elements, formatted such that it could be pasted into
3021 		an XML file.
3022 	*/
3023 	@property string innerHTML(Appender!string where = appender!string()) const {
3024 		if(children is null)
3025 			return "";
3026 
3027 		auto start = where.data.length;
3028 
3029 		foreach(child; children) {
3030 			assert(child !is null);
3031 
3032 			child.writeToAppender(where);
3033 		}
3034 
3035 		return where.data[start .. $];
3036 	}
3037 
3038 	/**
3039 		Takes some html and replaces the element's children with the tree made from the string.
3040 	*/
3041 	@property Element innerHTML(string html, bool strict = false) {
3042 		if(html.length)
3043 			selfClosed = false;
3044 
3045 		if(html.length == 0) {
3046 			// I often say innerHTML = ""; as a shortcut to clear it out,
3047 			// so let's optimize that slightly.
3048 			removeAllChildren();
3049 			return this;
3050 		}
3051 
3052 		auto doc = new Document();
3053 		doc.parseUtf8("<innerhtml>" ~ html ~ "</innerhtml>", strict, strict); // FIXME: this should preserve the strictness of the parent document
3054 
3055 		children = doc.root.children;
3056 		foreach(c; children) {
3057 			c.parentNode = this;
3058 		}
3059 
3060 		doc.root.children = null;
3061 
3062 		return this;
3063 	}
3064 
3065 	/// ditto
3066 	@property Element innerHTML(Html html) {
3067 		return this.innerHTML = html.source;
3068 	}
3069 
3070 	/**
3071 		Replaces this node with the given html string, which is parsed
3072 
3073 		Note: this invalidates the this reference, since it is removed
3074 		from the tree.
3075 
3076 		Returns the new children that replace this.
3077 	*/
3078 	@property Element[] outerHTML(string html) {
3079 		auto doc = new Document();
3080 		doc.parseUtf8("<innerhtml>" ~ html ~ "</innerhtml>"); // FIXME: needs to preserve the strictness
3081 
3082 		children = doc.root.children;
3083 		foreach(c; children) {
3084 			c.parentNode = this;
3085 		}
3086 
3087 		stripOut();
3088 
3089 		return doc.root.children;
3090 	}
3091 
3092 	/++
3093 		Returns all the html for this element, including the tag itself.
3094 
3095 		This is equivalent to calling toString().
3096 	+/
3097 	@property string outerHTML() {
3098 		return this.toString();
3099 	}
3100 
3101 	/// This sets the inner content of the element *without* trying to parse it.
3102 	/// You can inject any code in there; this serves as an escape hatch from the dom.
3103 	///
3104 	/// The only times you might actually need it are for < style > and < script > tags in html.
3105 	/// Other than that, innerHTML and/or innerText should do the job.
3106 	@property void innerRawSource(string rawSource) {
3107 		children.length = 0;
3108 		auto rs = new RawSource(parentDocument, rawSource);
3109 		children ~= rs;
3110 		rs.parentNode = this;
3111 	}
3112 
3113 	///.
3114 	Element replaceChild(Element find, Element replace)
3115 		in {
3116 			assert(find !is null);
3117 			assert(find.parentNode is this);
3118 			assert(replace !is null);
3119 			assert(replace.parentNode is null);
3120 		}
3121 		out(ret) {
3122 			assert(ret is replace);
3123 			assert(replace.parentNode is this);
3124 			assert(replace.parentDocument is this.parentDocument);
3125 			assert(find.parentNode is null);
3126 		}
3127 	do {
3128 		// FIXME
3129 		//if(auto frag = cast(DocumentFragment) replace)
3130 			//return this.replaceChild(frag, replace.children);
3131 		for(int i = 0; i < children.length; i++) {
3132 			if(children[i] is find) {
3133 				replace.parentNode = this;
3134 				children[i].parentNode = null;
3135 				children[i] = replace;
3136 				return replace;
3137 			}
3138 		}
3139 
3140 		throw new Exception("no such child ");// ~  find.toString ~ " among " ~ typeid(this).toString);//.toString ~ " magic \n\n\n" ~ find.parentNode.toString);
3141 	}
3142 
3143 	/**
3144 		Replaces the given element with a whole group.
3145 	*/
3146 	void replaceChild(Element find, Element[] replace)
3147 		in {
3148 			assert(find !is null);
3149 			assert(replace !is null);
3150 			assert(find.parentNode is this);
3151 			debug foreach(r; replace)
3152 				assert(r.parentNode is null);
3153 		}
3154 		out {
3155 			assert(find.parentNode is null);
3156 			assert(children.length >= replace.length);
3157 			debug foreach(child; children)
3158 				assert(child !is find);
3159 			debug foreach(r; replace)
3160 				assert(r.parentNode is this);
3161 		}
3162 	do {
3163 		if(replace.length == 0) {
3164 			removeChild(find);
3165 			return;
3166 		}
3167 		assert(replace.length);
3168 		for(int i = 0; i < children.length; i++) {
3169 			if(children[i] is find) {
3170 				children[i].parentNode = null; // this element should now be dead
3171 				children[i] = replace[0];
3172 				foreach(e; replace) {
3173 					e.parentNode = this;
3174 				}
3175 
3176 				children = .insertAfter(children, i, replace[1..$]);
3177 
3178 				return;
3179 			}
3180 		}
3181 
3182 		throw new Exception("no such child");
3183 	}
3184 
3185 
3186 	/**
3187 		Removes the given child from this list.
3188 
3189 		Returns the removed element.
3190 	*/
3191 	Element removeChild(Element c)
3192 		in {
3193 			assert(c !is null);
3194 			assert(c.parentNode is this);
3195 		}
3196 		out {
3197 			debug foreach(child; children)
3198 				assert(child !is c);
3199 			assert(c.parentNode is null);
3200 		}
3201 	do {
3202 		foreach(i, e; children) {
3203 			if(e is c) {
3204 				children = children[0..i] ~ children [i+1..$];
3205 				c.parentNode = null;
3206 				return c;
3207 			}
3208 		}
3209 
3210 		throw new Exception("no such child");
3211 	}
3212 
3213 	/// This removes all the children from this element, returning the old list.
3214 	Element[] removeChildren()
3215 		out (ret) {
3216 			assert(children.length == 0);
3217 			debug foreach(r; ret)
3218 				assert(r.parentNode is null);
3219 		}
3220 	do {
3221 		Element[] oldChildren = children.dup;
3222 		foreach(c; oldChildren)
3223 			c.parentNode = null;
3224 
3225 		children.length = 0;
3226 
3227 		return oldChildren;
3228 	}
3229 
3230 	/**
3231 		Fetch the inside text, with all tags stripped out.
3232 
3233 		<p>cool <b>api</b> &amp; code dude<p>
3234 		innerText of that is "cool api & code dude".
3235 
3236 		This does not match what real innerText does!
3237 		http://perfectionkills.com/the-poor-misunderstood-innerText/
3238 
3239 		It is more like textContent.
3240 	*/
3241 	@scriptable
3242 	@property string innerText() const {
3243 		string s;
3244 		foreach(child; children) {
3245 			if(child.nodeType != NodeType.Text)
3246 				s ~= child.innerText;
3247 			else
3248 				s ~= child.nodeValue();
3249 		}
3250 		return s;
3251 	}
3252 
3253 	///
3254 	alias textContent = innerText;
3255 
3256 	/**
3257 		Sets the inside text, replacing all children. You don't
3258 		have to worry about entity encoding.
3259 	*/
3260 	@scriptable
3261 	@property void innerText(string text) {
3262 		selfClosed = false;
3263 		Element e = new TextNode(parentDocument, text);
3264 		children = [e];
3265 		e.parentNode = this;
3266 	}
3267 
3268 	/**
3269 		Strips this node out of the document, replacing it with the given text
3270 	*/
3271 	@property void outerText(string text) {
3272 		parentNode.replaceChild(this, new TextNode(parentDocument, text));
3273 	}
3274 
3275 	/**
3276 		Same result as innerText; the tag with all inner tags stripped out
3277 	*/
3278 	@property string outerText() const {
3279 		return innerText;
3280 	}
3281 
3282 
3283 	/* *******************************
3284 	          Miscellaneous
3285 	*********************************/
3286 
3287 	/// This is a full clone of the element. Alias for cloneNode(true) now. Don't extend it.
3288 	@property Element cloned()
3289 	/+
3290 		out(ret) {
3291 			// FIXME: not sure why these fail...
3292 			assert(ret.children.length == this.children.length, format("%d %d", ret.children.length, this.children.length));
3293 			assert(ret.tagName == this.tagName);
3294 		}
3295 	do {
3296 	+/
3297 	{
3298 		return this.cloneNode(true);
3299 	}
3300 
3301 	/// 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.
3302 	Element cloneNode(bool deepClone) {
3303 		auto e = Element.make(this.tagName);
3304 		e.attributes = this.attributes.aadup;
3305 		e.selfClosed = this.selfClosed;
3306 
3307 		if(deepClone) {
3308 			foreach(child; children) {
3309 				e.appendChild(child.cloneNode(true));
3310 			}
3311 		}
3312 		
3313 
3314 		return e;
3315 	}
3316 
3317 	/// W3C DOM interface. Only really meaningful on [TextNode] instances, but the interface is present on the base class.
3318 	string nodeValue() const {
3319 		return "";
3320 	}
3321 
3322 	// should return int
3323 	///.
3324 	@property int nodeType() const {
3325 		return 1;
3326 	}
3327 
3328 
3329 	invariant () {
3330 		debug assert(tagName.indexOf(" ") == -1);
3331 
3332 		// commented cuz it gets into recursive pain and eff dat.
3333 		/+
3334 		if(children !is null)
3335 		foreach(child; children) {
3336 		//	assert(parentNode !is null);
3337 			assert(child !is null);
3338 			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));
3339 			assert(child !is this);
3340 			//assert(child !is parentNode);
3341 		}
3342 		+/
3343 
3344 		/+
3345 		// this isn't helping
3346 		if(parent_ && parent_.asElement) {
3347 			bool found = false;
3348 			foreach(child; parent_.asElement.children)
3349 				if(child is this)
3350 					found = true;
3351 			assert(found, format("%s lists %s as parent, but it is not in children", typeid(this), typeid(this.parent_.asElement)));
3352 		}
3353 		+/
3354 
3355 		/+ // only depend on parentNode's accuracy if you shuffle things around and use the top elements - where the contracts guarantee it on out
3356 		if(parentNode !is null) {
3357 			// if you have a parent, you should share the same parentDocument; this is appendChild()'s job
3358 			auto lol = cast(TextNode) this;
3359 			assert(parentDocument is parentNode.parentDocument, lol is null ? this.tagName : lol.contents);
3360 		}
3361 		+/
3362 		//assert(parentDocument !is null); // no more; if it is present, we use it, but it is not required
3363 		// reason is so you can create these without needing a reference to the document
3364 	}
3365 
3366 	/**
3367 		Turns the whole element, including tag, attributes, and children, into a string which could be pasted into
3368 		an XML file.
3369 	*/
3370 	override string toString() const {
3371 		return writeToAppender();
3372 	}
3373 
3374 	protected string toPrettyStringIndent(bool insertComments, int indentationLevel, string indentWith) const {
3375 		if(indentWith is null)
3376 			return null;
3377 		string s;
3378 
3379 		if(insertComments) s ~= "<!--";
3380 		s ~= "\n";
3381 		foreach(indent; 0 .. indentationLevel)
3382 			s ~= indentWith;
3383 		if(insertComments) s ~= "-->";
3384 
3385 		return s;
3386 	}
3387 
3388 	/++
3389 		Writes out with formatting. Be warned: formatting changes the contents. Use ONLY
3390 		for eyeball debugging.
3391 	+/
3392 	string toPrettyString(bool insertComments = false, int indentationLevel = 0, string indentWith = "\t") const {
3393 
3394 		// first step is to concatenate any consecutive text nodes to simplify
3395 		// the white space analysis. this changes the tree! but i'm allowed since
3396 		// the comment always says it changes the comments
3397 		//
3398 		// actually i'm not allowed cuz it is const so i will cheat and lie
3399 		/+
3400 		TextNode lastTextChild = null;
3401 		for(int a = 0; a < this.children.length; a++) {
3402 			auto child = this.children[a];
3403 			if(auto tn = cast(TextNode) child) {
3404 				if(lastTextChild) {
3405 					lastTextChild.contents ~= tn.contents;
3406 					for(int b = a; b < this.children.length - 1; b++)
3407 						this.children[b] = this.children[b + 1];
3408 					this.children = this.children[0 .. $-1];
3409 				} else {
3410 					lastTextChild = tn;
3411 				}
3412 			} else {
3413 				lastTextChild = null;
3414 			}
3415 		}
3416 		+/
3417 
3418 		auto inlineElements = (parentDocument is null ? null : parentDocument.inlineElements);
3419 
3420 		const(Element)[] children;
3421 
3422 		TextNode lastTextChild = null;
3423 		for(int a = 0; a < this.children.length; a++) {
3424 			auto child = this.children[a];
3425 			if(auto tn = cast(const(TextNode)) child) {
3426 				if(lastTextChild !is null) {
3427 					lastTextChild.contents ~= tn.contents;
3428 				} else {
3429 					lastTextChild = new TextNode("");
3430 					lastTextChild.parentNode = cast(Element) this;
3431 					lastTextChild.contents ~= tn.contents;
3432 					children ~= lastTextChild;
3433 				}
3434 			} else {
3435 				lastTextChild = null;
3436 				children ~= child;
3437 			}
3438 		}
3439 
3440 		string s = toPrettyStringIndent(insertComments, indentationLevel, indentWith);
3441 
3442 		s ~= "<";
3443 		s ~= tagName;
3444 
3445 		// i sort these for consistent output. might be more legible
3446 		// but especially it keeps it the same for diff purposes.
3447 		import std.algorithm : sort;
3448 		auto keys = sort(attributes.keys);
3449 		foreach(n; keys) {
3450 			auto v = attributes[n];
3451 			s ~= " ";
3452 			s ~= n;
3453 			s ~= "=\"";
3454 			s ~= htmlEntitiesEncode(v);
3455 			s ~= "\"";
3456 		}
3457 
3458 		if(selfClosed){
3459 			s ~= " />";
3460 			return s;
3461 		}
3462 
3463 		s ~= ">";
3464 
3465 		// for simple `<collection><item>text</item><item>text</item></collection>`, let's
3466 		// just keep them on the same line
3467 		if(tagName.isInArray(inlineElements) || allAreInlineHtml(children, inlineElements)) {
3468 			foreach(child; children) {
3469 				s ~= child.toString();//toPrettyString(false, 0, null);
3470 			}
3471 		} else {
3472 			foreach(child; children) {
3473 				assert(child !is null);
3474 
3475 				s ~= child.toPrettyString(insertComments, indentationLevel + 1, indentWith);
3476 			}
3477 
3478 			s ~= toPrettyStringIndent(insertComments, indentationLevel, indentWith);
3479 		}
3480 
3481 		s ~= "</";
3482 		s ~= tagName;
3483 		s ~= ">";
3484 
3485 		return s;
3486 	}
3487 
3488 	/+
3489 	/// Writes out the opening tag only, if applicable.
3490 	string writeTagOnly(Appender!string where = appender!string()) const {
3491 	+/
3492 
3493 	/// This is the actual implementation used by toString. You can pass it a preallocated buffer to save some time.
3494 	/// Note: the ordering of attributes in the string is undefined.
3495 	/// Returns the string it creates.
3496 	string writeToAppender(Appender!string where = appender!string()) const {
3497 		assert(tagName !is null);
3498 
3499 		where.reserve((this.children.length + 1) * 512);
3500 
3501 		auto start = where.data.length;
3502 
3503 		where.put("<");
3504 		where.put(tagName);
3505 
3506 		import std.algorithm : sort;
3507 		auto keys = sort(attributes.keys);
3508 		foreach(n; keys) {
3509 			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.
3510 			//assert(v !is null);
3511 			where.put(" ");
3512 			where.put(n);
3513 			where.put("=\"");
3514 			htmlEntitiesEncode(v, where);
3515 			where.put("\"");
3516 		}
3517 
3518 		if(selfClosed){
3519 			where.put(" />");
3520 			return where.data[start .. $];
3521 		}
3522 
3523 		where.put('>');
3524 
3525 		innerHTML(where);
3526 
3527 		where.put("</");
3528 		where.put(tagName);
3529 		where.put('>');
3530 
3531 		return where.data[start .. $];
3532 	}
3533 
3534 	/**
3535 		Returns a lazy range of all its children, recursively.
3536 	*/
3537 	@property ElementStream tree() {
3538 		return new ElementStream(this);
3539 	}
3540 
3541 	// I moved these from Form because they are generally useful.
3542 	// Ideally, I'd put them in arsd.html and use UFCS, but that doesn't work with the opDispatch here.
3543 	/// Tags: HTML, HTML5
3544 	// FIXME: add overloads for other label types...
3545 	Element addField(string label, string name, string type = "text", FormFieldOptions fieldOptions = FormFieldOptions.none) {
3546 		auto fs = this;
3547 		auto i = fs.addChild("label");
3548 
3549 		if(!(type == "checkbox" || type == "radio"))
3550 			i.addChild("span", label);
3551 
3552 		Element input;
3553 		if(type == "textarea")
3554 			input = i.addChild("textarea").
3555 			setAttribute("name", name).
3556 			setAttribute("rows", "6");
3557 		else
3558 			input = i.addChild("input").
3559 			setAttribute("name", name).
3560 			setAttribute("type", type);
3561 
3562 		if(type == "checkbox" || type == "radio")
3563 			i.addChild("span", label);
3564 
3565 		// 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.
3566 		fieldOptions.applyToElement(input);
3567 		return i;
3568 	}
3569 
3570 	Element addField(Element label, string name, string type = "text", FormFieldOptions fieldOptions = FormFieldOptions.none) {
3571 		auto fs = this;
3572 		auto i = fs.addChild("label");
3573 		i.addChild(label);
3574 		Element input;
3575 		if(type == "textarea")
3576 			input = i.addChild("textarea").
3577 			setAttribute("name", name).
3578 			setAttribute("rows", "6");
3579 		else
3580 			input = i.addChild("input").
3581 			setAttribute("name", name).
3582 			setAttribute("type", type);
3583 
3584 		// 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.
3585 		fieldOptions.applyToElement(input);
3586 		return i;
3587 	}
3588 
3589 	Element addField(string label, string name, FormFieldOptions fieldOptions) {
3590 		return addField(label, name, "text", fieldOptions);
3591 	}
3592 
3593 	Element addField(string label, string name, string[string] options, FormFieldOptions fieldOptions = FormFieldOptions.none) {
3594 		auto fs = this;
3595 		auto i = fs.addChild("label");
3596 		i.addChild("span", label);
3597 		auto sel = i.addChild("select").setAttribute("name", name);
3598 
3599 		foreach(k, opt; options)
3600 			sel.addChild("option", opt, k);
3601 
3602 		// FIXME: implement requirements somehow
3603 
3604 		return i;
3605 	}
3606 
3607 	Element addSubmitButton(string label = null) {
3608 		auto t = this;
3609 		auto holder = t.addChild("div");
3610 		holder.addClass("submit-holder");
3611 		auto i = holder.addChild("input");
3612 		i.type = "submit";
3613 		if(label.length)
3614 			i.value = label;
3615 		return holder;
3616 	}
3617 
3618 }
3619 // computedStyle could argubaly be removed to bring size down
3620 //pragma(msg, __traits(classInstanceSize, Element));
3621 //pragma(msg, Element.tupleof);
3622 
3623 // FIXME: since Document loosens the input requirements, it should probably be the sub class...
3624 /// Specializes Document for handling generic XML. (always uses strict mode, uses xml mime type and file header)
3625 /// Group: core_functionality
3626 class XmlDocument : Document {
3627 	this(string data) {
3628 		selfClosedElements = null;
3629 		inlineElements = null;
3630 		contentType = "text/xml; charset=utf-8";
3631 		_prolog = `<?xml version="1.0" encoding="UTF-8"?>` ~ "\n";
3632 
3633 		parseStrict(data);
3634 	}
3635 }
3636 
3637 
3638 
3639 
3640 import std.string;
3641 
3642 /* domconvenience follows { */
3643 
3644 /// finds comments that match the given txt. Case insensitive, strips whitespace.
3645 /// Group: core_functionality
3646 Element[] findComments(Document document, string txt) {
3647 	return findComments(document.root, txt);
3648 }
3649 
3650 /// ditto
3651 Element[] findComments(Element element, string txt) {
3652 	txt = txt.strip().toLower();
3653 	Element[] ret;
3654 
3655 	foreach(comment; element.getElementsByTagName("#comment")) {
3656 		string t = comment.nodeValue().strip().toLower();
3657 		if(t == txt)
3658 			ret ~= comment;
3659 	}
3660 
3661 	return ret;
3662 }
3663 
3664 /// An option type that propagates null. See: [Element.optionSelector]
3665 /// Group: implementations
3666 struct MaybeNullElement(SomeElementType) {
3667 	this(SomeElementType ele) {
3668 		this.element = ele;
3669 	}
3670 	SomeElementType element;
3671 
3672 	/// Forwards to the element, wit a null check inserted that propagates null.
3673 	auto opDispatch(string method, T...)(T args) {
3674 		alias type = typeof(__traits(getMember, element, method)(args));
3675 		static if(is(type : Element)) {
3676 			if(element is null)
3677 				return MaybeNullElement!type(null);
3678 			return __traits(getMember, element, method)(args);
3679 		} else static if(is(type == string)) {
3680 			if(element is null)
3681 				return cast(string) null;
3682 			return __traits(getMember, element, method)(args);
3683 		} else static if(is(type == void)) {
3684 			if(element is null)
3685 				return;
3686 			__traits(getMember, element, method)(args);
3687 		} else {
3688 			static assert(0);
3689 		}
3690 	}
3691 
3692 	/// Allows implicit casting to the wrapped element.
3693 	alias element this;
3694 }
3695 
3696 /++
3697 	A collection of elements which forwards methods to the children.
3698 +/
3699 /// Group: implementations
3700 struct ElementCollection {
3701 	///
3702 	this(Element e) {
3703 		elements = [e];
3704 	}
3705 
3706 	///
3707 	this(Element e, string selector) {
3708 		elements = e.querySelectorAll(selector);
3709 	}
3710 
3711 	///
3712 	this(Element[] e) {
3713 		elements = e;
3714 	}
3715 
3716 	Element[] elements;
3717 	//alias elements this; // let it implicitly convert to the underlying array
3718 
3719 	///
3720 	ElementCollection opIndex(string selector) {
3721 		ElementCollection ec;
3722 		foreach(e; elements)
3723 			ec.elements ~= e.getElementsBySelector(selector);
3724 		return ec;
3725 	}
3726 
3727 	///
3728 	Element opIndex(int i) {
3729 		return elements[i];
3730 	}
3731 
3732 	/// if you slice it, give the underlying array for easy forwarding of the
3733 	/// collection to range expecting algorithms or looping over.
3734 	Element[] opSlice() {
3735 		return elements;
3736 	}
3737 
3738 	/// And input range primitives so we can foreach over this
3739 	void popFront() {
3740 		elements = elements[1..$];
3741 	}
3742 
3743 	/// ditto
3744 	Element front() {
3745 		return elements[0];
3746 	}
3747 
3748 	/// ditto
3749 	bool empty() {
3750 		return !elements.length;
3751 	}
3752 
3753 	/++
3754 		Collects strings from the collection, concatenating them together
3755 		Kinda like running reduce and ~= on it.
3756 
3757 		---
3758 		document["p"].collect!"innerText";
3759 		---
3760 	+/
3761 	string collect(string method)(string separator = "") {
3762 		string text;
3763 		foreach(e; elements) {
3764 			text ~= mixin("e." ~ method);
3765 			text ~= separator;
3766 		}
3767 		return text;
3768 	}
3769 
3770 	/// Forward method calls to each individual [Element|element] of the collection
3771 	/// returns this so it can be chained.
3772 	ElementCollection opDispatch(string name, T...)(T t) {
3773 		foreach(e; elements) {
3774 			mixin("e." ~ name)(t);
3775 		}
3776 		return this;
3777 	}
3778 
3779 	/++
3780 		Calls [Element.wrapIn] on each member of the collection, but clones the argument `what` for each one.
3781 	+/
3782 	ElementCollection wrapIn(Element what) {
3783 		foreach(e; elements) {
3784 			e.wrapIn(what.cloneNode(false));
3785 		}
3786 
3787 		return this;
3788 	}
3789 
3790 	/// Concatenates two ElementCollection together.
3791 	ElementCollection opBinary(string op : "~")(ElementCollection rhs) {
3792 		return ElementCollection(this.elements ~ rhs.elements);
3793 	}
3794 }
3795 
3796 
3797 /// this puts in operators and opDispatch to handle string indexes and properties, forwarding to get and set functions.
3798 /// Group: implementations
3799 mixin template JavascriptStyleDispatch() {
3800 	///
3801 	string opDispatch(string name)(string v = null) if(name != "popFront") { // popFront will make this look like a range. Do not want.
3802 		if(v !is null)
3803 			return set(name, v);
3804 		return get(name);
3805 	}
3806 
3807 	///
3808 	string opIndex(string key) const {
3809 		return get(key);
3810 	}
3811 
3812 	///
3813 	string opIndexAssign(string value, string field) {
3814 		return set(field, value);
3815 	}
3816 
3817 	// FIXME: doesn't seem to work
3818 	string* opBinary(string op)(string key)  if(op == "in") {
3819 		return key in fields;
3820 	}
3821 }
3822 
3823 /// A proxy object to do the Element class' dataset property. See Element.dataset for more info.
3824 ///
3825 /// Do not create this object directly.
3826 /// Group: implementations
3827 struct DataSet {
3828 	///
3829 	this(Element e) {
3830 		this._element = e;
3831 	}
3832 
3833 	private Element _element;
3834 	///
3835 	string set(string name, string value) {
3836 		_element.setAttribute("data-" ~ unCamelCase(name), value);
3837 		return value;
3838 	}
3839 
3840 	///
3841 	string get(string name) const {
3842 		return _element.getAttribute("data-" ~ unCamelCase(name));
3843 	}
3844 
3845 	///
3846 	mixin JavascriptStyleDispatch!();
3847 }
3848 
3849 /// Proxy object for attributes which will replace the main opDispatch eventually
3850 /// Group: implementations
3851 struct AttributeSet {
3852 	///
3853 	this(Element e) {
3854 		this._element = e;
3855 	}
3856 
3857 	private Element _element;
3858 	///
3859 	string set(string name, string value) {
3860 		_element.setAttribute(name, value);
3861 		return value;
3862 	}
3863 
3864 	///
3865 	string get(string name) const {
3866 		return _element.getAttribute(name);
3867 	}
3868 
3869 	///
3870 	mixin JavascriptStyleDispatch!();
3871 }
3872 
3873 
3874 
3875 /// for style, i want to be able to set it with a string like a plain attribute,
3876 /// but also be able to do properties Javascript style.
3877 
3878 /// Group: implementations
3879 struct ElementStyle {
3880 	this(Element parent) {
3881 		_element = parent;
3882 	}
3883 
3884 	Element _element;
3885 
3886 	@property ref inout(string) _attribute() inout {
3887 		auto s = "style" in _element.attributes;
3888 		if(s is null) {
3889 			auto e = cast() _element; // const_cast
3890 			e.attributes["style"] = ""; // we need something to reference
3891 			s = cast(inout) ("style" in e.attributes);
3892 		}
3893 
3894 		assert(s !is null);
3895 		return *s;
3896 	}
3897 
3898 	alias _attribute this; // this is meant to allow element.style = element.style ~ " string "; to still work.
3899 
3900 	string set(string name, string value) {
3901 		if(name.length == 0)
3902 			return value;
3903 		if(name == "cssFloat")
3904 			name = "float";
3905 		else
3906 			name = unCamelCase(name);
3907 		auto r = rules();
3908 		r[name] = value;
3909 
3910 		_attribute = "";
3911 		foreach(k, v; r) {
3912 			if(v is null || v.length == 0) /* css can't do empty rules anyway so we'll use that to remove */
3913 				continue;
3914 			if(_attribute.length)
3915 				_attribute ~= " ";
3916 			_attribute ~= k ~ ": " ~ v ~ ";";
3917 		}
3918 
3919 		_element.setAttribute("style", _attribute); // this is to trigger the observer call
3920 
3921 		return value;
3922 	}
3923 	string get(string name) const {
3924 		if(name == "cssFloat")
3925 			name = "float";
3926 		else
3927 			name = unCamelCase(name);
3928 		auto r = rules();
3929 		if(name in r)
3930 			return r[name];
3931 		return null;
3932 	}
3933 
3934 	string[string] rules() const {
3935 		string[string] ret;
3936 		foreach(rule;  _attribute.split(";")) {
3937 			rule = rule.strip();
3938 			if(rule.length == 0)
3939 				continue;
3940 			auto idx = rule.indexOf(":");
3941 			if(idx == -1)
3942 				ret[rule] = "";
3943 			else {
3944 				auto name = rule[0 .. idx].strip();
3945 				auto value = rule[idx + 1 .. $].strip();
3946 
3947 				ret[name] = value;
3948 			}
3949 		}
3950 
3951 		return ret;
3952 	}
3953 
3954 	mixin JavascriptStyleDispatch!();
3955 }
3956 
3957 /// Converts a camel cased propertyName to a css style dashed property-name
3958 string unCamelCase(string a) {
3959 	string ret;
3960 	foreach(c; a)
3961 		if((c >= 'A' && c <= 'Z'))
3962 			ret ~= "-" ~ toLower("" ~ c)[0];
3963 		else
3964 			ret ~= c;
3965 	return ret;
3966 }
3967 
3968 /// Translates a css style property-name to a camel cased propertyName
3969 string camelCase(string a) {
3970 	string ret;
3971 	bool justSawDash = false;
3972 	foreach(c; a)
3973 		if(c == '-') {
3974 			justSawDash = true;
3975 		} else {
3976 			if(justSawDash) {
3977 				justSawDash = false;
3978 				ret ~= toUpper("" ~ c);
3979 			} else
3980 				ret ~= c;
3981 		}
3982 	return ret;
3983 }
3984 
3985 
3986 
3987 
3988 
3989 
3990 
3991 
3992 
3993 // domconvenience ends }
3994 
3995 
3996 
3997 
3998 
3999 
4000 
4001 
4002 
4003 
4004 
4005 // @safe:
4006 
4007 // NOTE: do *NOT* override toString on Element subclasses. It won't work.
4008 // Instead, override writeToAppender();
4009 
4010 // 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.
4011 
4012 // Stripping them is useful for reading php as html.... but adding them
4013 // is good for building php.
4014 
4015 // I need to maintain compatibility with the way it is now too.
4016 
4017 import std.string;
4018 import std.exception;
4019 import std.uri;
4020 import std.array;
4021 import std.range;
4022 
4023 //import std.stdio;
4024 
4025 // 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
4026 // 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
4027 // most likely a typo so I say kill kill kill.
4028 
4029 
4030 /++
4031 	This might belong in another module, but it represents a file with a mime type and some data.
4032 	Document implements this interface with type = text/html (see Document.contentType for more info)
4033 	and data = document.toString, so you can return Documents anywhere web.d expects FileResources.
4034 +/
4035 /// Group: bonus_functionality
4036 interface FileResource {
4037 	/// the content-type of the file. e.g. "text/html; charset=utf-8" or "image/png"
4038 	@property string contentType() const;
4039 	/// the data
4040 	immutable(ubyte)[] getData() const;
4041 	/++
4042 		filename, return null if none
4043 
4044 		History:
4045 			Added December 25, 2020
4046 	+/
4047 	@property string filename() const;
4048 }
4049 
4050 
4051 
4052 
4053 ///.
4054 /// Group: bonus_functionality
4055 enum NodeType { Text = 3 }
4056 
4057 
4058 /// You can use this to do an easy null check or a dynamic cast+null check on any element.
4059 /// Group: core_functionality
4060 T require(T = Element, string file = __FILE__, int line = __LINE__)(Element e) if(is(T : Element))
4061 	in {}
4062 	out(ret) { assert(ret !is null); }
4063 do {
4064 	auto ret = cast(T) e;
4065 	if(ret is null)
4066 		throw new ElementNotFoundException(T.stringof, "passed value", e, file, line);
4067 	return ret;
4068 }
4069 
4070 
4071 ///.
4072 /// Group: core_functionality
4073 class DocumentFragment : Element {
4074 	///.
4075 	this(Document _parentDocument) {
4076 		tagName = "#fragment";
4077 		super(_parentDocument);
4078 	}
4079 
4080 	/++
4081 		Creates a document fragment from the given HTML. Note that the HTML is assumed to close all tags contained inside it.
4082 
4083 		Since: March 29, 2018 (or git tagged v2.1.0)
4084 	+/
4085 	this(Html html) {
4086 		this(null);
4087 
4088 		this.innerHTML = html.source;
4089 	}
4090 
4091 	///.
4092 	override string writeToAppender(Appender!string where = appender!string()) const {
4093 		return this.innerHTML(where);
4094 	}
4095 
4096 	override string toPrettyString(bool insertComments, int indentationLevel, string indentWith) const {
4097 		string s;
4098 		foreach(child; children)
4099 			s ~= child.toPrettyString(insertComments, indentationLevel, indentWith);
4100 		return s;
4101 	}
4102 
4103 	/// DocumentFragments don't really exist in a dom, so they ignore themselves in parent nodes
4104 	/*
4105 	override inout(Element) parentNode() inout {
4106 		return children.length ? children[0].parentNode : null;
4107 	}
4108 	*/
4109 	/+
4110 	override Element parentNode(Element p) {
4111 		this.parentNode = p;
4112 		foreach(child; children)
4113 			child.parentNode = p;
4114 		return p;
4115 	}
4116 	+/
4117 }
4118 
4119 /// Given text, encode all html entities on it - &, <, >, and ". This function also
4120 /// encodes all 8 bit characters as entities, thus ensuring the resultant text will work
4121 /// even if your charset isn't set right. You can suppress with by setting encodeNonAscii = false
4122 ///
4123 /// The output parameter can be given to append to an existing buffer. You don't have to
4124 /// pass one; regardless, the return value will be usable for you, with just the data encoded.
4125 /// Group: core_functionality
4126 string htmlEntitiesEncode(string data, Appender!string output = appender!string(), bool encodeNonAscii = true) {
4127 	// if there's no entities, we can save a lot of time by not bothering with the
4128 	// decoding loop. This check cuts the net toString time by better than half in my test.
4129 	// let me know if it made your tests worse though, since if you use an entity in just about
4130 	// every location, the check will add time... but I suspect the average experience is like mine
4131 	// since the check gives up as soon as it can anyway.
4132 
4133 	bool shortcut = true;
4134 	foreach(char c; data) {
4135 		// non ascii chars are always higher than 127 in utf8; we'd better go to the full decoder if we see it.
4136 		if(c == '<' || c == '>' || c == '"' || c == '&' || (encodeNonAscii && cast(uint) c > 127)) {
4137 			shortcut = false; // there's actual work to be done
4138 			break;
4139 		}
4140 	}
4141 
4142 	if(shortcut) {
4143 		output.put(data);
4144 		return data;
4145 	}
4146 
4147 	auto start = output.data.length;
4148 
4149 	output.reserve(data.length + 64); // grab some extra space for the encoded entities
4150 
4151 	foreach(dchar d; data) {
4152 		if(d == '&')
4153 			output.put("&amp;");
4154 		else if (d == '<')
4155 			output.put("&lt;");
4156 		else if (d == '>')
4157 			output.put("&gt;");
4158 		else if (d == '\"')
4159 			output.put("&quot;");
4160 //		else if (d == '\'')
4161 //			output.put("&#39;"); // if you are in an attribute, it might be important to encode for the same reason as double quotes
4162 			// FIXME: should I encode apostrophes too? as &#39;... I could also do space but if your html is so bad that it doesn't
4163 			// quote attributes at all, maybe you deserve the xss. Encoding spaces will make everything really ugly so meh
4164 			// idk about apostrophes though. Might be worth it, might not.
4165 		else if (!encodeNonAscii || (d < 128 && d > 0))
4166 			output.put(d);
4167 		else
4168 			output.put("&#" ~ std.conv.to!string(cast(int) d) ~ ";");
4169 	}
4170 
4171 	//assert(output !is null); // this fails on empty attributes.....
4172 	return output.data[start .. $];
4173 
4174 //	data = data.replace("\u00a0", "&nbsp;");
4175 }
4176 
4177 /// An alias for htmlEntitiesEncode; it works for xml too
4178 /// Group: core_functionality
4179 string xmlEntitiesEncode(string data) {
4180 	return htmlEntitiesEncode(data);
4181 }
4182 
4183 /// This helper function is used for decoding html entities. It has a hard-coded list of entities and characters.
4184 /// Group: core_functionality
4185 dchar parseEntity(in dchar[] entity) {
4186 
4187 	char[128] buffer;
4188 	int bpos;
4189 	foreach(char c; entity[1 .. $-1])
4190 		buffer[bpos++] = c;
4191 	char[] entityAsString = buffer[0 .. bpos];
4192 
4193 	int min = 0;
4194 	int max = cast(int) availableEntities.length;
4195 
4196 	keep_looking:
4197 	if(min + 1 < max) {
4198 		int spot = (max - min) / 2 + min;
4199 		if(availableEntities[spot] == entityAsString) {
4200 			return availableEntitiesValues[spot];
4201 		} else if(entityAsString < availableEntities[spot]) {
4202 			max = spot;
4203 			goto keep_looking;
4204 		} else {
4205 			min = spot;
4206 			goto keep_looking;
4207 		}
4208 	}
4209 
4210 	switch(entity[1..$-1]) {
4211 		case "quot":
4212 			return '"';
4213 		case "apos":
4214 			return '\'';
4215 		case "lt":
4216 			return '<';
4217 		case "gt":
4218 			return '>';
4219 		case "amp":
4220 			return '&';
4221 		// the next are html rather than xml
4222 
4223 		// and handling numeric entities
4224 		default:
4225 			if(entity[1] == '#') {
4226 				if(entity[2] == 'x' /*|| (!strict && entity[2] == 'X')*/) {
4227 					auto hex = entity[3..$-1];
4228 
4229 					auto p = intFromHex(to!string(hex).toLower());
4230 					return cast(dchar) p;
4231 				} else {
4232 					auto decimal = entity[2..$-1];
4233 
4234 					// dealing with broken html entities
4235 					while(decimal.length && (decimal[0] < '0' || decimal[0] >   '9'))
4236 						decimal = decimal[1 .. $];
4237 
4238 					if(decimal.length == 0)
4239 						return ' '; // this is really broken html
4240 					// done with dealing with broken stuff
4241 
4242 					auto p = std.conv.to!int(decimal);
4243 					return cast(dchar) p;
4244 				}
4245 			} else
4246 				return '\ufffd'; // replacement character diamond thing
4247 	}
4248 
4249 	assert(0);
4250 }
4251 
4252 unittest {
4253 	// not in the binary search
4254 	assert(parseEntity("&quot;"d) == '"');
4255 
4256 	// numeric value
4257 	assert(parseEntity("&#x0534;") == '\u0534');
4258 
4259 	// not found at all
4260 	assert(parseEntity("&asdasdasd;"d) == '\ufffd');
4261 
4262 	// random values in the bin search
4263 	assert(parseEntity("&Tab;"d) == '\t');
4264 	assert(parseEntity("&raquo;"d) == '\&raquo;');
4265 
4266 	// near the middle and edges of the bin search
4267 	assert(parseEntity("&ascr;"d) == '\U0001d4b6');
4268 	assert(parseEntity("&ast;"d) == '\u002a');
4269 	assert(parseEntity("&AElig;"d) == '\u00c6');
4270 	assert(parseEntity("&zwnj;"d) == '\u200c');
4271 }
4272 
4273 import std.utf;
4274 import std.stdio;
4275 
4276 /// This takes a string of raw HTML and decodes the entities into a nice D utf-8 string.
4277 /// By default, it uses loose mode - it will try to return a useful string from garbage input too.
4278 /// Set the second parameter to true if you'd prefer it to strictly throw exceptions on garbage input.
4279 /// Group: core_functionality
4280 string htmlEntitiesDecode(string data, bool strict = false) {
4281 	// this check makes a *big* difference; about a 50% improvement of parse speed on my test.
4282 	if(data.indexOf("&") == -1) // all html entities begin with &
4283 		return data; // if there are no entities in here, we can return the original slice and save some time
4284 
4285 	char[] a; // this seems to do a *better* job than appender!
4286 
4287 	char[4] buffer;
4288 
4289 	bool tryingEntity = false;
4290 	dchar[16] entityBeingTried;
4291 	int entityBeingTriedLength = 0;
4292 	int entityAttemptIndex = 0;
4293 
4294 	foreach(dchar ch; data) {
4295 		if(tryingEntity) {
4296 			entityAttemptIndex++;
4297 			entityBeingTried[entityBeingTriedLength++] = ch;
4298 
4299 			// I saw some crappy html in the wild that looked like &0&#1111; this tries to handle that.
4300 			if(ch == '&') {
4301 				if(strict)
4302 					throw new Exception("unterminated entity; & inside another at " ~ to!string(entityBeingTried[0 .. entityBeingTriedLength]));
4303 
4304 				// if not strict, let's try to parse both.
4305 
4306 				if(entityBeingTried[0 .. entityBeingTriedLength] == "&&")
4307 					a ~= "&"; // double amp means keep the first one, still try to parse the next one
4308 				else
4309 					a ~= buffer[0.. std.utf.encode(buffer, parseEntity(entityBeingTried[0 .. entityBeingTriedLength]))];
4310 
4311 				// tryingEntity is still true
4312 				entityBeingTriedLength = 1;
4313 				entityAttemptIndex = 0; // restarting o this
4314 			} else
4315 			if(ch == ';') {
4316 				tryingEntity = false;
4317 				a ~= buffer[0.. std.utf.encode(buffer, parseEntity(entityBeingTried[0 .. entityBeingTriedLength]))];
4318 			} else if(ch == ' ') {
4319 				// e.g. you &amp i
4320 				if(strict)
4321 					throw new Exception("unterminated entity at " ~ to!string(entityBeingTried[0 .. entityBeingTriedLength]));
4322 				else {
4323 					tryingEntity = false;
4324 					a ~= to!(char[])(entityBeingTried[0 .. entityBeingTriedLength]);
4325 				}
4326 			} else {
4327 				if(entityAttemptIndex >= 9) {
4328 					if(strict)
4329 						throw new Exception("unterminated entity at " ~ to!string(entityBeingTried[0 .. entityBeingTriedLength]));
4330 					else {
4331 						tryingEntity = false;
4332 						a ~= to!(char[])(entityBeingTried[0 .. entityBeingTriedLength]);
4333 					}
4334 				}
4335 			}
4336 		} else {
4337 			if(ch == '&') {
4338 				tryingEntity = true;
4339 				entityBeingTriedLength = 0;
4340 				entityBeingTried[entityBeingTriedLength++] = ch;
4341 				entityAttemptIndex = 0;
4342 			} else {
4343 				a ~= buffer[0 .. std.utf.encode(buffer, ch)];
4344 			}
4345 		}
4346 	}
4347 
4348 	if(tryingEntity) {
4349 		if(strict)
4350 			throw new Exception("unterminated entity at " ~ to!string(entityBeingTried[0 .. entityBeingTriedLength]));
4351 
4352 		// otherwise, let's try to recover, at least so we don't drop any data
4353 		a ~= to!string(entityBeingTried[0 .. entityBeingTriedLength]);
4354 		// FIXME: what if we have "cool &amp"? should we try to parse it?
4355 	}
4356 
4357 	return cast(string) a; // assumeUnique is actually kinda slow, lol
4358 }
4359 
4360 /// Group: implementations
4361 abstract class SpecialElement : Element {
4362 	this(Document _parentDocument) {
4363 		super(_parentDocument);
4364 	}
4365 
4366 	///.
4367 	override Element appendChild(Element e) {
4368 		assert(0, "Cannot append to a special node");
4369 	}
4370 
4371 	///.
4372 	@property override int nodeType() const {
4373 		return 100;
4374 	}
4375 }
4376 
4377 ///.
4378 /// Group: implementations
4379 class RawSource : SpecialElement {
4380 	///.
4381 	this(Document _parentDocument, string s) {
4382 		super(_parentDocument);
4383 		source = s;
4384 		tagName = "#raw";
4385 	}
4386 
4387 	///.
4388 	override string nodeValue() const {
4389 		return this.toString();
4390 	}
4391 
4392 	///.
4393 	override string writeToAppender(Appender!string where = appender!string()) const {
4394 		where.put(source);
4395 		return source;
4396 	}
4397 
4398 	override string toPrettyString(bool, int, string) const {
4399 		return source;
4400 	}
4401 
4402 
4403 	override RawSource cloneNode(bool deep) {
4404 		return new RawSource(parentDocument, source);
4405 	}
4406 
4407 	///.
4408 	string source;
4409 }
4410 
4411 /// Group: implementations
4412 abstract class ServerSideCode : SpecialElement {
4413 	this(Document _parentDocument, string type) {
4414 		super(_parentDocument);
4415 		tagName = "#" ~ type;
4416 	}
4417 
4418 	///.
4419 	override string nodeValue() const {
4420 		return this.source;
4421 	}
4422 
4423 	///.
4424 	override string writeToAppender(Appender!string where = appender!string()) const {
4425 		auto start = where.data.length;
4426 		where.put("<");
4427 		where.put(source);
4428 		where.put(">");
4429 		return where.data[start .. $];
4430 	}
4431 
4432 	override string toPrettyString(bool, int, string) const {
4433 		return "<" ~ source ~ ">";
4434 	}
4435 
4436 	///.
4437 	string source;
4438 }
4439 
4440 ///.
4441 /// Group: implementations
4442 class PhpCode : ServerSideCode {
4443 	///.
4444 	this(Document _parentDocument, string s) {
4445 		super(_parentDocument, "php");
4446 		source = s;
4447 	}
4448 
4449 	override PhpCode cloneNode(bool deep) {
4450 		return new PhpCode(parentDocument, source);
4451 	}
4452 }
4453 
4454 ///.
4455 /// Group: implementations
4456 class AspCode : ServerSideCode {
4457 	///.
4458 	this(Document _parentDocument, string s) {
4459 		super(_parentDocument, "asp");
4460 		source = s;
4461 	}
4462 
4463 	override AspCode cloneNode(bool deep) {
4464 		return new AspCode(parentDocument, source);
4465 	}
4466 }
4467 
4468 ///.
4469 /// Group: implementations
4470 class BangInstruction : SpecialElement {
4471 	///.
4472 	this(Document _parentDocument, string s) {
4473 		super(_parentDocument);
4474 		source = s;
4475 		tagName = "#bpi";
4476 	}
4477 
4478 	///.
4479 	override string nodeValue() const {
4480 		return this.source;
4481 	}
4482 
4483 	override BangInstruction cloneNode(bool deep) {
4484 		return new BangInstruction(parentDocument, source);
4485 	}
4486 
4487 	///.
4488 	override string writeToAppender(Appender!string where = appender!string()) const {
4489 		auto start = where.data.length;
4490 		where.put("<!");
4491 		where.put(source);
4492 		where.put(">");
4493 		return where.data[start .. $];
4494 	}
4495 
4496 	override string toPrettyString(bool, int, string) const {
4497 		string s;
4498 		s ~= "<!";
4499 		s ~= source;
4500 		s ~= ">";
4501 		return s;
4502 	}
4503 
4504 	///.
4505 	string source;
4506 }
4507 
4508 ///.
4509 /// Group: implementations
4510 class QuestionInstruction : SpecialElement {
4511 	///.
4512 	this(Document _parentDocument, string s) {
4513 		super(_parentDocument);
4514 		source = s;
4515 		tagName = "#qpi";
4516 	}
4517 
4518 	override QuestionInstruction cloneNode(bool deep) {
4519 		return new QuestionInstruction(parentDocument, source);
4520 	}
4521 
4522 	///.
4523 	override string nodeValue() const {
4524 		return this.source;
4525 	}
4526 
4527 	///.
4528 	override string writeToAppender(Appender!string where = appender!string()) const {
4529 		auto start = where.data.length;
4530 		where.put("<");
4531 		where.put(source);
4532 		where.put(">");
4533 		return where.data[start .. $];
4534 	}
4535 
4536 	override string toPrettyString(bool, int, string) const {
4537 		string s;
4538 		s ~= "<";
4539 		s ~= source;
4540 		s ~= ">";
4541 		return s;
4542 	}
4543 
4544 
4545 	///.
4546 	string source;
4547 }
4548 
4549 ///.
4550 /// Group: implementations
4551 class HtmlComment : SpecialElement {
4552 	///.
4553 	this(Document _parentDocument, string s) {
4554 		super(_parentDocument);
4555 		source = s;
4556 		tagName = "#comment";
4557 	}
4558 
4559 	override HtmlComment cloneNode(bool deep) {
4560 		return new HtmlComment(parentDocument, source);
4561 	}
4562 
4563 	///.
4564 	override string nodeValue() const {
4565 		return this.source;
4566 	}
4567 
4568 	///.
4569 	override string writeToAppender(Appender!string where = appender!string()) const {
4570 		auto start = where.data.length;
4571 		where.put("<!--");
4572 		where.put(source);
4573 		where.put("-->");
4574 		return where.data[start .. $];
4575 	}
4576 
4577 	override string toPrettyString(bool, int, string) const {
4578 		string s;
4579 		s ~= "<!--";
4580 		s ~= source;
4581 		s ~= "-->";
4582 		return s;
4583 	}
4584 
4585 
4586 	///.
4587 	string source;
4588 }
4589 
4590 
4591 
4592 
4593 ///.
4594 /// Group: implementations
4595 class TextNode : Element {
4596   public:
4597 	///.
4598 	this(Document _parentDocument, string e) {
4599 		super(_parentDocument);
4600 		contents = e;
4601 		tagName = "#text";
4602 	}
4603 
4604 	///
4605 	this(string e) {
4606 		this(null, e);
4607 	}
4608 
4609 	string opDispatch(string name)(string v = null) if(0) { return null; } // text nodes don't have attributes
4610 
4611 	///.
4612 	static TextNode fromUndecodedString(Document _parentDocument, string html) {
4613 		auto e = new TextNode(_parentDocument, "");
4614 		e.contents = htmlEntitiesDecode(html, _parentDocument is null ? false : !_parentDocument.loose);
4615 		return e;
4616 	}
4617 
4618 	///.
4619 	override @property TextNode cloneNode(bool deep) {
4620 		auto n = new TextNode(parentDocument, contents);
4621 		return n;
4622 	}
4623 
4624 	///.
4625 	override string nodeValue() const {
4626 		return this.contents; //toString();
4627 	}
4628 
4629 	///.
4630 	@property override int nodeType() const {
4631 		return NodeType.Text;
4632 	}
4633 
4634 	///.
4635 	override string writeToAppender(Appender!string where = appender!string()) const {
4636 		string s;
4637 		if(contents.length)
4638 			s = htmlEntitiesEncode(contents, where);
4639 		else
4640 			s = "";
4641 
4642 		assert(s !is null);
4643 		return s;
4644 	}
4645 
4646 	override string toPrettyString(bool insertComments = false, int indentationLevel = 0, string indentWith = "\t") const {
4647 		string s;
4648 
4649 		string contents = this.contents;
4650 		// we will first collapse the whitespace per html
4651 		// sort of. note this can break stuff yo!!!!
4652 		if(this.parentNode is null || this.parentNode.tagName != "pre") {
4653 			string n = "";
4654 			bool lastWasWhitespace = indentationLevel > 0;
4655 			foreach(char c; contents) {
4656 				if(c.isSimpleWhite) {
4657 					if(!lastWasWhitespace)
4658 						n ~= ' ';
4659 					lastWasWhitespace = true;
4660 				} else {
4661 					n ~= c;
4662 					lastWasWhitespace = false;
4663 				}
4664 			}
4665 
4666 			contents = n;
4667 		}
4668 
4669 		if(this.parentNode !is null && this.parentNode.tagName != "p") {
4670 			contents = contents.strip;
4671 		}
4672 
4673 		auto e = htmlEntitiesEncode(contents);
4674 		import std.algorithm.iteration : splitter;
4675 		bool first = true;
4676 		foreach(line; splitter(e, "\n")) {
4677 			if(first) {
4678 				s ~= toPrettyStringIndent(insertComments, indentationLevel, indentWith);
4679 				first = false;
4680 			} else {
4681 				s ~= "\n";
4682 				if(insertComments)
4683 					s ~= "<!--";
4684 				foreach(i; 0 .. indentationLevel)
4685 					s ~= "\t";
4686 				if(insertComments)
4687 					s ~= "-->";
4688 			}
4689 			s ~= line.stripRight;
4690 		}
4691 		return s;
4692 	}
4693 
4694 	///.
4695 	override Element appendChild(Element e) {
4696 		assert(0, "Cannot append to a text node");
4697 	}
4698 
4699 	///.
4700 	string contents;
4701 	// alias contents content; // I just mistype this a lot,
4702 }
4703 
4704 /**
4705 	There are subclasses of Element offering improved helper
4706 	functions for the element in HTML.
4707 */
4708 
4709 ///.
4710 /// Group: implementations
4711 class Link : Element {
4712 
4713 	///.
4714 	this(Document _parentDocument) {
4715 		super(_parentDocument);
4716 		this.tagName = "a";
4717 	}
4718 
4719 
4720 	///.
4721 	this(string href, string text) {
4722 		super("a");
4723 		setAttribute("href", href);
4724 		innerText = text;
4725 	}
4726 /+
4727 	/// Returns everything in the href EXCEPT the query string
4728 	@property string targetSansQuery() {
4729 
4730 	}
4731 
4732 	///.
4733 	@property string domainName() {
4734 
4735 	}
4736 
4737 	///.
4738 	@property string path
4739 +/
4740 	/// This gets a variable from the URL's query string.
4741 	string getValue(string name) {
4742 		auto vars = variablesHash();
4743 		if(name in vars)
4744 			return vars[name];
4745 		return null;
4746 	}
4747 
4748 	private string[string] variablesHash() {
4749 		string href = getAttribute("href");
4750 		if(href is null)
4751 			return null;
4752 
4753 		auto ques = href.indexOf("?");
4754 		string str = "";
4755 		if(ques != -1) {
4756 			str = href[ques+1..$];
4757 
4758 			auto fragment = str.indexOf("#");
4759 			if(fragment != -1)
4760 				str = str[0..fragment];
4761 		}
4762 
4763 		string[] variables = str.split("&");
4764 
4765 		string[string] hash;
4766 
4767 		foreach(var; variables) {
4768 			auto index = var.indexOf("=");
4769 			if(index == -1)
4770 				hash[var] = "";
4771 			else {
4772 				hash[decodeComponent(var[0..index])] = decodeComponent(var[index + 1 .. $]);
4773 			}
4774 		}
4775 
4776 		return hash;
4777 	}
4778 
4779 	///.
4780 	/*private*/ void updateQueryString(string[string] vars) {
4781 		string href = getAttribute("href");
4782 
4783 		auto question = href.indexOf("?");
4784 		if(question != -1)
4785 			href = href[0..question];
4786 
4787 		string frag = "";
4788 		auto fragment = href.indexOf("#");
4789 		if(fragment != -1) {
4790 			frag = href[fragment..$];
4791 			href = href[0..fragment];
4792 		}
4793 
4794 		string query = "?";
4795 		bool first = true;
4796 		foreach(name, value; vars) {
4797 			if(!first)
4798 				query ~= "&";
4799 			else
4800 				first = false;
4801 
4802 			query ~= encodeComponent(name);
4803 			if(value.length)
4804 				query ~= "=" ~ encodeComponent(value);
4805 		}
4806 
4807 		if(query != "?")
4808 			href ~= query;
4809 
4810 		href ~= frag;
4811 
4812 		setAttribute("href", href);
4813 	}
4814 
4815 	/// Sets or adds the variable with the given name to the given value
4816 	/// It automatically URI encodes the values and takes care of the ? and &.
4817 	override void setValue(string name, string variable) {
4818 		auto vars = variablesHash();
4819 		vars[name] = variable;
4820 
4821 		updateQueryString(vars);
4822 	}
4823 
4824 	/// Removes the given variable from the query string
4825 	void removeValue(string name) {
4826 		auto vars = variablesHash();
4827 		vars.remove(name);
4828 
4829 		updateQueryString(vars);
4830 	}
4831 
4832 	/*
4833 	///.
4834 	override string toString() {
4835 
4836 	}
4837 
4838 	///.
4839 	override string getAttribute(string name) {
4840 		if(name == "href") {
4841 
4842 		} else
4843 			return super.getAttribute(name);
4844 	}
4845 	*/
4846 }
4847 
4848 ///.
4849 /// Group: implementations
4850 class Form : Element {
4851 
4852 	///.
4853 	this(Document _parentDocument) {
4854 		super(_parentDocument);
4855 		tagName = "form";
4856 	}
4857 
4858 	override Element addField(string label, string name, string type = "text", FormFieldOptions fieldOptions = FormFieldOptions.none) {
4859 		auto t = this.querySelector("fieldset div");
4860 		if(t is null)
4861 			return super.addField(label, name, type, fieldOptions);
4862 		else
4863 			return t.addField(label, name, type, fieldOptions);
4864 	}
4865 
4866 	override Element addField(string label, string name, FormFieldOptions fieldOptions) {
4867 		auto type = "text";
4868 		auto t = this.querySelector("fieldset div");
4869 		if(t is null)
4870 			return super.addField(label, name, type, fieldOptions);
4871 		else
4872 			return t.addField(label, name, type, fieldOptions);
4873 	}
4874 
4875 	override Element addField(string label, string name, string[string] options, FormFieldOptions fieldOptions = FormFieldOptions.none) {
4876 		auto t = this.querySelector("fieldset div");
4877 		if(t is null)
4878 			return super.addField(label, name, options, fieldOptions);
4879 		else
4880 			return t.addField(label, name, options, fieldOptions);
4881 	}
4882 
4883 	override void setValue(string field, string value) {
4884 		setValue(field, value, true);
4885 	}
4886 
4887 	// FIXME: doesn't handle arrays; multiple fields can have the same name
4888 
4889 	/// Set's the form field's value. For input boxes, this sets the value attribute. For
4890 	/// textareas, it sets the innerText. For radio boxes and select boxes, it removes
4891 	/// the checked/selected attribute from all, and adds it to the one matching the value.
4892 	/// For checkboxes, if the value is non-null and not empty, it checks the box.
4893 
4894 	/// If you set a value that doesn't exist, it throws an exception if makeNew is false.
4895 	/// Otherwise, it makes a new input with type=hidden to keep the value.
4896 	void setValue(string field, string value, bool makeNew) {
4897 		auto eles = getField(field);
4898 		if(eles.length == 0) {
4899 			if(makeNew) {
4900 				addInput(field, value);
4901 				return;
4902 			} else
4903 				throw new Exception("form field does not exist");
4904 		}
4905 
4906 		if(eles.length == 1) {
4907 			auto e = eles[0];
4908 			switch(e.tagName) {
4909 				default: assert(0);
4910 				case "textarea":
4911 					e.innerText = value;
4912 				break;
4913 				case "input":
4914 					string type = e.getAttribute("type");
4915 					if(type is null) {
4916 						e.value = value;
4917 						return;
4918 					}
4919 					switch(type) {
4920 						case "checkbox":
4921 						case "radio":
4922 							if(value.length && value != "false")
4923 								e.setAttribute("checked", "checked");
4924 							else
4925 								e.removeAttribute("checked");
4926 						break;
4927 						default:
4928 							e.value = value;
4929 							return;
4930 					}
4931 				break;
4932 				case "select":
4933 					bool found = false;
4934 					foreach(child; e.tree) {
4935 						if(child.tagName != "option")
4936 							continue;
4937 						string val = child.getAttribute("value");
4938 						if(val is null)
4939 							val = child.innerText;
4940 						if(val == value) {
4941 							child.setAttribute("selected", "selected");
4942 							found = true;
4943 						} else
4944 							child.removeAttribute("selected");
4945 					}
4946 
4947 					if(!found) {
4948 						e.addChild("option", value)
4949 						.setAttribute("selected", "selected");
4950 					}
4951 				break;
4952 			}
4953 		} else {
4954 			// assume radio boxes
4955 			foreach(e; eles) {
4956 				string val = e.getAttribute("value");
4957 				//if(val is null)
4958 				//	throw new Exception("don't know what to do with radio boxes with null value");
4959 				if(val == value)
4960 					e.setAttribute("checked", "checked");
4961 				else
4962 					e.removeAttribute("checked");
4963 			}
4964 		}
4965 	}
4966 
4967 	/// This takes an array of strings and adds hidden <input> elements for each one of them. Unlike setValue,
4968 	/// it makes no attempt to find and modify existing elements in the form to the new values.
4969 	void addValueArray(string key, string[] arrayOfValues) {
4970 		foreach(arr; arrayOfValues)
4971 			addChild("input", key, arr);
4972 	}
4973 
4974 	/// Gets the value of the field; what would be given if it submitted right now. (so
4975 	/// it handles select boxes and radio buttons too). For checkboxes, if a value isn't
4976 	/// given, but it is checked, it returns "checked", since null and "" are indistinguishable
4977 	string getValue(string field) {
4978 		auto eles = getField(field);
4979 		if(eles.length == 0)
4980 			return "";
4981 		if(eles.length == 1) {
4982 			auto e = eles[0];
4983 			switch(e.tagName) {
4984 				default: assert(0);
4985 				case "input":
4986 					if(e.type == "checkbox") {
4987 						if(e.checked)
4988 							return e.value.length ? e.value : "checked";
4989 						return "";
4990 					} else
4991 						return e.value;
4992 				case "textarea":
4993 					return e.innerText;
4994 				case "select":
4995 					foreach(child; e.tree) {
4996 						if(child.tagName != "option")
4997 							continue;
4998 						if(child.selected)
4999 							return child.value;
5000 					}
5001 				break;
5002 			}
5003 		} else {
5004 			// assuming radio
5005 			foreach(e; eles) {
5006 				if(e.checked)
5007 					return e.value;
5008 			}
5009 		}
5010 
5011 		return "";
5012 	}
5013 
5014 	// FIXME: doesn't handle multiple elements with the same name (except radio buttons)
5015 	///.
5016 	string getPostableData() {
5017 		bool[string] namesDone;
5018 
5019 		string ret;
5020 		bool outputted = false;
5021 
5022 		foreach(e; getElementsBySelector("[name]")) {
5023 			if(e.name in namesDone)
5024 				continue;
5025 
5026 			if(outputted)
5027 				ret ~= "&";
5028 			else
5029 				outputted = true;
5030 
5031 			ret ~= std.uri.encodeComponent(e.name) ~ "=" ~ std.uri.encodeComponent(getValue(e.name));
5032 
5033 			namesDone[e.name] = true;
5034 		}
5035 
5036 		return ret;
5037 	}
5038 
5039 	/// Gets the actual elements with the given name
5040 	Element[] getField(string name) {
5041 		Element[] ret;
5042 		foreach(e; tree) {
5043 			if(e.name == name)
5044 				ret ~= e;
5045 		}
5046 		return ret;
5047 	}
5048 
5049 	/// Grabs the <label> with the given for tag, if there is one.
5050 	Element getLabel(string forId) {
5051 		foreach(e; tree)
5052 			if(e.tagName == "label" && e.getAttribute("for") == forId)
5053 				return e;
5054 		return null;
5055 	}
5056 
5057 	/// Adds a new INPUT field to the end of the form with the given attributes.
5058 	Element addInput(string name, string value, string type = "hidden") {
5059 		auto e = new Element(parentDocument, "input", null, true);
5060 		e.name = name;
5061 		e.value = value;
5062 		e.type = type;
5063 
5064 		appendChild(e);
5065 
5066 		return e;
5067 	}
5068 
5069 	/// Removes the given field from the form. It finds the element and knocks it right out.
5070 	void removeField(string name) {
5071 		foreach(e; getField(name))
5072 			e.parentNode.removeChild(e);
5073 	}
5074 
5075 	/+
5076 	/// Returns all form members.
5077 	@property Element[] elements() {
5078 
5079 	}
5080 
5081 	///.
5082 	string opDispatch(string name)(string v = null)
5083 		// filter things that should actually be attributes on the form
5084 		if( name != "method" && name != "action" && name != "enctype"
5085 		 && name != "style"  && name != "name" && name != "id" && name != "class")
5086 	{
5087 
5088 	}
5089 	+/
5090 /+
5091 	void submit() {
5092 		// take its elements and submit them through http
5093 	}
5094 +/
5095 }
5096 
5097 import std.conv;
5098 
5099 ///.
5100 /// Group: implementations
5101 class Table : Element {
5102 
5103 	///.
5104 	this(Document _parentDocument) {
5105 		super(_parentDocument);
5106 		tagName = "table";
5107 	}
5108 
5109 	/// Creates an element with the given type and content.
5110 	Element th(T)(T t) {
5111 		Element e;
5112 		if(parentDocument !is null)
5113 			e = parentDocument.createElement("th");
5114 		else
5115 			e = Element.make("th");
5116 		static if(is(T == Html))
5117 			e.innerHTML = t;
5118 		else
5119 			e.innerText = to!string(t);
5120 		return e;
5121 	}
5122 
5123 	/// ditto
5124 	Element td(T)(T t) {
5125 		Element e;
5126 		if(parentDocument !is null)
5127 			e = parentDocument.createElement("td");
5128 		else
5129 			e = Element.make("td");
5130 		static if(is(T == Html))
5131 			e.innerHTML = t;
5132 		else
5133 			e.innerText = to!string(t);
5134 		return e;
5135 	}
5136 
5137 	/// .
5138 	Element appendHeaderRow(T...)(T t) {
5139 		return appendRowInternal("th", "thead", t);
5140 	}
5141 
5142 	/// .
5143 	Element appendFooterRow(T...)(T t) {
5144 		return appendRowInternal("td", "tfoot", t);
5145 	}
5146 
5147 	/// .
5148 	Element appendRow(T...)(T t) {
5149 		return appendRowInternal("td", "tbody", t);
5150 	}
5151 
5152 	void addColumnClasses(string[] classes...) {
5153 		auto grid = getGrid();
5154 		foreach(row; grid)
5155 		foreach(i, cl; classes) {
5156 			if(cl.length)
5157 			if(i < row.length)
5158 				row[i].addClass(cl);
5159 		}
5160 	}
5161 
5162 	private Element appendRowInternal(T...)(string innerType, string findType, T t) {
5163 		Element row = Element.make("tr");
5164 
5165 		foreach(e; t) {
5166 			static if(is(typeof(e) : Element)) {
5167 				if(e.tagName == "td" || e.tagName == "th")
5168 					row.appendChild(e);
5169 				else {
5170 					Element a = Element.make(innerType);
5171 
5172 					a.appendChild(e);
5173 
5174 					row.appendChild(a);
5175 				}
5176 			} else static if(is(typeof(e) == Html)) {
5177 				Element a = Element.make(innerType);
5178 				a.innerHTML = e.source;
5179 				row.appendChild(a);
5180 			} else static if(is(typeof(e) == Element[])) {
5181 				Element a = Element.make(innerType);
5182 				foreach(ele; e)
5183 					a.appendChild(ele);
5184 				row.appendChild(a);
5185 			} else static if(is(typeof(e) == string[])) {
5186 				foreach(ele; e) {
5187 					Element a = Element.make(innerType);
5188 					a.innerText = to!string(ele);
5189 					row.appendChild(a);
5190 				}
5191 			} else {
5192 				Element a = Element.make(innerType);
5193 				a.innerText = to!string(e);
5194 				row.appendChild(a);
5195 			}
5196 		}
5197 
5198 		foreach(e; children) {
5199 			if(e.tagName == findType) {
5200 				e.appendChild(row);
5201 				return row;
5202 			}
5203 		}
5204 
5205 		// the type was not found if we are here... let's add it so it is well-formed
5206 		auto lol = this.addChild(findType);
5207 		lol.appendChild(row);
5208 
5209 		return row;
5210 	}
5211 
5212 	///.
5213 	Element captionElement() {
5214 		Element cap;
5215 		foreach(c; children) {
5216 			if(c.tagName == "caption") {
5217 				cap = c;
5218 				break;
5219 			}
5220 		}
5221 
5222 		if(cap is null) {
5223 			cap = Element.make("caption");
5224 			appendChild(cap);
5225 		}
5226 
5227 		return cap;
5228 	}
5229 
5230 	///.
5231 	@property string caption() {
5232 		return captionElement().innerText;
5233 	}
5234 
5235 	///.
5236 	@property void caption(string text) {
5237 		captionElement().innerText = text;
5238 	}
5239 
5240 	/// Gets the logical layout of the table as a rectangular grid of
5241 	/// cells. It considers rowspan and colspan. A cell with a large
5242 	/// span is represented in the grid by being referenced several times.
5243 	/// The tablePortition parameter can get just a <thead>, <tbody>, or
5244 	/// <tfoot> portion if you pass one.
5245 	///
5246 	/// Note: the rectangular grid might include null cells.
5247 	///
5248 	/// This is kinda expensive so you should call once when you want the grid,
5249 	/// then do lookups on the returned array.
5250 	TableCell[][] getGrid(Element tablePortition = null)
5251 		in {
5252 			if(tablePortition is null)
5253 				assert(tablePortition is null);
5254 			else {
5255 				assert(tablePortition !is null);
5256 				assert(tablePortition.parentNode is this);
5257 				assert(
5258 					tablePortition.tagName == "tbody"
5259 					||
5260 					tablePortition.tagName == "tfoot"
5261 					||
5262 					tablePortition.tagName == "thead"
5263 				);
5264 			}
5265 		}
5266 	do {
5267 		if(tablePortition is null)
5268 			tablePortition = this;
5269 
5270 		TableCell[][] ret;
5271 
5272 		// FIXME: will also return rows of sub tables!
5273 		auto rows = tablePortition.getElementsByTagName("tr");
5274 		ret.length = rows.length;
5275 
5276 		int maxLength = 0;
5277 
5278 		int insertCell(int row, int position, TableCell cell) {
5279 			if(row >= ret.length)
5280 				return position; // not supposed to happen - a rowspan is prolly too big.
5281 
5282 			if(position == -1) {
5283 				position++;
5284 				foreach(item; ret[row]) {
5285 					if(item is null)
5286 						break;
5287 					position++;
5288 				}
5289 			}
5290 
5291 			if(position < ret[row].length)
5292 				ret[row][position] = cell;
5293 			else
5294 				foreach(i; ret[row].length .. position + 1) {
5295 					if(i == position)
5296 						ret[row] ~= cell;
5297 					else
5298 						ret[row] ~= null;
5299 				}
5300 			return position;
5301 		}
5302 
5303 		foreach(i, rowElement; rows) {
5304 			auto row = cast(TableRow) rowElement;
5305 			assert(row !is null);
5306 			assert(i < ret.length);
5307 
5308 			int position = 0;
5309 			foreach(cellElement; rowElement.childNodes) {
5310 				auto cell = cast(TableCell) cellElement;
5311 				if(cell is null)
5312 					continue;
5313 
5314 				// FIXME: colspan == 0 or rowspan == 0
5315 				// is supposed to mean fill in the rest of
5316 				// the table, not skip it
5317 				foreach(int j; 0 .. cell.colspan) {
5318 					foreach(int k; 0 .. cell.rowspan)
5319 						// if the first row, always append.
5320 						insertCell(k + cast(int) i, k == 0 ? -1 : position, cell);
5321 					position++;
5322 				}
5323 			}
5324 
5325 			if(ret[i].length > maxLength)
5326 				maxLength = cast(int) ret[i].length;
5327 		}
5328 
5329 		// want to ensure it's rectangular
5330 		foreach(ref r; ret) {
5331 			foreach(i; r.length .. maxLength)
5332 				r ~= null;
5333 		}
5334 
5335 		return ret;
5336 	}
5337 }
5338 
5339 /// Represents a table row element - a <tr>
5340 /// Group: implementations
5341 class TableRow : Element {
5342 	///.
5343 	this(Document _parentDocument) {
5344 		super(_parentDocument);
5345 		tagName = "tr";
5346 	}
5347 
5348 	// FIXME: the standard says there should be a lot more in here,
5349 	// but meh, I never use it and it's a pain to implement.
5350 }
5351 
5352 /// Represents anything that can be a table cell - <td> or <th> html.
5353 /// Group: implementations
5354 class TableCell : Element {
5355 	///.
5356 	this(Document _parentDocument, string _tagName) {
5357 		super(_parentDocument, _tagName);
5358 	}
5359 
5360 	@property int rowspan() const {
5361 		int ret = 1;
5362 		auto it = getAttribute("rowspan");
5363 		if(it.length)
5364 			ret = to!int(it);
5365 		return ret;
5366 	}
5367 
5368 	@property int colspan() const {
5369 		int ret = 1;
5370 		auto it = getAttribute("colspan");
5371 		if(it.length)
5372 			ret = to!int(it);
5373 		return ret;
5374 	}
5375 
5376 	@property int rowspan(int i) {
5377 		setAttribute("rowspan", to!string(i));
5378 		return i;
5379 	}
5380 
5381 	@property int colspan(int i) {
5382 		setAttribute("colspan", to!string(i));
5383 		return i;
5384 	}
5385 
5386 }
5387 
5388 
5389 ///.
5390 /// Group: implementations
5391 class MarkupException : Exception {
5392 
5393 	///.
5394 	this(string message, string file = __FILE__, size_t line = __LINE__) {
5395 		super(message, file, line);
5396 	}
5397 }
5398 
5399 /// This is used when you are using one of the require variants of navigation, and no matching element can be found in the tree.
5400 /// Group: implementations
5401 class ElementNotFoundException : Exception {
5402 
5403 	/// type == kind of element you were looking for and search == a selector describing the search.
5404 	this(string type, string search, Element searchContext, string file = __FILE__, size_t line = __LINE__) {
5405 		this.searchContext = searchContext;
5406 		super("Element of type '"~type~"' matching {"~search~"} not found.", file, line);
5407 	}
5408 
5409 	Element searchContext;
5410 }
5411 
5412 /// The html struct is used to differentiate between regular text nodes and html in certain functions
5413 ///
5414 /// Easiest way to construct it is like this: `auto html = Html("<p>hello</p>");`
5415 /// Group: core_functionality
5416 struct Html {
5417 	/// This string holds the actual html. Use it to retrieve the contents.
5418 	string source;
5419 }
5420 
5421 // for the observers
5422 enum DomMutationOperations {
5423 	setAttribute,
5424 	removeAttribute,
5425 	appendChild, // tagname, attributes[], innerHTML
5426 	insertBefore,
5427 	truncateChildren,
5428 	removeChild,
5429 	appendHtml,
5430 	replaceHtml,
5431 	appendText,
5432 	replaceText,
5433 	replaceTextOnly
5434 }
5435 
5436 // and for observers too
5437 struct DomMutationEvent {
5438 	DomMutationOperations operation;
5439 	Element target;
5440 	Element related; // what this means differs with the operation
5441 	Element related2;
5442 	string relatedString;
5443 	string relatedString2;
5444 }
5445 
5446 
5447 private immutable static string[] htmlSelfClosedElements = [
5448 	// html 4
5449 	"img", "hr", "input", "br", "col", "link", "meta",
5450 	// html 5
5451 	"source" ];
5452 
5453 private immutable static string[] htmlInlineElements = [
5454 	"span", "strong", "em", "b", "i", "a"
5455 ];
5456 
5457 
5458 static import std.conv;
5459 
5460 ///.
5461 int intFromHex(string hex) {
5462 	int place = 1;
5463 	int value = 0;
5464 	for(sizediff_t a = hex.length - 1; a >= 0; a--) {
5465 		int v;
5466 		char q = hex[a];
5467 		if( q >= '0' && q <= '9')
5468 			v = q - '0';
5469 		else if (q >= 'a' && q <= 'f')
5470 			v = q - 'a' + 10;
5471 		else throw new Exception("Illegal hex character: " ~ q);
5472 
5473 		value += v * place;
5474 
5475 		place *= 16;
5476 	}
5477 
5478 	return value;
5479 }
5480 
5481 
5482 // CSS selector handling
5483 
5484 // EXTENSIONS
5485 // dd - dt means get the dt directly before that dd (opposite of +)                  NOT IMPLEMENTED
5486 // dd -- dt means rewind siblings until you hit a dt, go as far as you need to       NOT IMPLEMENTED
5487 // 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")
5488 // dt << dl  means go as far up as needed to find a dl (you have an element and want its containers)      NOT IMPLEMENTED
5489 // :first  means to stop at the first hit, don't do more (so p + p == p ~ p:first
5490 
5491 
5492 
5493 // CSS4 draft currently says you can change the subject (the element actually returned) by putting a ! at the end of it.
5494 // That might be useful to implement, though I do have parent selectors too.
5495 
5496 		///.
5497 		static immutable string[] selectorTokens = [
5498 			// It is important that the 2 character possibilities go first here for accurate lexing
5499 		    "~=", "*=", "|=", "^=", "$=", "!=",
5500 		    "::", ">>",
5501 		    "<<", // my any-parent extension (reciprocal of whitespace)
5502 		    // " - ", // previous-sibling extension (whitespace required to disambiguate tag-names)
5503 		    ".", ">", "+", "*", ":", "[", "]", "=", "\"", "#", ",", " ", "~", "<", "(", ")"
5504 		]; // other is white space or a name.
5505 
5506 		///.
5507 		sizediff_t idToken(string str, sizediff_t position) {
5508 			sizediff_t tid = -1;
5509 			char c = str[position];
5510 			foreach(a, token; selectorTokens)
5511 
5512 				if(c == token[0]) {
5513 					if(token.length > 1) {
5514 						if(position + 1 >= str.length   ||   str[position+1] != token[1])
5515 							continue; // not this token
5516 					}
5517 					tid = a;
5518 					break;
5519 				}
5520 			return tid;
5521 		}
5522 
5523 	///.
5524 	// look, ma, no phobos!
5525 	// new lexer by ketmar
5526 	string[] lexSelector (string selstr) {
5527 
5528 		static sizediff_t idToken (string str, size_t stpos) {
5529 			char c = str[stpos];
5530 			foreach (sizediff_t tidx, immutable token; selectorTokens) {
5531 				if (c == token[0]) {
5532 					if (token.length > 1) {
5533 						assert(token.length == 2, token); // we don't have 3-char tokens yet
5534 						if (str.length-stpos < 2 || str[stpos+1] != token[1]) continue;
5535 					}
5536 					return tidx;
5537 				}
5538 			}
5539 			return -1;
5540 		}
5541 
5542 		// skip spaces and comments
5543 		static string removeLeadingBlanks (string str) {
5544 			size_t curpos = 0;
5545 			while (curpos < str.length) {
5546 				immutable char ch = str[curpos];
5547 				// this can overflow on 4GB strings on 32-bit; 'cmon, don't be silly, nobody cares!
5548 				if (ch == '/' && str.length-curpos > 1 && str[curpos+1] == '*') {
5549 					// comment
5550 					curpos += 2;
5551 					while (curpos < str.length) {
5552 						if (str[curpos] == '*' && str.length-curpos > 1 && str[curpos+1] == '/') {
5553 							curpos += 2;
5554 							break;
5555 						}
5556 						++curpos;
5557 					}
5558 				} else if (ch < 32) { // The < instead of <= is INTENTIONAL. See note from adr below.
5559 					++curpos;
5560 
5561 					// FROM ADR: This does NOT catch ' '! Spaces have semantic meaning in CSS! While
5562 					// "foo bar" is clear, and can only have one meaning, consider ".foo .bar".
5563 					// That is not the same as ".foo.bar". If the space is stripped, important
5564 					// information is lost, despite the tokens being separatable anyway.
5565 					//
5566 					// The parser really needs to be aware of the presence of a space.
5567 				} else {
5568 					break;
5569 				}
5570 			}
5571 			return str[curpos..$];
5572 		}
5573 
5574 		static bool isBlankAt() (string str, size_t pos) {
5575 			// we should consider unicode spaces too, but... unicode sux anyway.
5576 			return
5577 				(pos < str.length && // in string
5578 				 (str[pos] <= 32 || // space
5579 					(str.length-pos > 1 && str[pos] == '/' && str[pos+1] == '*'))); // comment
5580 		}
5581 
5582 		string[] tokens;
5583 		// lexx it!
5584 		while ((selstr = removeLeadingBlanks(selstr)).length > 0) {
5585 			if(selstr[0] == '\"' || selstr[0] == '\'') {
5586 				auto end = selstr[0];
5587 				auto pos = 1;
5588 				bool escaping;
5589 				while(pos < selstr.length && !escaping && selstr[pos] != end) {
5590 					if(escaping)
5591 						escaping = false;
5592 					else if(selstr[pos] == '\\')
5593 						escaping = true;
5594 					pos++;
5595 				}
5596 
5597 				// FIXME: do better unescaping
5598 				tokens ~= selstr[1 .. pos].replace(`\"`, `"`).replace(`\'`, `'`).replace(`\\`, `\`);
5599 				if(pos+1 >= selstr.length)
5600 					assert(0, selstr);
5601 				selstr = selstr[pos + 1.. $];
5602 				continue;
5603 			}
5604 
5605 
5606 			// no tokens starts with escape
5607 			immutable tid = idToken(selstr, 0);
5608 			if (tid >= 0) {
5609 				// special token
5610 				tokens ~= selectorTokens[tid]; // it's funnier this way
5611 				selstr = selstr[selectorTokens[tid].length..$];
5612 				continue;
5613 			}
5614 			// from start to space or special token
5615 			size_t escapePos = size_t.max;
5616 			size_t curpos = 0; // i can has chizburger^w escape at the start
5617 			while (curpos < selstr.length) {
5618 				if (selstr[curpos] == '\\') {
5619 					// this is escape, just skip it and next char
5620 					if (escapePos == size_t.max) escapePos = curpos;
5621 					curpos = (selstr.length-curpos >= 2 ? curpos+2 : selstr.length);
5622 				} else {
5623 					if (isBlankAt(selstr, curpos) || idToken(selstr, curpos) >= 0) break;
5624 					++curpos;
5625 				}
5626 			}
5627 			// identifier
5628 			if (escapePos != size_t.max) {
5629 				// i hate it when it happens
5630 				string id = selstr[0..escapePos];
5631 				while (escapePos < curpos) {
5632 					if (curpos-escapePos < 2) break;
5633 					id ~= selstr[escapePos+1]; // escaped char
5634 					escapePos += 2;
5635 					immutable stp = escapePos;
5636 					while (escapePos < curpos && selstr[escapePos] != '\\') ++escapePos;
5637 					if (escapePos > stp) id ~= selstr[stp..escapePos];
5638 				}
5639 				if (id.length > 0) tokens ~= id;
5640 			} else {
5641 				tokens ~= selstr[0..curpos];
5642 			}
5643 			selstr = selstr[curpos..$];
5644 		}
5645 		return tokens;
5646 	}
5647 	version(unittest_domd_lexer) unittest {
5648 		assert(lexSelector(r" test\=me  /*d*/") == [r"test=me"]);
5649 		assert(lexSelector(r"div/**/. id") == ["div", ".", "id"]);
5650 		assert(lexSelector(r" < <") == ["<", "<"]);
5651 		assert(lexSelector(r" <<") == ["<<"]);
5652 		assert(lexSelector(r" <</") == ["<<", "/"]);
5653 		assert(lexSelector(r" <</*") == ["<<"]);
5654 		assert(lexSelector(r" <\</*") == ["<", "<"]);
5655 		assert(lexSelector(r"heh\") == ["heh"]);
5656 		assert(lexSelector(r"alice \") == ["alice"]);
5657 		assert(lexSelector(r"alice,is#best") == ["alice", ",", "is", "#", "best"]);
5658 	}
5659 
5660 	///.
5661 	struct SelectorPart {
5662 		string tagNameFilter; ///.
5663 		string[] attributesPresent; /// [attr]
5664 		string[2][] attributesEqual; /// [attr=value]
5665 		string[2][] attributesStartsWith; /// [attr^=value]
5666 		string[2][] attributesEndsWith; /// [attr$=value]
5667 		// split it on space, then match to these
5668 		string[2][] attributesIncludesSeparatedBySpaces; /// [attr~=value]
5669 		// split it on dash, then match to these
5670 		string[2][] attributesIncludesSeparatedByDashes; /// [attr|=value]
5671 		string[2][] attributesInclude; /// [attr*=value]
5672 		string[2][] attributesNotEqual; /// [attr!=value] -- extension by me
5673 
5674 		string[] hasSelectors; /// :has(this)
5675 		string[] notSelectors; /// :not(this)
5676 
5677 		string[] isSelectors; /// :is(this)
5678 		string[] whereSelectors; /// :where(this)
5679 
5680 		ParsedNth[] nthOfType; /// .
5681 		ParsedNth[] nthLastOfType; /// .
5682 		ParsedNth[] nthChild; /// .
5683 
5684 		bool firstChild; ///.
5685 		bool lastChild; ///.
5686 
5687 		bool firstOfType; /// .
5688 		bool lastOfType; /// .
5689 
5690 		bool emptyElement; ///.
5691 		bool whitespaceOnly; ///
5692 		bool oddChild; ///.
5693 		bool evenChild; ///.
5694 
5695 		bool scopeElement; /// the css :scope thing; matches just the `this` element. NOT IMPLEMENTED
5696 
5697 		bool rootElement; ///.
5698 
5699 		int separation = -1; /// -1 == only itself; the null selector, 0 == tree, 1 == childNodes, 2 == childAfter, 3 == youngerSibling, 4 == parentOf
5700 
5701 		bool isCleanSlateExceptSeparation() {
5702 			auto cp = this;
5703 			cp.separation = -1;
5704 			return cp is SelectorPart.init;
5705 		}
5706 
5707 		///.
5708 		string toString() {
5709 			string ret;
5710 			switch(separation) {
5711 				default: assert(0);
5712 				case -1: break;
5713 				case 0: ret ~= " "; break;
5714 				case 1: ret ~= " > "; break;
5715 				case 2: ret ~= " + "; break;
5716 				case 3: ret ~= " ~ "; break;
5717 				case 4: ret ~= " < "; break;
5718 			}
5719 			ret ~= tagNameFilter;
5720 			foreach(a; attributesPresent) ret ~= "[" ~ a ~ "]";
5721 			foreach(a; attributesEqual) ret ~= "[" ~ a[0] ~ "=\"" ~ a[1] ~ "\"]";
5722 			foreach(a; attributesEndsWith) ret ~= "[" ~ a[0] ~ "$=\"" ~ a[1] ~ "\"]";
5723 			foreach(a; attributesStartsWith) ret ~= "[" ~ a[0] ~ "^=\"" ~ a[1] ~ "\"]";
5724 			foreach(a; attributesNotEqual) ret ~= "[" ~ a[0] ~ "!=\"" ~ a[1] ~ "\"]";
5725 			foreach(a; attributesInclude) ret ~= "[" ~ a[0] ~ "*=\"" ~ a[1] ~ "\"]";
5726 			foreach(a; attributesIncludesSeparatedByDashes) ret ~= "[" ~ a[0] ~ "|=\"" ~ a[1] ~ "\"]";
5727 			foreach(a; attributesIncludesSeparatedBySpaces) ret ~= "[" ~ a[0] ~ "~=\"" ~ a[1] ~ "\"]";
5728 
5729 			foreach(a; notSelectors) ret ~= ":not(" ~ a ~ ")";
5730 			foreach(a; hasSelectors) ret ~= ":has(" ~ a ~ ")";
5731 
5732 			foreach(a; isSelectors) ret ~= ":is(" ~ a ~ ")";
5733 			foreach(a; whereSelectors) ret ~= ":where(" ~ a ~ ")";
5734 
5735 			foreach(a; nthChild) ret ~= ":nth-child(" ~ a.toString ~ ")";
5736 			foreach(a; nthOfType) ret ~= ":nth-of-type(" ~ a.toString ~ ")";
5737 			foreach(a; nthLastOfType) ret ~= ":nth-last-of-type(" ~ a.toString ~ ")";
5738 
5739 			if(firstChild) ret ~= ":first-child";
5740 			if(lastChild) ret ~= ":last-child";
5741 			if(firstOfType) ret ~= ":first-of-type";
5742 			if(lastOfType) ret ~= ":last-of-type";
5743 			if(emptyElement) ret ~= ":empty";
5744 			if(whitespaceOnly) ret ~= ":whitespace-only";
5745 			if(oddChild) ret ~= ":odd-child";
5746 			if(evenChild) ret ~= ":even-child";
5747 			if(rootElement) ret ~= ":root";
5748 			if(scopeElement) ret ~= ":scope";
5749 
5750 			return ret;
5751 		}
5752 
5753 		// USEFUL
5754 		///.
5755 		bool matchElement(Element e) {
5756 			// FIXME: this can be called a lot of times, and really add up in times according to the profiler.
5757 			// Each individual call is reasonably fast already, but it adds up.
5758 			if(e is null) return false;
5759 			if(e.nodeType != 1) return false;
5760 
5761 			if(tagNameFilter != "" && tagNameFilter != "*")
5762 				if(e.tagName != tagNameFilter)
5763 					return false;
5764 			if(firstChild) {
5765 				if(e.parentNode is null)
5766 					return false;
5767 				if(e.parentNode.childElements[0] !is e)
5768 					return false;
5769 			}
5770 			if(lastChild) {
5771 				if(e.parentNode is null)
5772 					return false;
5773 				auto ce = e.parentNode.childElements;
5774 				if(ce[$-1] !is e)
5775 					return false;
5776 			}
5777 			if(firstOfType) {
5778 				if(e.parentNode is null)
5779 					return false;
5780 				auto ce = e.parentNode.childElements;
5781 				foreach(c; ce) {
5782 					if(c.tagName == e.tagName) {
5783 						if(c is e)
5784 							return true;
5785 						else
5786 							return false;
5787 					}
5788 				}
5789 			}
5790 			if(lastOfType) {
5791 				if(e.parentNode is null)
5792 					return false;
5793 				auto ce = e.parentNode.childElements;
5794 				foreach_reverse(c; ce) {
5795 					if(c.tagName == e.tagName) {
5796 						if(c is e)
5797 							return true;
5798 						else
5799 							return false;
5800 					}
5801 				}
5802 			}
5803 			/+
5804 			if(scopeElement) {
5805 				if(e !is this_)
5806 					return false;
5807 			}
5808 			+/
5809 			if(emptyElement) {
5810 				if(e.children.length)
5811 					return false;
5812 			}
5813 			if(whitespaceOnly) {
5814 				if(e.innerText.strip.length)
5815 					return false;
5816 			}
5817 			if(rootElement) {
5818 				if(e.parentNode !is null)
5819 					return false;
5820 			}
5821 			if(oddChild || evenChild) {
5822 				if(e.parentNode is null)
5823 					return false;
5824 				foreach(i, child; e.parentNode.childElements) {
5825 					if(child is e) {
5826 						if(oddChild && !(i&1))
5827 							return false;
5828 						if(evenChild && (i&1))
5829 							return false;
5830 						break;
5831 					}
5832 				}
5833 			}
5834 
5835 			bool matchWithSeparator(string attr, string value, string separator) {
5836 				foreach(s; attr.split(separator))
5837 					if(s == value)
5838 						return true;
5839 				return false;
5840 			}
5841 
5842 			foreach(a; attributesPresent)
5843 				if(a !in e.attributes)
5844 					return false;
5845 			foreach(a; attributesEqual)
5846 				if(a[0] !in e.attributes || e.attributes[a[0]] != a[1])
5847 					return false;
5848 			foreach(a; attributesNotEqual)
5849 				// FIXME: maybe it should say null counts... this just bit me.
5850 				// I did [attr][attr!=value] to work around.
5851 				//
5852 				// if it's null, it's not equal, right?
5853 				//if(a[0] !in e.attributes || e.attributes[a[0]] == a[1])
5854 				if(e.getAttribute(a[0]) == a[1])
5855 					return false;
5856 			foreach(a; attributesInclude)
5857 				if(a[0] !in e.attributes || (e.attributes[a[0]].indexOf(a[1]) == -1))
5858 					return false;
5859 			foreach(a; attributesStartsWith)
5860 				if(a[0] !in e.attributes || !e.attributes[a[0]].startsWith(a[1]))
5861 					return false;
5862 			foreach(a; attributesEndsWith)
5863 				if(a[0] !in e.attributes || !e.attributes[a[0]].endsWith(a[1]))
5864 					return false;
5865 			foreach(a; attributesIncludesSeparatedBySpaces)
5866 				if(a[0] !in e.attributes || !matchWithSeparator(e.attributes[a[0]], a[1], " "))
5867 					return false;
5868 			foreach(a; attributesIncludesSeparatedByDashes)
5869 				if(a[0] !in e.attributes || !matchWithSeparator(e.attributes[a[0]], a[1], "-"))
5870 					return false;
5871 			foreach(a; hasSelectors) {
5872 				if(e.querySelector(a) is null)
5873 					return false;
5874 			}
5875 			foreach(a; notSelectors) {
5876 				auto sel = Selector(a);
5877 				if(sel.matchesElement(e))
5878 					return false;
5879 			}
5880 			foreach(a; isSelectors) {
5881 				auto sel = Selector(a);
5882 				if(!sel.matchesElement(e))
5883 					return false;
5884 			}
5885 			foreach(a; whereSelectors) {
5886 				auto sel = Selector(a);
5887 				if(!sel.matchesElement(e))
5888 					return false;
5889 			}
5890 
5891 			foreach(a; nthChild) {
5892 				if(e.parentNode is null)
5893 					return false;
5894 
5895 				auto among = e.parentNode.childElements;
5896 
5897 				if(!a.solvesFor(among, e))
5898 					return false;
5899 			}
5900 			foreach(a; nthOfType) {
5901 				if(e.parentNode is null)
5902 					return false;
5903 
5904 				auto among = e.parentNode.childElements(e.tagName);
5905 
5906 				if(!a.solvesFor(among, e))
5907 					return false;
5908 			}
5909 			foreach(a; nthLastOfType) {
5910 				if(e.parentNode is null)
5911 					return false;
5912 
5913 				auto among = retro(e.parentNode.childElements(e.tagName));
5914 
5915 				if(!a.solvesFor(among, e))
5916 					return false;
5917 			}
5918 
5919 			return true;
5920 		}
5921 	}
5922 
5923 	struct ParsedNth {
5924 		int multiplier;
5925 		int adder;
5926 
5927 		string of;
5928 
5929 		this(string text) {
5930 			auto original = text;
5931 			consumeWhitespace(text);
5932 			if(text.startsWith("odd")) {
5933 				multiplier = 2;
5934 				adder = 1;
5935 
5936 				text = text[3 .. $];
5937 			} else if(text.startsWith("even")) {
5938 				multiplier = 2;
5939 				adder = 1;
5940 
5941 				text = text[4 .. $];
5942 			} else {
5943 				int n = (text.length && text[0] == 'n') ? 1 : parseNumber(text);
5944 				consumeWhitespace(text);
5945 				if(text.length && text[0] == 'n') {
5946 					multiplier = n;
5947 					text = text[1 .. $];
5948 					consumeWhitespace(text);
5949 					if(text.length) {
5950 						if(text[0] == '+') {
5951 							text = text[1 .. $];
5952 							adder = parseNumber(text);
5953 						} else if(text[0] == '-') {
5954 							text = text[1 .. $];
5955 							adder = -parseNumber(text);
5956 						} else if(text[0] == 'o') {
5957 							// continue, this is handled below
5958 						} else
5959 							throw new Exception("invalid css string at " ~ text ~ " in " ~ original);
5960 					}
5961 				} else {
5962 					adder = n;
5963 				}
5964 			}
5965 
5966 			consumeWhitespace(text);
5967 			if(text.startsWith("of")) {
5968 				text = text[2 .. $];
5969 				consumeWhitespace(text);
5970 				of = text[0 .. $];
5971 			}
5972 		}
5973 
5974 		string toString() {
5975 			return format("%dn%s%d%s%s", multiplier, adder >= 0 ? "+" : "", adder, of.length ? " of " : "", of);
5976 		}
5977 
5978 		bool solvesFor(R)(R elements, Element e) {
5979 			int idx = 1;
5980 			bool found = false;
5981 			foreach(ele; elements) {
5982 				if(of.length) {
5983 					auto sel = Selector(of);
5984 					if(!sel.matchesElement(ele))
5985 						continue;
5986 				}
5987 				if(ele is e) {
5988 					found = true;
5989 					break;
5990 				}
5991 				idx++;
5992 			}
5993 			if(!found) return false;
5994 
5995 			// multiplier* n + adder = idx
5996 			// if there is a solution for integral n, it matches
5997 
5998 			idx -= adder;
5999 			if(multiplier) {
6000 				if(idx % multiplier == 0)
6001 					return true;
6002 			} else {
6003 				return idx == 0;
6004 			}
6005 			return false;
6006 		}
6007 
6008 		private void consumeWhitespace(ref string text) {
6009 			while(text.length && text[0] == ' ')
6010 				text = text[1 .. $];
6011 		}
6012 
6013 		private int parseNumber(ref string text) {
6014 			consumeWhitespace(text);
6015 			if(text.length == 0) return 0;
6016 			bool negative = text[0] == '-';
6017 			if(text[0] == '+')
6018 				text = text[1 .. $];
6019 			if(negative) text = text[1 .. $];
6020 			int i = 0;
6021 			while(i < text.length && (text[i] >= '0' && text[i] <= '9'))
6022 				i++;
6023 			if(i == 0)
6024 				return 0;
6025 			int cool = to!int(text[0 .. i]);
6026 			text = text[i .. $];
6027 			return negative ? -cool : cool;
6028 		}
6029 	}
6030 
6031 	// USEFUL
6032 	///.
6033 	Element[] getElementsBySelectorParts(Element start, SelectorPart[] parts) {
6034 		Element[] ret;
6035 		if(!parts.length) {
6036 			return [start]; // the null selector only matches the start point; it
6037 				// is what terminates the recursion
6038 		}
6039 
6040 		auto part = parts[0];
6041 		//writeln("checking ", part, " against ", start, " with ", part.separation);
6042 		switch(part.separation) {
6043 			default: assert(0);
6044 			case -1:
6045 			case 0: // tree
6046 				foreach(e; start.tree) {
6047 					if(part.separation == 0 && start is e)
6048 						continue; // space doesn't match itself!
6049 					if(part.matchElement(e)) {
6050 						ret ~= getElementsBySelectorParts(e, parts[1..$]);
6051 					}
6052 				}
6053 			break;
6054 			case 1: // children
6055 				foreach(e; start.childNodes) {
6056 					if(part.matchElement(e)) {
6057 						ret ~= getElementsBySelectorParts(e, parts[1..$]);
6058 					}
6059 				}
6060 			break;
6061 			case 2: // next-sibling
6062 				auto e = start.nextSibling("*");
6063 				if(part.matchElement(e))
6064 					ret ~= getElementsBySelectorParts(e, parts[1..$]);
6065 			break;
6066 			case 3: // younger sibling
6067 				auto tmp = start.parentNode;
6068 				if(tmp !is null) {
6069 					sizediff_t pos = -1;
6070 					auto children = tmp.childElements;
6071 					foreach(i, child; children) {
6072 						if(child is start) {
6073 							pos = i;
6074 							break;
6075 						}
6076 					}
6077 					assert(pos != -1);
6078 					foreach(e; children[pos+1..$]) {
6079 						if(part.matchElement(e))
6080 							ret ~= getElementsBySelectorParts(e, parts[1..$]);
6081 					}
6082 				}
6083 			break;
6084 			case 4: // immediate parent node, an extension of mine to walk back up the tree
6085 				auto e = start.parentNode;
6086 				if(part.matchElement(e)) {
6087 					ret ~= getElementsBySelectorParts(e, parts[1..$]);
6088 				}
6089 				/*
6090 					Example of usefulness:
6091 
6092 					Consider you have an HTML table. If you want to get all rows that have a th, you can do:
6093 
6094 					table th < tr
6095 
6096 					Get all th descendants of the table, then walk back up the tree to fetch their parent tr nodes
6097 				*/
6098 			break;
6099 			case 5: // any parent note, another extension of mine to go up the tree (backward of the whitespace operator)
6100 				/*
6101 					Like with the < operator, this is best used to find some parent of a particular known element.
6102 
6103 					Say you have an anchor inside a
6104 				*/
6105 		}
6106 
6107 		return ret;
6108 	}
6109 
6110 	/++
6111 		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.
6112 
6113 		See_Also:
6114 			$(LIST
6115 				* [Element.querySelector]
6116 				* [Element.querySelectorAll]
6117 				* [Element.matches]
6118 				* [Element.closest]
6119 				* [Document.querySelector]
6120 				* [Document.querySelectorAll]
6121 			)
6122 	+/
6123 	/// Group: core_functionality
6124 	struct Selector {
6125 		SelectorComponent[] components;
6126 		string original;
6127 		/++
6128 			Parses the selector string and constructs the usable structure.
6129 		+/
6130 		this(string cssSelector) {
6131 			components = parseSelectorString(cssSelector);
6132 			original = cssSelector;
6133 		}
6134 
6135 		/++
6136 			Returns true if the given element matches this selector,
6137 			considered relative to an arbitrary element.
6138 
6139 			You can do a form of lazy [Element.querySelectorAll|querySelectorAll] by using this
6140 			with [std.algorithm.iteration.filter]:
6141 
6142 			---
6143 			Selector sel = Selector("foo > bar");
6144 			auto lazySelectorRange = element.tree.filter!(e => sel.matchElement(e))(document.root);
6145 			---
6146 		+/
6147 		bool matchesElement(Element e, Element relativeTo = null) {
6148 			foreach(component; components)
6149 				if(component.matchElement(e, relativeTo))
6150 					return true;
6151 
6152 			return false;
6153 		}
6154 
6155 		/++
6156 			Reciprocal of [Element.querySelectorAll]
6157 		+/
6158 		Element[] getMatchingElements(Element start) {
6159 			Element[] ret;
6160 			foreach(component; components)
6161 				ret ~= getElementsBySelectorParts(start, component.parts);
6162 			return removeDuplicates(ret);
6163 		}
6164 
6165 		/++
6166 			Like [getMatchingElements], but returns a lazy range. Be careful
6167 			about mutating the dom as you iterate through this.
6168 		+/
6169 		auto getMatchingElementsLazy(Element start, Element relativeTo = null) {
6170 			import std.algorithm.iteration;
6171 			return start.tree.filter!(a => this.matchesElement(a, relativeTo));
6172 		}
6173 
6174 
6175 		/// Returns the string this was built from
6176 		string toString() {
6177 			return original;
6178 		}
6179 
6180 		/++
6181 			Returns a string from the parsed result
6182 
6183 
6184 			(may not match the original, this is mostly for debugging right now but in the future might be useful for pretty-printing)
6185 		+/
6186 		string parsedToString() {
6187 			string ret;
6188 
6189 			foreach(idx, component; components) {
6190 				if(idx) ret ~= ", ";
6191 				ret ~= component.toString();
6192 			}
6193 
6194 			return ret;
6195 		}
6196 	}
6197 
6198 	///.
6199 	struct SelectorComponent {
6200 		///.
6201 		SelectorPart[] parts;
6202 
6203 		///.
6204 		string toString() {
6205 			string ret;
6206 			foreach(part; parts)
6207 				ret ~= part.toString();
6208 			return ret;
6209 		}
6210 
6211 		// USEFUL
6212 		///.
6213 		Element[] getElements(Element start) {
6214 			return removeDuplicates(getElementsBySelectorParts(start, parts));
6215 		}
6216 
6217 		// USEFUL (but not implemented)
6218 		/// If relativeTo == null, it assumes the root of the parent document.
6219 		bool matchElement(Element e, Element relativeTo = null) {
6220 			if(e is null) return false;
6221 			Element where = e;
6222 			int lastSeparation = -1;
6223 
6224 			auto lparts = parts;
6225 
6226 			if(parts.length && parts[0].separation > 0) {
6227 				// if it starts with a non-trivial separator, inject
6228 				// a "*" matcher to act as a root. for cases like document.querySelector("> body")
6229 				// which implies html
6230 
6231 				// however, if it is a child-matching selector and there are no children,
6232 				// bail out early as it obviously cannot match.
6233 				bool hasNonTextChildren = false;
6234 				foreach(c; e.children)
6235 					if(c.nodeType != 3)
6236 						hasNonTextChildren = true;
6237 				if(!hasNonTextChildren)
6238 					return false;
6239 
6240 				// there is probably a MUCH better way to do this.
6241 				auto dummy = SelectorPart.init;
6242 				dummy.tagNameFilter = "*";
6243 				dummy.separation = 0;
6244 				lparts = dummy ~ lparts;
6245 			}
6246 
6247 			foreach(part; retro(lparts)) {
6248 
6249 				 // writeln("matching ", where, " with ", part, " via ", lastSeparation);
6250 				 // writeln(parts);
6251 
6252 				if(lastSeparation == -1) {
6253 					if(!part.matchElement(where))
6254 						return false;
6255 				} else if(lastSeparation == 0) { // generic parent
6256 					// need to go up the whole chain
6257 					where = where.parentNode;
6258 
6259 					while(where !is null) {
6260 						if(part.matchElement(where))
6261 							break;
6262 
6263 						if(where is relativeTo)
6264 							return false;
6265 
6266 						where = where.parentNode;
6267 					}
6268 
6269 					if(where is null)
6270 						return false;
6271 				} else if(lastSeparation == 1) { // the > operator
6272 					where = where.parentNode;
6273 
6274 					if(!part.matchElement(where))
6275 						return false;
6276 				} else if(lastSeparation == 2) { // the + operator
6277 				//writeln("WHERE", where, " ", part);
6278 					where = where.previousSibling("*");
6279 
6280 					if(!part.matchElement(where))
6281 						return false;
6282 				} else if(lastSeparation == 3) { // the ~ operator
6283 					where = where.previousSibling("*");
6284 					while(where !is null) {
6285 						if(part.matchElement(where))
6286 							break;
6287 
6288 						if(where is relativeTo)
6289 							return false;
6290 
6291 						where = where.previousSibling("*");
6292 					}
6293 
6294 					if(where is null)
6295 						return false;
6296 				} else if(lastSeparation == 4) { // my bad idea extension < operator, don't use this anymore
6297 					// FIXME
6298 				}
6299 
6300 				lastSeparation = part.separation;
6301 
6302 				if(where is relativeTo)
6303 					return false; // at end of line, if we aren't done by now, the match fails
6304 			}
6305 			return true; // if we got here, it is a success
6306 		}
6307 
6308 		// the string should NOT have commas. Use parseSelectorString for that instead
6309 		///.
6310 		static SelectorComponent fromString(string selector) {
6311 			return parseSelector(lexSelector(selector));
6312 		}
6313 	}
6314 
6315 	///.
6316 	SelectorComponent[] parseSelectorString(string selector, bool caseSensitiveTags = true) {
6317 		SelectorComponent[] ret;
6318 		auto tokens = lexSelector(selector); // this will parse commas too
6319 		// and now do comma-separated slices (i haz phobosophobia!)
6320 		int parensCount = 0;
6321 		while (tokens.length > 0) {
6322 			size_t end = 0;
6323 			while (end < tokens.length && (parensCount > 0 || tokens[end] != ",")) {
6324 				if(tokens[end] == "(") parensCount++;
6325 				if(tokens[end] == ")") parensCount--;
6326 				++end;
6327 			}
6328 			if (end > 0) ret ~= parseSelector(tokens[0..end], caseSensitiveTags);
6329 			if (tokens.length-end < 2) break;
6330 			tokens = tokens[end+1..$];
6331 		}
6332 		return ret;
6333 	}
6334 
6335 	///.
6336 	SelectorComponent parseSelector(string[] tokens, bool caseSensitiveTags = true) {
6337 		SelectorComponent s;
6338 
6339 		SelectorPart current;
6340 		void commit() {
6341 			// might as well skip null items
6342 			if(!current.isCleanSlateExceptSeparation()) {
6343 				s.parts ~= current;
6344 				current = current.init; // start right over
6345 			}
6346 		}
6347 		enum State {
6348 			Starting,
6349 			ReadingClass,
6350 			ReadingId,
6351 			ReadingAttributeSelector,
6352 			ReadingAttributeComparison,
6353 			ExpectingAttributeCloser,
6354 			ReadingPseudoClass,
6355 			ReadingAttributeValue,
6356 
6357 			SkippingFunctionalSelector,
6358 		}
6359 		State state = State.Starting;
6360 		string attributeName, attributeValue, attributeComparison;
6361 		int parensCount;
6362 		foreach(idx, token; tokens) {
6363 			string readFunctionalSelector() {
6364 				string s;
6365 				if(tokens[idx + 1] != "(")
6366 					throw new Exception("parse error");
6367 				int pc = 1;
6368 				foreach(t; tokens[idx + 2 .. $]) {
6369 					if(t == "(")
6370 						pc++;
6371 					if(t == ")")
6372 						pc--;
6373 					if(pc == 0)
6374 						break;
6375 					s ~= t;
6376 				}
6377 
6378 				return s;
6379 			}
6380 
6381 			sizediff_t tid = -1;
6382 			foreach(i, item; selectorTokens)
6383 				if(token == item) {
6384 					tid = i;
6385 					break;
6386 				}
6387 			final switch(state) {
6388 				case State.Starting: // fresh, might be reading an operator or a tagname
6389 					if(tid == -1) {
6390 						if(!caseSensitiveTags)
6391 							token = token.toLower();
6392 
6393 						if(current.isCleanSlateExceptSeparation()) {
6394 							current.tagNameFilter = token;
6395 							// default thing, see comment under "*" below
6396 							if(current.separation == -1) current.separation = 0;
6397 						} else {
6398 							// if it was already set, we must see two thingies
6399 							// separated by whitespace...
6400 							commit();
6401 							current.separation = 0; // tree
6402 							current.tagNameFilter = token;
6403 						}
6404 					} else {
6405 						// Selector operators
6406 						switch(token) {
6407 							case "*":
6408 								current.tagNameFilter = "*";
6409 								// the idea here is if we haven't actually set a separation
6410 								// yet (e.g. the > operator), it should assume the generic
6411 								// whitespace (descendant) mode to avoid matching self with -1
6412 								if(current.separation == -1) current.separation = 0;
6413 							break;
6414 							case " ":
6415 								// If some other separation has already been set,
6416 								// this is irrelevant whitespace, so we should skip it.
6417 								// this happens in the case of "foo > bar" for example.
6418 								if(current.isCleanSlateExceptSeparation() && current.separation > 0)
6419 									continue;
6420 								commit();
6421 								current.separation = 0; // tree
6422 							break;
6423 							case ">>":
6424 								commit();
6425 								current.separation = 0; // alternate syntax for tree from html5 css
6426 							break;
6427 							case ">":
6428 								commit();
6429 								current.separation = 1; // child
6430 							break;
6431 							case "+":
6432 								commit();
6433 								current.separation = 2; // sibling directly after
6434 							break;
6435 							case "~":
6436 								commit();
6437 								current.separation = 3; // any sibling after
6438 							break;
6439 							case "<":
6440 								commit();
6441 								current.separation = 4; // immediate parent of
6442 							break;
6443 							case "[":
6444 								state = State.ReadingAttributeSelector;
6445 								if(current.separation == -1) current.separation = 0;
6446 							break;
6447 							case ".":
6448 								state = State.ReadingClass;
6449 								if(current.separation == -1) current.separation = 0;
6450 							break;
6451 							case "#":
6452 								state = State.ReadingId;
6453 								if(current.separation == -1) current.separation = 0;
6454 							break;
6455 							case ":":
6456 							case "::":
6457 								state = State.ReadingPseudoClass;
6458 								if(current.separation == -1) current.separation = 0;
6459 							break;
6460 
6461 							default:
6462 								assert(0, token);
6463 						}
6464 					}
6465 				break;
6466 				case State.ReadingClass:
6467 					current.attributesIncludesSeparatedBySpaces ~= ["class", token];
6468 					state = State.Starting;
6469 				break;
6470 				case State.ReadingId:
6471 					current.attributesEqual ~= ["id", token];
6472 					state = State.Starting;
6473 				break;
6474 				case State.ReadingPseudoClass:
6475 					switch(token) {
6476 						case "first-of-type":
6477 							current.firstOfType = true;
6478 						break;
6479 						case "last-of-type":
6480 							current.lastOfType = true;
6481 						break;
6482 						case "only-of-type":
6483 							current.firstOfType = true;
6484 							current.lastOfType = true;
6485 						break;
6486 						case "first-child":
6487 							current.firstChild = true;
6488 						break;
6489 						case "last-child":
6490 							current.lastChild = true;
6491 						break;
6492 						case "only-child":
6493 							current.firstChild = true;
6494 							current.lastChild = true;
6495 						break;
6496 						case "scope":
6497 							current.scopeElement = true;
6498 						break;
6499 						case "empty":
6500 							// one with no children
6501 							current.emptyElement = true;
6502 						break;
6503 						case "whitespace-only":
6504 							current.whitespaceOnly = true;
6505 						break;
6506 						case "link":
6507 							current.attributesPresent ~= "href";
6508 						break;
6509 						case "root":
6510 							current.rootElement = true;
6511 						break;
6512 						case "nth-child":
6513 							current.nthChild ~= ParsedNth(readFunctionalSelector());
6514 							state = State.SkippingFunctionalSelector;
6515 						continue;
6516 						case "nth-of-type":
6517 							current.nthOfType ~= ParsedNth(readFunctionalSelector());
6518 							state = State.SkippingFunctionalSelector;
6519 						continue;
6520 						case "nth-last-of-type":
6521 							current.nthLastOfType ~= ParsedNth(readFunctionalSelector());
6522 							state = State.SkippingFunctionalSelector;
6523 						continue;
6524 						case "is":
6525 							state = State.SkippingFunctionalSelector;
6526 							current.isSelectors ~= readFunctionalSelector();
6527 						continue; // now the rest of the parser skips past the parens we just handled
6528 						case "where":
6529 							state = State.SkippingFunctionalSelector;
6530 							current.whereSelectors ~= readFunctionalSelector();
6531 						continue; // now the rest of the parser skips past the parens we just handled
6532 						case "not":
6533 							state = State.SkippingFunctionalSelector;
6534 							current.notSelectors ~= readFunctionalSelector();
6535 						continue; // now the rest of the parser skips past the parens we just handled
6536 						case "has":
6537 							state = State.SkippingFunctionalSelector;
6538 							current.hasSelectors ~= readFunctionalSelector();
6539 						continue; // now the rest of the parser skips past the parens we just handled
6540 						// back to standards though not quite right lol
6541 						case "disabled":
6542 							current.attributesPresent ~= "disabled";
6543 						break;
6544 						case "checked":
6545 							current.attributesPresent ~= "checked";
6546 						break;
6547 
6548 						case "visited", "active", "hover", "target", "focus", "selected":
6549 							current.attributesPresent ~= "nothing";
6550 							// FIXME
6551 						/+
6552 						// extensions not implemented
6553 						//case "text": // takes the text in the element and wraps it in an element, returning it
6554 						+/
6555 							goto case;
6556 						case "before", "after":
6557 							current.attributesPresent ~= "FIXME";
6558 
6559 						break;
6560 						// My extensions
6561 						case "odd-child":
6562 							current.oddChild = true;
6563 						break;
6564 						case "even-child":
6565 							current.evenChild = true;
6566 						break;
6567 						default:
6568 							//if(token.indexOf("lang") == -1)
6569 							//assert(0, token);
6570 						break;
6571 					}
6572 					state = State.Starting;
6573 				break;
6574 				case State.SkippingFunctionalSelector:
6575 					if(token == "(") {
6576 						parensCount++;
6577 					} else if(token == ")") {
6578 						parensCount--;
6579 					}
6580 
6581 					if(parensCount == 0)
6582 						state = State.Starting;
6583 				break;
6584 				case State.ReadingAttributeSelector:
6585 					attributeName = token;
6586 					attributeComparison = null;
6587 					attributeValue = null;
6588 					state = State.ReadingAttributeComparison;
6589 				break;
6590 				case State.ReadingAttributeComparison:
6591 					// FIXME: these things really should be quotable in the proper lexer...
6592 					if(token != "]") {
6593 						if(token.indexOf("=") == -1) {
6594 							// not a comparison; consider it
6595 							// part of the attribute
6596 							attributeValue ~= token;
6597 						} else {
6598 							attributeComparison = token;
6599 							state = State.ReadingAttributeValue;
6600 						}
6601 						break;
6602 					}
6603 					goto case;
6604 				case State.ExpectingAttributeCloser:
6605 					if(token != "]") {
6606 						// not the closer; consider it part of comparison
6607 						if(attributeComparison == "")
6608 							attributeName ~= token;
6609 						else
6610 							attributeValue ~= token;
6611 						break;
6612 					}
6613 
6614 					// Selector operators
6615 					switch(attributeComparison) {
6616 						default: assert(0);
6617 						case "":
6618 							current.attributesPresent ~= attributeName;
6619 						break;
6620 						case "=":
6621 							current.attributesEqual ~= [attributeName, attributeValue];
6622 						break;
6623 						case "|=":
6624 							current.attributesIncludesSeparatedByDashes ~= [attributeName, attributeValue];
6625 						break;
6626 						case "~=":
6627 							current.attributesIncludesSeparatedBySpaces ~= [attributeName, attributeValue];
6628 						break;
6629 						case "$=":
6630 							current.attributesEndsWith ~= [attributeName, attributeValue];
6631 						break;
6632 						case "^=":
6633 							current.attributesStartsWith ~= [attributeName, attributeValue];
6634 						break;
6635 						case "*=":
6636 							current.attributesInclude ~= [attributeName, attributeValue];
6637 						break;
6638 						case "!=":
6639 							current.attributesNotEqual ~= [attributeName, attributeValue];
6640 						break;
6641 					}
6642 
6643 					state = State.Starting;
6644 				break;
6645 				case State.ReadingAttributeValue:
6646 					attributeValue = token;
6647 					state = State.ExpectingAttributeCloser;
6648 				break;
6649 			}
6650 		}
6651 
6652 		commit();
6653 
6654 		return s;
6655 	}
6656 
6657 ///.
6658 Element[] removeDuplicates(Element[] input) {
6659 	Element[] ret;
6660 
6661 	bool[Element] already;
6662 	foreach(e; input) {
6663 		if(e in already) continue;
6664 		already[e] = true;
6665 		ret ~= e;
6666 	}
6667 
6668 	return ret;
6669 }
6670 
6671 // done with CSS selector handling
6672 
6673 
6674 // FIXME: use the better parser from html.d
6675 /// This is probably not useful to you unless you're writing a browser or something like that.
6676 /// It represents a *computed* style, like what the browser gives you after applying stylesheets, inline styles, and html attributes.
6677 /// From here, you can start to make a layout engine for the box model and have a css aware browser.
6678 class CssStyle {
6679 	///.
6680 	this(string rule, string content) {
6681 		rule = rule.strip();
6682 		content = content.strip();
6683 
6684 		if(content.length == 0)
6685 			return;
6686 
6687 		originatingRule = rule;
6688 		originatingSpecificity = getSpecificityOfRule(rule); // FIXME: if there's commas, this won't actually work!
6689 
6690 		foreach(part; content.split(";")) {
6691 			part = part.strip();
6692 			if(part.length == 0)
6693 				continue;
6694 			auto idx = part.indexOf(":");
6695 			if(idx == -1)
6696 				continue;
6697 				//throw new Exception("Bad css rule (no colon): " ~ part);
6698 
6699 			Property p;
6700 
6701 			p.name = part[0 .. idx].strip();
6702 			p.value = part[idx + 1 .. $].replace("! important", "!important").replace("!important", "").strip(); // FIXME don't drop important
6703 			p.givenExplicitly = true;
6704 			p.specificity = originatingSpecificity;
6705 
6706 			properties ~= p;
6707 		}
6708 
6709 		foreach(property; properties)
6710 			expandShortForm(property, originatingSpecificity);
6711 	}
6712 
6713 	///.
6714 	Specificity getSpecificityOfRule(string rule) {
6715 		Specificity s;
6716 		if(rule.length == 0) { // inline
6717 		//	s.important = 2;
6718 		} else {
6719 			// FIXME
6720 		}
6721 
6722 		return s;
6723 	}
6724 
6725 	string originatingRule; ///.
6726 	Specificity originatingSpecificity; ///.
6727 
6728 	///.
6729 	union Specificity {
6730 		uint score; ///.
6731 		// version(little_endian)
6732 		///.
6733 		struct {
6734 			ubyte tags; ///.
6735 			ubyte classes; ///.
6736 			ubyte ids; ///.
6737 			ubyte important; /// 0 = none, 1 = stylesheet author, 2 = inline style, 3 = user important
6738 		}
6739 	}
6740 
6741 	///.
6742 	struct Property {
6743 		bool givenExplicitly; /// this is false if for example the user said "padding" and this is "padding-left"
6744 		string name; ///.
6745 		string value; ///.
6746 		Specificity specificity; ///.
6747 		// do we care about the original source rule?
6748 	}
6749 
6750 	///.
6751 	Property[] properties;
6752 
6753 	///.
6754 	string opDispatch(string nameGiven)(string value = null) if(nameGiven != "popFront") {
6755 		string name = unCamelCase(nameGiven);
6756 		if(value is null)
6757 			return getValue(name);
6758 		else
6759 			return setValue(name, value, 0x02000000 /* inline specificity */);
6760 	}
6761 
6762 	/// takes dash style name
6763 	string getValue(string name) {
6764 		foreach(property; properties)
6765 			if(property.name == name)
6766 				return property.value;
6767 		return null;
6768 	}
6769 
6770 	/// takes dash style name
6771 	string setValue(string name, string value, Specificity newSpecificity, bool explicit = true) {
6772 		value = value.replace("! important", "!important");
6773 		if(value.indexOf("!important") != -1) {
6774 			newSpecificity.important = 1; // FIXME
6775 			value = value.replace("!important", "").strip();
6776 		}
6777 
6778 		foreach(ref property; properties)
6779 			if(property.name == name) {
6780 				if(newSpecificity.score >= property.specificity.score) {
6781 					property.givenExplicitly = explicit;
6782 					expandShortForm(property, newSpecificity);
6783 					return (property.value = value);
6784 				} else {
6785 					if(name == "display")
6786 					{}//writeln("Not setting ", name, " to ", value, " because ", newSpecificity.score, " < ", property.specificity.score);
6787 					return value; // do nothing - the specificity is too low
6788 				}
6789 			}
6790 
6791 		// it's not here...
6792 
6793 		Property p;
6794 		p.givenExplicitly = true;
6795 		p.name = name;
6796 		p.value = value;
6797 		p.specificity = originatingSpecificity;
6798 
6799 		properties ~= p;
6800 		expandShortForm(p, originatingSpecificity);
6801 
6802 		return value;
6803 	}
6804 
6805 	private void expandQuadShort(string name, string value, Specificity specificity) {
6806 		auto parts = value.split(" ");
6807 		switch(parts.length) {
6808 			case 1:
6809 				setValue(name ~"-left", parts[0], specificity, false);
6810 				setValue(name ~"-right", parts[0], specificity, false);
6811 				setValue(name ~"-top", parts[0], specificity, false);
6812 				setValue(name ~"-bottom", parts[0], specificity, false);
6813 			break;
6814 			case 2:
6815 				setValue(name ~"-left", parts[1], specificity, false);
6816 				setValue(name ~"-right", parts[1], specificity, false);
6817 				setValue(name ~"-top", parts[0], specificity, false);
6818 				setValue(name ~"-bottom", parts[0], specificity, false);
6819 			break;
6820 			case 3:
6821 				setValue(name ~"-top", parts[0], specificity, false);
6822 				setValue(name ~"-right", parts[1], specificity, false);
6823 				setValue(name ~"-bottom", parts[2], specificity, false);
6824 				setValue(name ~"-left", parts[2], specificity, false);
6825 
6826 			break;
6827 			case 4:
6828 				setValue(name ~"-top", parts[0], specificity, false);
6829 				setValue(name ~"-right", parts[1], specificity, false);
6830 				setValue(name ~"-bottom", parts[2], specificity, false);
6831 				setValue(name ~"-left", parts[3], specificity, false);
6832 			break;
6833 			default:
6834 				assert(0, value);
6835 		}
6836 	}
6837 
6838 	///.
6839 	void expandShortForm(Property p, Specificity specificity) {
6840 		switch(p.name) {
6841 			case "margin":
6842 			case "padding":
6843 				expandQuadShort(p.name, p.value, specificity);
6844 			break;
6845 			case "border":
6846 			case "outline":
6847 				setValue(p.name ~ "-left", p.value, specificity, false);
6848 				setValue(p.name ~ "-right", p.value, specificity, false);
6849 				setValue(p.name ~ "-top", p.value, specificity, false);
6850 				setValue(p.name ~ "-bottom", p.value, specificity, false);
6851 			break;
6852 
6853 			case "border-top":
6854 			case "border-bottom":
6855 			case "border-left":
6856 			case "border-right":
6857 			case "outline-top":
6858 			case "outline-bottom":
6859 			case "outline-left":
6860 			case "outline-right":
6861 
6862 			default: {}
6863 		}
6864 	}
6865 
6866 	///.
6867 	override string toString() {
6868 		string ret;
6869 		if(originatingRule.length)
6870 			ret = originatingRule ~ " {";
6871 
6872 		foreach(property; properties) {
6873 			if(!property.givenExplicitly)
6874 				continue; // skip the inferred shit
6875 
6876 			if(originatingRule.length)
6877 				ret ~= "\n\t";
6878 			else
6879 				ret ~= " ";
6880 
6881 			ret ~= property.name ~ ": " ~ property.value ~ ";";
6882 		}
6883 
6884 		if(originatingRule.length)
6885 			ret ~= "\n}\n";
6886 
6887 		return ret;
6888 	}
6889 }
6890 
6891 string cssUrl(string url) {
6892 	return "url(\"" ~ url ~ "\")";
6893 }
6894 
6895 /// This probably isn't useful, unless you're writing a browser or something like that.
6896 /// You might want to look at arsd.html for css macro, nesting, etc., or just use standard css
6897 /// as text.
6898 ///
6899 /// The idea, however, is to represent a kind of CSS object model, complete with specificity,
6900 /// that you can apply to your documents to build the complete computedStyle object.
6901 class StyleSheet {
6902 	///.
6903 	CssStyle[] rules;
6904 
6905 	///.
6906 	this(string source) {
6907 		// FIXME: handle @ rules and probably could improve lexer
6908 		// add nesting?
6909 		int state;
6910 		string currentRule;
6911 		string currentValue;
6912 
6913 		string* currentThing = &currentRule;
6914 		foreach(c; source) {
6915 			handle: switch(state) {
6916 				default: assert(0);
6917 				case 0: // starting - we assume we're reading a rule
6918 					switch(c) {
6919 						case '@':
6920 							state = 4;
6921 						break;
6922 						case '/':
6923 							state = 1;
6924 						break;
6925 						case '{':
6926 							currentThing = &currentValue;
6927 						break;
6928 						case '}':
6929 							if(currentThing is &currentValue) {
6930 								rules ~= new CssStyle(currentRule, currentValue);
6931 
6932 								currentRule = "";
6933 								currentValue = "";
6934 
6935 								currentThing = &currentRule;
6936 							} else {
6937 								// idk what is going on here.
6938 								// check sveit.com to reproduce
6939 								currentRule = "";
6940 								currentValue = "";
6941 							}
6942 						break;
6943 						default:
6944 							(*currentThing) ~= c;
6945 					}
6946 				break;
6947 				case 1: // expecting *
6948 					if(c == '*')
6949 						state = 2;
6950 					else {
6951 						state = 0;
6952 						(*currentThing) ~= "/" ~ c;
6953 					}
6954 				break;
6955 				case 2: // inside comment
6956 					if(c == '*')
6957 						state = 3;
6958 				break;
6959 				case 3: // expecting / to end comment
6960 					if(c == '/')
6961 						state = 0;
6962 					else
6963 						state = 2; // it's just a comment so no need to append
6964 				break;
6965 				case 4:
6966 					if(c == '{')
6967 						state = 5;
6968 					if(c == ';')
6969 						state = 0; // just skipping import
6970 				break;
6971 				case 5:
6972 					if(c == '}')
6973 						state = 0; // skipping font face probably
6974 			}
6975 		}
6976 	}
6977 
6978 	/// Run through the document and apply this stylesheet to it. The computedStyle member will be accurate after this call
6979 	void apply(Document document) {
6980 		foreach(rule; rules) {
6981 			if(rule.originatingRule.length == 0)
6982 				continue; // this shouldn't happen here in a stylesheet
6983 			foreach(element; document.querySelectorAll(rule.originatingRule)) {
6984 				// note: this should be a different object than the inline style
6985 				// since givenExplicitly is likely destroyed here
6986 				auto current = element.computedStyle;
6987 
6988 				foreach(item; rule.properties)
6989 					current.setValue(item.name, item.value, item.specificity);
6990 			}
6991 		}
6992 	}
6993 }
6994 
6995 
6996 /// This is kinda private; just a little utility container for use by the ElementStream class.
6997 final class Stack(T) {
6998 	this() {
6999 		internalLength = 0;
7000 		arr = initialBuffer[];
7001 	}
7002 
7003 	///.
7004 	void push(T t) {
7005 		if(internalLength >= arr.length) {
7006 			auto oldarr = arr;
7007 			if(arr.length < 4096)
7008 				arr = new T[arr.length * 2];
7009 			else
7010 				arr = new T[arr.length + 4096];
7011 			arr[0 .. oldarr.length] = oldarr[];
7012 		}
7013 
7014 		arr[internalLength] = t;
7015 		internalLength++;
7016 	}
7017 
7018 	///.
7019 	T pop() {
7020 		assert(internalLength);
7021 		internalLength--;
7022 		return arr[internalLength];
7023 	}
7024 
7025 	///.
7026 	T peek() {
7027 		assert(internalLength);
7028 		return arr[internalLength - 1];
7029 	}
7030 
7031 	///.
7032 	@property bool empty() {
7033 		return internalLength ? false : true;
7034 	}
7035 
7036 	///.
7037 	private T[] arr;
7038 	private size_t internalLength;
7039 	private T[64] initialBuffer;
7040 	// 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),
7041 	// using this saves us a bunch of trips to the GC. In my last profiling, I got about a 50x improvement in the push()
7042 	// function thanks to this, and push() was actually one of the slowest individual functions in the code!
7043 }
7044 
7045 /// 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.
7046 final class ElementStream {
7047 
7048 	///.
7049 	@property Element front() {
7050 		return current.element;
7051 	}
7052 
7053 	/// Use Element.tree instead.
7054 	this(Element start) {
7055 		current.element = start;
7056 		current.childPosition = -1;
7057 		isEmpty = false;
7058 		stack = new Stack!(Current);
7059 	}
7060 
7061 	/*
7062 		Handle it
7063 		handle its children
7064 
7065 	*/
7066 
7067 	///.
7068 	void popFront() {
7069 	    more:
7070 	    	if(isEmpty) return;
7071 
7072 		// FIXME: the profiler says this function is somewhat slow (noticeable because it can be called a lot of times)
7073 
7074 		current.childPosition++;
7075 		if(current.childPosition >= current.element.children.length) {
7076 			if(stack.empty())
7077 				isEmpty = true;
7078 			else {
7079 				current = stack.pop();
7080 				goto more;
7081 			}
7082 		} else {
7083 			stack.push(current);
7084 			current.element = current.element.children[current.childPosition];
7085 			current.childPosition = -1;
7086 		}
7087 	}
7088 
7089 	/// 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.
7090 	void currentKilled() {
7091 		if(stack.empty) // should never happen
7092 			isEmpty = true;
7093 		else {
7094 			current = stack.pop();
7095 			current.childPosition--; // when it is killed, the parent is brought back a lil so when we popFront, this is then right
7096 		}
7097 	}
7098 
7099 	///.
7100 	@property bool empty() {
7101 		return isEmpty;
7102 	}
7103 
7104 	private:
7105 
7106 	struct Current {
7107 		Element element;
7108 		int childPosition;
7109 	}
7110 
7111 	Current current;
7112 
7113 	Stack!(Current) stack;
7114 
7115 	bool isEmpty;
7116 }
7117 
7118 
7119 
7120 // unbelievable.
7121 // 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.
7122 sizediff_t indexOfBytes(immutable(ubyte)[] haystack, immutable(ubyte)[] needle) {
7123 	static import std.algorithm;
7124 	auto found = std.algorithm.find(haystack, needle);
7125 	if(found.length == 0)
7126 		return -1;
7127 	return haystack.length - found.length;
7128 }
7129 
7130 private T[] insertAfter(T)(T[] arr, int position, T[] what) {
7131 	assert(position < arr.length);
7132 	T[] ret;
7133 	ret.length = arr.length + what.length;
7134 	int a = 0;
7135 	foreach(i; arr[0..position+1])
7136 		ret[a++] = i;
7137 
7138 	foreach(i; what)
7139 		ret[a++] = i;
7140 
7141 	foreach(i; arr[position+1..$])
7142 		ret[a++] = i;
7143 
7144 	return ret;
7145 }
7146 
7147 package bool isInArray(T)(T item, T[] arr) {
7148 	foreach(i; arr)
7149 		if(item == i)
7150 			return true;
7151 	return false;
7152 }
7153 
7154 private string[string] aadup(in string[string] arr) {
7155 	string[string] ret;
7156 	foreach(k, v; arr)
7157 		ret[k] = v;
7158 	return ret;
7159 }
7160 
7161 
7162 
7163 
7164 
7165 
7166 
7167 
7168 
7169 
7170 
7171 
7172 
7173 
7174 
7175 // 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)
7176 
7177 immutable string[] availableEntities =
7178 ["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", 
7179 "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", 
7180 "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", 
7181 "ClockwiseContourIntegral", "ClockwiseContourIntegral", "CloseCurlyDoubleQuote", "CloseCurlyDoubleQuote", "CloseCurlyQuote", "CloseCurlyQuote", "Colon", "Colon", "Colone", "Colone", "Congruent", "Congruent", "Conint", "Conint", "ContourIntegral", "ContourIntegral", "Copf", "Copf", "Coproduct", "Coproduct", "CounterClockwiseContourIntegral", 
7182 "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", 
7183 "DiacriticalAcute", "DiacriticalAcute", "DiacriticalDot", "DiacriticalDot", "DiacriticalDoubleAcute", "DiacriticalDoubleAcute", "DiacriticalGrave", "DiacriticalGrave", "DiacriticalTilde", "DiacriticalTilde", "Diamond", "Diamond", "DifferentialD", "DifferentialD", "Dopf", "Dopf", "Dot", "Dot", "DotDot", "DotDot", "DotEqual", 
7184 "DotEqual", "DoubleContourIntegral", "DoubleContourIntegral", "DoubleDot", "DoubleDot", "DoubleDownArrow", "DoubleDownArrow", "DoubleLeftArrow", "DoubleLeftArrow", "DoubleLeftRightArrow", "DoubleLeftRightArrow", "DoubleLeftTee", "DoubleLeftTee", "DoubleLongLeftArrow", "DoubleLongLeftArrow", "DoubleLongLeftRightArrow", 
7185 "DoubleLongLeftRightArrow", "DoubleLongRightArrow", "DoubleLongRightArrow", "DoubleRightArrow", "DoubleRightArrow", "DoubleRightTee", "DoubleRightTee", "DoubleUpArrow", "DoubleUpArrow", "DoubleUpDownArrow", "DoubleUpDownArrow", "DoubleVerticalBar", "DoubleVerticalBar", "DownArrow", "DownArrow", "DownArrowBar", "DownArrowBar", 
7186 "DownArrowUpArrow", "DownArrowUpArrow", "DownBreve", "DownBreve", "DownLeftRightVector", "DownLeftRightVector", "DownLeftTeeVector", "DownLeftTeeVector", "DownLeftVector", "DownLeftVector", "DownLeftVectorBar", "DownLeftVectorBar", "DownRightTeeVector", "DownRightTeeVector", "DownRightVector", "DownRightVector", "DownRightVectorBar", 
7187 "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", 
7188 "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", 
7189 "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", 
7190 "Gcy", "Gcy", "Gdot", "Gdot", "Gfr", "Gfr", "Gg", "Gg", "Gopf", "Gopf", "GreaterEqual", "GreaterEqual", "GreaterEqualLess", "GreaterEqualLess", "GreaterFullEqual", "GreaterFullEqual", "GreaterGreater", "GreaterGreater", "GreaterLess", "GreaterLess", "GreaterSlantEqual", "GreaterSlantEqual", "GreaterTilde", "GreaterTilde", 
7191 "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", 
7192 "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", 
7193 "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", 
7194 "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", 
7195 "LeftArrowBar", "LeftArrowRightArrow", "LeftArrowRightArrow", "LeftCeiling", "LeftCeiling", "LeftDoubleBracket", "LeftDoubleBracket", "LeftDownTeeVector", "LeftDownTeeVector", "LeftDownVector", "LeftDownVector", "LeftDownVectorBar", "LeftDownVectorBar", "LeftFloor", "LeftFloor", "LeftRightArrow", "LeftRightArrow", "LeftRightVector", 
7196 "LeftRightVector", "LeftTee", "LeftTee", "LeftTeeArrow", "LeftTeeArrow", "LeftTeeVector", "LeftTeeVector", "LeftTriangle", "LeftTriangle", "LeftTriangleBar", "LeftTriangleBar", "LeftTriangleEqual", "LeftTriangleEqual", "LeftUpDownVector", "LeftUpDownVector", "LeftUpTeeVector", "LeftUpTeeVector", "LeftUpVector", "LeftUpVector", 
7197 "LeftUpVectorBar", "LeftUpVectorBar", "LeftVector", "LeftVector", "LeftVectorBar", "LeftVectorBar", "Leftarrow", "Leftarrow", "Leftrightarrow", "Leftrightarrow", "LessEqualGreater", "LessEqualGreater", "LessFullEqual", "LessFullEqual", "LessGreater", "LessGreater", "LessLess", "LessLess", "LessSlantEqual", "LessSlantEqual", 
7198 "LessTilde", "LessTilde", "Lfr", "Lfr", "Ll", "Ll", "Lleftarrow", "Lleftarrow", "Lmidot", "Lmidot", "LongLeftArrow", "LongLeftArrow", "LongLeftRightArrow", "LongLeftRightArrow", "LongRightArrow", "LongRightArrow", "Longleftarrow", "Longleftarrow", "Longleftrightarrow", "Longleftrightarrow", "Longrightarrow", "Longrightarrow", 
7199 "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", 
7200 "NJcy", "NJcy", "Nacute", "Nacute", "Ncaron", "Ncaron", "Ncedil", "Ncedil", "Ncy", "Ncy", "NegativeMediumSpace", "NegativeMediumSpace", "NegativeThickSpace", "NegativeThickSpace", "NegativeThinSpace", "NegativeThinSpace", "NegativeVeryThinSpace", "NegativeVeryThinSpace", "NestedGreaterGreater", "NestedGreaterGreater", 
7201 "NestedLessLess", "NestedLessLess", "NewLine", "NewLine", "Nfr", "Nfr", "NoBreak", "NoBreak", "NonBreakingSpace", "NonBreakingSpace", "Nopf", "Nopf", "Not", "Not", "NotCongruent", "NotCongruent", "NotCupCap", "NotCupCap", "NotDoubleVerticalBar", "NotDoubleVerticalBar", "NotElement", "NotElement", "NotEqual", "NotEqual", 
7202 "NotExists", "NotExists", "NotGreater", "NotGreater", "NotGreaterEqual", "NotGreaterEqual", "NotGreaterLess", "NotGreaterLess", "NotGreaterTilde", "NotGreaterTilde", "NotLeftTriangle", "NotLeftTriangle", "NotLeftTriangleEqual", "NotLeftTriangleEqual", "NotLess", "NotLess", "NotLessEqual", "NotLessEqual", "NotLessGreater", 
7203 "NotLessGreater", "NotLessTilde", "NotLessTilde", "NotPrecedes", "NotPrecedes", "NotPrecedesSlantEqual", "NotPrecedesSlantEqual", "NotReverseElement", "NotReverseElement", "NotRightTriangle", "NotRightTriangle", "NotRightTriangleEqual", "NotRightTriangleEqual", "NotSquareSubsetEqual", "NotSquareSubsetEqual", "NotSquareSupersetEqual", 
7204 "NotSquareSupersetEqual", "NotSubsetEqual", "NotSubsetEqual", "NotSucceeds", "NotSucceeds", "NotSucceedsSlantEqual", "NotSucceedsSlantEqual", "NotSupersetEqual", "NotSupersetEqual", "NotTilde", "NotTilde", "NotTildeEqual", "NotTildeEqual", "NotTildeFullEqual", "NotTildeFullEqual", "NotTildeTilde", "NotTildeTilde", "NotVerticalBar", 
7205 "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", 
7206 "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", 
7207 "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", 
7208 "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", 
7209 "ReverseUpEquilibrium", "ReverseUpEquilibrium", "Rfr", "Rfr", "Rho", "Rho", "RightAngleBracket", "RightAngleBracket", "RightArrow", "RightArrow", "RightArrowBar", "RightArrowBar", "RightArrowLeftArrow", "RightArrowLeftArrow", "RightCeiling", "RightCeiling", "RightDoubleBracket", "RightDoubleBracket", "RightDownTeeVector", 
7210 "RightDownTeeVector", "RightDownVector", "RightDownVector", "RightDownVectorBar", "RightDownVectorBar", "RightFloor", "RightFloor", "RightTee", "RightTee", "RightTeeArrow", "RightTeeArrow", "RightTeeVector", "RightTeeVector", "RightTriangle", "RightTriangle", "RightTriangleBar", "RightTriangleBar", "RightTriangleEqual", 
7211 "RightTriangleEqual", "RightUpDownVector", "RightUpDownVector", "RightUpTeeVector", "RightUpTeeVector", "RightUpVector", "RightUpVector", "RightUpVectorBar", "RightUpVectorBar", "RightVector", "RightVector", "RightVectorBar", "RightVectorBar", "Rightarrow", "Rightarrow", "Ropf", "Ropf", "RoundImplies", "RoundImplies", 
7212 "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", 
7213 "ShortRightArrow", "ShortRightArrow", "ShortUpArrow", "ShortUpArrow", "Sigma", "Sigma", "SmallCircle", "SmallCircle", "Sopf", "Sopf", "Sqrt", "Sqrt", "Square", "Square", "SquareIntersection", "SquareIntersection", "SquareSubset", "SquareSubset", "SquareSubsetEqual", "SquareSubsetEqual", "SquareSuperset", "SquareSuperset", 
7214 "SquareSupersetEqual", "SquareSupersetEqual", "SquareUnion", "SquareUnion", "Sscr", "Sscr", "Star", "Star", "Sub", "Sub", "Subset", "Subset", "SubsetEqual", "SubsetEqual", "Succeeds", "Succeeds", "SucceedsEqual", "SucceedsEqual", "SucceedsSlantEqual", "SucceedsSlantEqual", "SucceedsTilde", "SucceedsTilde", "SuchThat", 
7215 "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", 
7216 "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", 
7217 "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", 
7218 "UpArrowBar", "UpArrowDownArrow", "UpArrowDownArrow", "UpDownArrow", "UpDownArrow", "UpEquilibrium", "UpEquilibrium", "UpTee", "UpTee", "UpTeeArrow", "UpTeeArrow", "Uparrow", "Uparrow", "Updownarrow", "Updownarrow", "UpperLeftArrow", "UpperLeftArrow", "UpperRightArrow", "UpperRightArrow", "Upsi", "Upsi", "Upsilon", "Upsilon", 
7219 "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", 
7220 "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", 
7221 "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", 
7222 "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", 
7223 "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", 
7224 "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", 
7225 "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", 
7226 "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", 
7227 "bigsqcup", "bigsqcup", "bigstar", "bigstar", "bigtriangledown", "bigtriangledown", "bigtriangleup", "bigtriangleup", "biguplus", "biguplus", "bigvee", "bigvee", "bigwedge", "bigwedge", "bkarow", "bkarow", "blacklozenge", "blacklozenge", "blacksquare", "blacksquare", "blacktriangle", "blacktriangle", "blacktriangledown", 
7228 "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", 
7229 "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", 
7230 "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", 
7231 "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", 
7232 "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", 
7233 "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", 
7234 "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", 
7235 "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", 
7236 "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", 
7237 "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", 
7238 "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", 
7239 "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", 
7240 "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", 
7241 "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", 
7242 "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", 
7243 "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", 
7244 "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", 
7245 "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", 
7246 "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", 
7247 "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", 
7248 "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", 
7249 "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", 
7250 "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", 
7251 "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", 
7252 "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", 
7253 "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", 
7254 "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", 
7255 "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", 
7256 "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", 
7257 "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", 
7258 "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", 
7259 "leftarrow", "leftarrowtail", "leftarrowtail", "leftharpoondown", "leftharpoondown", "leftharpoonup", "leftharpoonup", "leftleftarrows", "leftleftarrows", "leftrightarrow", "leftrightarrow", "leftrightarrows", "leftrightarrows", "leftrightharpoons", "leftrightharpoons", "leftrightsquigarrow", "leftrightsquigarrow", "leftthreetimes", 
7260 "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", 
7261 "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", 
7262 "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", 
7263 "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", 
7264 "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", 
7265 "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", 
7266 "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", 
7267 "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", 
7268 "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", 
7269 "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", 
7270 "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", 
7271 "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", 
7272 "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", 
7273 "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", 
7274 "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", 
7275 "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", 
7276 "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", 
7277 "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", 
7278 "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", 
7279 "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", 
7280 "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", 
7281 "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", 
7282 "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", 
7283 "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", 
7284 "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", 
7285 "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", 
7286 "rightharpoonup", "rightharpoonup", "rightleftarrows", "rightleftarrows", "rightleftharpoons", "rightleftharpoons", "rightrightarrows", "rightrightarrows", "rightsquigarrow", "rightsquigarrow", "rightthreetimes", "rightthreetimes", "ring", "ring", "risingdotseq", "risingdotseq", "rlarr", "rlarr", "rlhar", "rlhar", "rlm", 
7287 "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", 
7288 "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", 
7289 "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", 
7290 "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", 
7291 "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", 
7292 "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", 
7293 "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", 
7294 "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", 
7295 "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", 
7296 "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", 
7297 "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", 
7298 "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", 
7299 "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", 
7300 "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", 
7301 "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", 
7302 "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", 
7303 "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", 
7304 "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", 
7305 "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", 
7306 "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", 
7307 "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", 
7308 "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", 
7309 "zeetrf", "zeta", "zeta", "zfr", "zfr", "zhcy", "zhcy", "zigrarr", "zigrarr", "zopf", "zopf", "zscr", "zscr", "zwj", "zwj", "zwnj", "zwnj", ];
7310 
7311 immutable dchar[] availableEntitiesValues =
7312 ['\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', 
7313 '\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', 
7314 '\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', 
7315 '\u2232', '\u2232', '\u201d', '\u201d', '\u2019', '\u2019', '\u2237', '\u2237', '\u2a74', '\u2a74', '\u2261', '\u2261', '\u222f', '\u222f', '\u222e', '\u222e', '\u2102', '\u2102', '\u2210', '\u2210', '\u2233', 
7316 '\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', 
7317 '\u00b4', '\u00b4', '\u02d9', '\u02d9', '\u02dd', '\u02dd', '\u0060', '\u0060', '\u02dc', '\u02dc', '\u22c4', '\u22c4', '\u2146', '\u2146', '\U0001d53b', '\U0001d53b', '\u00a8', '\u00a8', '\u20dc', '\u20dc', '\u2250', 
7318 '\u2250', '\u222f', '\u222f', '\u00a8', '\u00a8', '\u21d3', '\u21d3', '\u21d0', '\u21d0', '\u21d4', '\u21d4', '\u2ae4', '\u2ae4', '\u27f8', '\u27f8', '\u27fa', 
7319 '\u27fa', '\u27f9', '\u27f9', '\u21d2', '\u21d2', '\u22a8', '\u22a8', '\u21d1', '\u21d1', '\u21d5', '\u21d5', '\u2225', '\u2225', '\u2193', '\u2193', '\u2913', '\u2913', 
7320 '\u21f5', '\u21f5', '\u0311', '\u0311', '\u2950', '\u2950', '\u295e', '\u295e', '\u21bd', '\u21bd', '\u2956', '\u2956', '\u295f', '\u295f', '\u21c1', '\u21c1', '\u2957', 
7321 '\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', 
7322 '\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', 
7323 '\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', 
7324 '\u0413', '\u0413', '\u0120', '\u0120', '\U0001d50a', '\U0001d50a', '\u22d9', '\u22d9', '\U0001d53e', '\U0001d53e', '\u2265', '\u2265', '\u22db', '\u22db', '\u2267', '\u2267', '\u2aa2', '\u2aa2', '\u2277', '\u2277', '\u2a7e', '\u2a7e', '\u2273', '\u2273', 
7325 '\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', 
7326 '\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', 
7327 '\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', 
7328 '\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', 
7329 '\u21e4', '\u21c6', '\u21c6', '\u2308', '\u2308', '\u27e6', '\u27e6', '\u2961', '\u2961', '\u21c3', '\u21c3', '\u2959', '\u2959', '\u230a', '\u230a', '\u2194', '\u2194', '\u294e', 
7330 '\u294e', '\u22a3', '\u22a3', '\u21a4', '\u21a4', '\u295a', '\u295a', '\u22b2', '\u22b2', '\u29cf', '\u29cf', '\u22b4', '\u22b4', '\u2951', '\u2951', '\u2960', '\u2960', '\u21bf', '\u21bf', 
7331 '\u2958', '\u2958', '\u21bc', '\u21bc', '\u2952', '\u2952', '\u21d0', '\u21d0', '\u21d4', '\u21d4', '\u22da', '\u22da', '\u2266', '\u2266', '\u2276', '\u2276', '\u2aa1', '\u2aa1', '\u2a7d', '\u2a7d', 
7332 '\u2272', '\u2272', '\U0001d50f', '\U0001d50f', '\u22d8', '\u22d8', '\u21da', '\u21da', '\u013f', '\u013f', '\u27f5', '\u27f5', '\u27f7', '\u27f7', '\u27f6', '\u27f6', '\u27f8', '\u27f8', '\u27fa', '\u27fa', '\u27f9', '\u27f9', 
7333 '\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', 
7334 '\u040a', '\u040a', '\u0143', '\u0143', '\u0147', '\u0147', '\u0145', '\u0145', '\u041d', '\u041d', '\u200b', '\u200b', '\u200b', '\u200b', '\u200b', '\u200b', '\u200b', '\u200b', '\u226b', '\u226b', 
7335 '\u226a', '\u226a', '\u000a', '\u000a', '\U0001d511', '\U0001d511', '\u2060', '\u2060', '\u00a0', '\u00a0', '\u2115', '\u2115', '\u2aec', '\u2aec', '\u2262', '\u2262', '\u226d', '\u226d', '\u2226', '\u2226', '\u2209', '\u2209', '\u2260', '\u2260', 
7336 '\u2204', '\u2204', '\u226f', '\u226f', '\u2271', '\u2271', '\u2279', '\u2279', '\u2275', '\u2275', '\u22ea', '\u22ea', '\u22ec', '\u22ec', '\u226e', '\u226e', '\u2270', '\u2270', '\u2278', 
7337 '\u2278', '\u2274', '\u2274', '\u2280', '\u2280', '\u22e0', '\u22e0', '\u220c', '\u220c', '\u22eb', '\u22eb', '\u22ed', '\u22ed', '\u22e2', '\u22e2', '\u22e3', 
7338 '\u22e3', '\u2288', '\u2288', '\u2281', '\u2281', '\u22e1', '\u22e1', '\u2289', '\u2289', '\u2241', '\u2241', '\u2244', '\u2244', '\u2247', '\u2247', '\u2249', '\u2249', '\u2224', 
7339 '\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', 
7340 '\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', 
7341 '\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', 
7342 '\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', 
7343 '\u296f', '\u296f', '\u211c', '\u211c', '\u03a1', '\u03a1', '\u27e9', '\u27e9', '\u2192', '\u2192', '\u21e5', '\u21e5', '\u21c4', '\u21c4', '\u2309', '\u2309', '\u27e7', '\u27e7', '\u295d', 
7344 '\u295d', '\u21c2', '\u21c2', '\u2955', '\u2955', '\u230b', '\u230b', '\u22a2', '\u22a2', '\u21a6', '\u21a6', '\u295b', '\u295b', '\u22b3', '\u22b3', '\u29d0', '\u29d0', '\u22b5', 
7345 '\u22b5', '\u294f', '\u294f', '\u295c', '\u295c', '\u21be', '\u21be', '\u2954', '\u2954', '\u21c0', '\u21c0', '\u2953', '\u2953', '\u21d2', '\u21d2', '\u211d', '\u211d', '\u2970', '\u2970', 
7346 '\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', 
7347 '\u2192', '\u2192', '\u2191', '\u2191', '\u03a3', '\u03a3', '\u2218', '\u2218', '\U0001d54a', '\U0001d54a', '\u221a', '\u221a', '\u25a1', '\u25a1', '\u2293', '\u2293', '\u228f', '\u228f', '\u2291', '\u2291', '\u2290', '\u2290', 
7348 '\u2292', '\u2292', '\u2294', '\u2294', '\U0001d4ae', '\U0001d4ae', '\u22c6', '\u22c6', '\u22d0', '\u22d0', '\u22d0', '\u22d0', '\u2286', '\u2286', '\u227b', '\u227b', '\u2ab0', '\u2ab0', '\u227d', '\u227d', '\u227f', '\u227f', '\u220b', 
7349 '\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', 
7350 '\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', 
7351 '\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', 
7352 '\u2912', '\u21c5', '\u21c5', '\u2195', '\u2195', '\u296e', '\u296e', '\u22a5', '\u22a5', '\u21a5', '\u21a5', '\u21d1', '\u21d1', '\u21d5', '\u21d5', '\u2196', '\u2196', '\u2197', '\u2197', '\u03d2', '\u03d2', '\u03a5', '\u03a5', 
7353 '\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', 
7354 '\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', 
7355 '\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', 
7356 '\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', 
7357 '\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', 
7358 '\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', 
7359 '\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', 
7360 '\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', 
7361 '\u2a06', '\u2a06', '\u2605', '\u2605', '\u25bd', '\u25bd', '\u25b3', '\u25b3', '\u2a04', '\u2a04', '\u22c1', '\u22c1', '\u22c0', '\u22c0', '\u290d', '\u290d', '\u29eb', '\u29eb', '\u25aa', '\u25aa', '\u25b4', '\u25b4', '\u25be', 
7362 '\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', 
7363 '\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', 
7364 '\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', 
7365 '\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', 
7366 '\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', 
7367 '\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', 
7368 '\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', 
7369 '\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', 
7370 '\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', 
7371 '\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', 
7372 '\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', 
7373 '\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', 
7374 '\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', 
7375 '\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', 
7376 '\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', 
7377 '\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', 
7378 '\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', 
7379 '\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', 
7380 '\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', 
7381 '\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', 
7382 '\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', 
7383 '\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', 
7384 '\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', 
7385 '\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', 
7386 '\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', 
7387 '\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', 
7388 '\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', 
7389 '\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', 
7390 '\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', 
7391 '\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', 
7392 '\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', 
7393 '\u2190', '\u21a2', '\u21a2', '\u21bd', '\u21bd', '\u21bc', '\u21bc', '\u21c7', '\u21c7', '\u2194', '\u2194', '\u21c6', '\u21c6', '\u21cb', '\u21cb', '\u21ad', '\u21ad', '\u22cb', 
7394 '\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', 
7395 '\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', 
7396 '\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', 
7397 '\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', 
7398 '\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', 
7399 '\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', 
7400 '\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', 
7401 '\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', 
7402 '\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', 
7403 '\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', 
7404 '\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', 
7405 '\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', 
7406 '\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', 
7407 '\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', 
7408 '\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', 
7409 '\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', 
7410 '\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', 
7411 '\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', 
7412 '\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', 
7413 '\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', 
7414 '\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', 
7415 '\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', 
7416 '\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', 
7417 '\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', 
7418 '\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', 
7419 '\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', 
7420 '\u21c0', '\u21c0', '\u21c4', '\u21c4', '\u21cc', '\u21cc', '\u21c9', '\u21c9', '\u219d', '\u219d', '\u22cc', '\u22cc', '\u02da', '\u02da', '\u2253', '\u2253', '\u21c4', '\u21c4', '\u21cc', '\u21cc', '\u200f', 
7421 '\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', 
7422 '\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', 
7423 '\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', 
7424 '\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', 
7425 '\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', 
7426 '\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', 
7427 '\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', 
7428 '\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', 
7429 '\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', 
7430 '\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', 
7431 '\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', 
7432 '\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', 
7433 '\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', 
7434 '\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', 
7435 '\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', 
7436 '\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', 
7437 '\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', 
7438 '\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', 
7439 '\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', 
7440 '\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', 
7441 '\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', 
7442 '\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', 
7443 '\u2128', '\u03b6', '\u03b6', '\U0001d537', '\U0001d537', '\u0436', '\u0436', '\u21dd', '\u21dd', '\U0001d56b', '\U0001d56b', '\U0001d4cf', '\U0001d4cf', '\u200d', '\u200d', '\u200c', '\u200c', ];
7444 
7445 
7446 
7447 
7448 
7449 
7450 
7451 
7452 
7453 
7454 
7455 
7456 
7457 
7458 
7459 
7460 
7461 
7462 
7463 
7464 
7465 
7466 
7467 // dom event support, if you want to use it
7468 
7469 /// used for DOM events
7470 version(dom_with_events)
7471 alias EventHandler = void delegate(Element handlerAttachedTo, Event event);
7472 
7473 /// 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.
7474 version(dom_with_events)
7475 class Event {
7476 	this(string eventName, Element target) {
7477 		this.eventName = eventName;
7478 		this.srcElement = target;
7479 	}
7480 
7481 	/// Prevents the default event handler (if there is one) from being called
7482 	void preventDefault() {
7483 		defaultPrevented = true;
7484 	}
7485 
7486 	/// Stops the event propagation immediately.
7487 	void stopPropagation() {
7488 		propagationStopped = true;
7489 	}
7490 
7491 	bool defaultPrevented;
7492 	bool propagationStopped;
7493 	string eventName;
7494 
7495 	Element srcElement;
7496 	alias srcElement target;
7497 
7498 	Element relatedTarget;
7499 
7500 	int clientX;
7501 	int clientY;
7502 
7503 	int button;
7504 
7505 	bool isBubbling;
7506 
7507 	/// this sends it only to the target. If you want propagation, use dispatch() instead.
7508 	void send() {
7509 		if(srcElement is null)
7510 			return;
7511 
7512 		auto e = srcElement;
7513 
7514 		if(eventName in e.bubblingEventHandlers)
7515 		foreach(handler; e.bubblingEventHandlers[eventName])
7516 			handler(e, this);
7517 
7518 		if(!defaultPrevented)
7519 			if(eventName in e.defaultEventHandlers)
7520 				e.defaultEventHandlers[eventName](e, this);
7521 	}
7522 
7523 	/// this dispatches the element using the capture -> target -> bubble process
7524 	void dispatch() {
7525 		if(srcElement is null)
7526 			return;
7527 
7528 		// first capture, then bubble
7529 
7530 		Element[] chain;
7531 		Element curr = srcElement;
7532 		while(curr) {
7533 			auto l = curr;
7534 			chain ~= l;
7535 			curr = curr.parentNode;
7536 
7537 		}
7538 
7539 		isBubbling = false;
7540 
7541 		foreach(e; chain.retro()) {
7542 			if(eventName in e.capturingEventHandlers)
7543 			foreach(handler; e.capturingEventHandlers[eventName])
7544 				handler(e, this);
7545 
7546 			// the default on capture should really be to always do nothing
7547 
7548 			//if(!defaultPrevented)
7549 			//	if(eventName in e.defaultEventHandlers)
7550 			//		e.defaultEventHandlers[eventName](e.element, this);
7551 
7552 			if(propagationStopped)
7553 				break;
7554 		}
7555 
7556 		isBubbling = true;
7557 		if(!propagationStopped)
7558 		foreach(e; chain) {
7559 			if(eventName in e.bubblingEventHandlers)
7560 			foreach(handler; e.bubblingEventHandlers[eventName])
7561 				handler(e, this);
7562 
7563 			if(propagationStopped)
7564 				break;
7565 		}
7566 
7567 		if(!defaultPrevented)
7568 		foreach(e; chain) {
7569 				if(eventName in e.defaultEventHandlers)
7570 					e.defaultEventHandlers[eventName](e, this);
7571 		}
7572 	}
7573 }
7574 
7575 struct FormFieldOptions {
7576 	// usable for any
7577 
7578 	/// this is a regex pattern used to validate the field
7579 	string pattern;
7580 	/// must the field be filled in? Even with a regex, it can be submitted blank if this is false.
7581 	bool isRequired;
7582 	/// this is displayed as an example to the user
7583 	string placeholder;
7584 
7585 	// usable for numeric ones
7586 
7587 
7588 	// convenience methods to quickly get some options
7589 	@property static FormFieldOptions none() {
7590 		FormFieldOptions f;
7591 		return f;
7592 	}
7593 
7594 	static FormFieldOptions required() {
7595 		FormFieldOptions f;
7596 		f.isRequired = true;
7597 		return f;
7598 	}
7599 
7600 	static FormFieldOptions regex(string pattern, bool required = false) {
7601 		FormFieldOptions f;
7602 		f.pattern = pattern;
7603 		f.isRequired = required;
7604 		return f;
7605 	}
7606 
7607 	static FormFieldOptions fromElement(Element e) {
7608 		FormFieldOptions f;
7609 		if(e.hasAttribute("required"))
7610 			f.isRequired = true;
7611 		if(e.hasAttribute("pattern"))
7612 			f.pattern = e.pattern;
7613 		if(e.hasAttribute("placeholder"))
7614 			f.placeholder = e.placeholder;
7615 		return f;
7616 	}
7617 
7618 	Element applyToElement(Element e) {
7619 		if(this.isRequired)
7620 			e.required = "required";
7621 		if(this.pattern.length)
7622 			e.pattern = this.pattern;
7623 		if(this.placeholder.length)
7624 			e.placeholder = this.placeholder;
7625 		return e;
7626 	}
7627 }
7628 
7629 // this needs to look just like a string, but can expand as needed
7630 version(no_dom_stream)
7631 alias string Utf8Stream;
7632 else
7633 class Utf8Stream {
7634 	protected:
7635 		// these two should be overridden in subclasses to actually do the stream magic
7636 		string getMore() {
7637 			if(getMoreHelper !is null)
7638 				return getMoreHelper();
7639 			return null;
7640 		}
7641 
7642 		bool hasMore() {
7643 			if(hasMoreHelper !is null)
7644 				return hasMoreHelper();
7645 			return false;
7646 		}
7647 		// the rest should be ok
7648 
7649 	public:
7650 		this(string d) {
7651 			this.data = d;
7652 		}
7653 
7654 		this(string delegate() getMoreHelper, bool delegate() hasMoreHelper) {
7655 			this.getMoreHelper = getMoreHelper;
7656 			this.hasMoreHelper = hasMoreHelper;
7657 
7658 			if(hasMore())
7659 				this.data ~= getMore();
7660 
7661 			stdout.flush();
7662 		}
7663 
7664 		@property final size_t length() {
7665 			// the parser checks length primarily directly before accessing the next character
7666 			// so this is the place we'll hook to append more if possible and needed.
7667 			if(lastIdx + 1 >= data.length && hasMore()) {
7668 				data ~= getMore();
7669 			}
7670 			return data.length;
7671 		}
7672 
7673 		final char opIndex(size_t idx) {
7674 			if(idx > lastIdx)
7675 				lastIdx = idx;
7676 			return data[idx];
7677 		}
7678 
7679 		final string opSlice(size_t start, size_t end) {
7680 			if(end > lastIdx)
7681 				lastIdx = end;
7682 			return data[start .. end];
7683 		}
7684 
7685 		final size_t opDollar() {
7686 			return length();
7687 		}
7688 
7689 		final Utf8Stream opBinary(string op : "~")(string s) {
7690 			this.data ~= s;
7691 			return this;
7692 		}
7693 
7694 		final Utf8Stream opOpAssign(string op : "~")(string s) {
7695 			this.data ~= s;
7696 			return this;
7697 		}
7698 
7699 		final Utf8Stream opAssign(string rhs) {
7700 			this.data = rhs;
7701 			return this;
7702 		}
7703 	private:
7704 		string data;
7705 
7706 		size_t lastIdx;
7707 
7708 		bool delegate() hasMoreHelper;
7709 		string delegate() getMoreHelper;
7710 
7711 
7712 		/+
7713 		// used to maybe clear some old stuff
7714 		// you might have to remove elements parsed with it too since they can hold slices into the
7715 		// old stuff, preventing gc
7716 		void dropFront(int bytes) {
7717 			posAdjustment += bytes;
7718 			data = data[bytes .. $];
7719 		}
7720 
7721 		int posAdjustment;
7722 		+/
7723 }
7724 
7725 void fillForm(T)(Form form, T obj, string name) { 
7726 	import arsd.database; 
7727 	fillData((k, v) => form.setValue(k, v), obj, name); 
7728 } 
7729 
7730 
7731 /+
7732 /+
7733 Syntax:
7734 
7735 Tag: tagname#id.class
7736 Tree: Tag(Children, comma, separated...)
7737 Children: Tee or Variable
7738 Variable: $varname with optional |funcname following.
7739 
7740 If a variable has a tree after it, it breaks the variable down:
7741 	* if array, foreach it does the tree
7742 	* if struct, it breaks down the member variables
7743 
7744 stolen from georgy on irc, see: https://github.com/georgy7/stringplate
7745 +/
7746 struct Stringplate {
7747 	/++
7748 
7749 	+/
7750 	this(string s) {
7751 
7752 	}
7753 
7754 	/++
7755 
7756 	+/
7757 	Element expand(T...)(T vars) {
7758 		return null;
7759 	}
7760 }
7761 ///
7762 unittest {
7763 	auto stringplate = Stringplate("#bar(.foo($foo), .baz($baz))");
7764 	assert(stringplate.expand.innerHTML == `<div id="bar"><div class="foo">$foo</div><div class="baz">$baz</div></div>`);
7765 }
7766 +/
7767 
7768 bool allAreInlineHtml(const(Element)[] children, const string[] inlineElements) {
7769 	foreach(child; children) {
7770 		if(child.nodeType == NodeType.Text && child.nodeValue.strip.length) {
7771 			// cool
7772 		} else if(child.tagName.isInArray(inlineElements) && allAreInlineHtml(child.children, inlineElements)) {
7773 			// cool
7774 		} else {
7775 			// prolly block
7776 			return false;
7777 		}
7778 	}
7779 	return true;
7780 }
7781 
7782 private bool isSimpleWhite(dchar c) {
7783 	return c == ' ' || c == '\r' || c == '\n' || c == '\t';
7784 }
7785 
7786 unittest {
7787 	// Test for issue #120
7788 	string s = `<html>
7789 	<body>
7790 		<P>AN
7791 		<P>bubbles</P>
7792 		<P>giggles</P>
7793 	</body>
7794 </html>`;
7795 	auto doc = new Document();
7796 	doc.parseUtf8(s, false, false);
7797 	auto s2 = doc.toString();
7798 	assert(
7799 			s2.indexOf("bubbles") < s2.indexOf("giggles"),
7800 			"paragraph order incorrect:\n" ~ s2);
7801 }
7802 
7803 unittest {
7804 	// test for suncarpet email dec 24 2019
7805 	// arbitrary id asduiwh
7806 	auto document = new Document("<html>
7807         <head>
7808                 <meta charset=\"utf-8\"></meta>
7809                 <title>Element.querySelector Test</title>
7810         </head>
7811         <body>
7812                 <div id=\"foo\">
7813                         <div>Foo</div>
7814                         <div>Bar</div>
7815                 </div>
7816 		<div id=\"empty\"></div>
7817 		<div id=\"empty-but-text\">test</div>
7818         </body>
7819 </html>");
7820 
7821 	auto doc = document;
7822 
7823 	{
7824 	auto empty = doc.requireElementById("empty");
7825 	assert(empty.querySelector(" > *") is null, empty.querySelector(" > *").toString);
7826 	}
7827 	{
7828 	auto empty = doc.requireElementById("empty-but-text");
7829 	assert(empty.querySelector(" > *") is null, empty.querySelector(" > *").toString);
7830 	}
7831 
7832 	assert(doc.querySelectorAll("div div").length == 2);
7833 	assert(doc.querySelector("div").querySelectorAll("div").length == 2);
7834 	assert(doc.querySelectorAll("> html").length == 0);
7835 	assert(doc.querySelector("head").querySelectorAll("> title").length == 1);
7836 	assert(doc.querySelector("head").querySelectorAll("> meta[charset]").length == 1);
7837 
7838 
7839 	assert(doc.root.matches("html"));
7840 	assert(!doc.root.matches("nothtml"));
7841 	assert(doc.querySelector("#foo > div").matches("div"));
7842 	assert(doc.querySelector("body > #foo").matches("#foo"));
7843 
7844 	assert(doc.root.querySelectorAll(":root > body").length == 0); // the root has no CHILD root!
7845 	assert(doc.querySelectorAll(":root > body").length == 1); // but the DOCUMENT does
7846 	assert(doc.querySelectorAll(" > body").length == 1); //  should mean the same thing
7847 	assert(doc.root.querySelectorAll(" > body").length == 1); // the root of HTML has this
7848 	assert(doc.root.querySelectorAll(" > html").length == 0); // but not this
7849 
7850 	// also confirming the querySelector works via the mdn definition
7851 	auto foo = doc.requireSelector("#foo");
7852 	assert(foo.querySelector("#foo > div") !is null);
7853 	assert(foo.querySelector("body #foo > div") !is null);
7854 
7855 	// this is SUPPOSED to work according to the spec but never has in dom.d since it limits the scope.
7856 	// the new css :scope thing is designed to bring this in. and meh idk if i even care.
7857 	//assert(foo.querySelectorAll("#foo > div").length == 2);
7858 }
7859 
7860 unittest {
7861 	// based on https://developer.mozilla.org/en-US/docs/Web/API/Element/closest example
7862 	auto document = new Document(`<article>
7863   <div id="div-01">Here is div-01
7864     <div id="div-02">Here is div-02
7865       <div id="div-03">Here is div-03</div>
7866     </div>
7867   </div>
7868 </article>`, true, true);
7869 
7870 	auto el = document.getElementById("div-03");
7871 	assert(el.closest("#div-02").id == "div-02");
7872 	assert(el.closest("div div").id == "div-03");
7873 	assert(el.closest("article > div").id == "div-01");
7874 	assert(el.closest(":not(div)").tagName == "article");
7875 
7876 	assert(el.closest("p") is null);
7877 	assert(el.closest("p, div") is el);
7878 }
7879 
7880 unittest {
7881 	// https://developer.mozilla.org/en-US/docs/Web/CSS/:is
7882 	auto document = new Document(`<test>
7883 		<div class="foo"><p>cool</p><span>bar</span></div>
7884 		<main><p>two</p></main>
7885 	</test>`);
7886 
7887 	assert(document.querySelectorAll(":is(.foo, main) p").length == 2);
7888 	assert(document.querySelector("div:where(.foo)") !is null);
7889 }
7890 
7891 unittest {
7892 immutable string html = q{
7893 <root>
7894 <div class="roundedbox">
7895  <table>
7896   <caption class="boxheader">Recent Reviews</caption>
7897   <tr>
7898    <th>Game</th>
7899    <th>User</th>
7900    <th>Rating</th>
7901    <th>Created</th>
7902   </tr>
7903 
7904   <tr>
7905    <td>June 13, 2020 15:10</td>
7906    <td><a href="/reviews/8833">[Show]</a></td>
7907   </tr>
7908 
7909   <tr>
7910    <td>June 13, 2020 15:02</td>
7911    <td><a href="/reviews/8832">[Show]</a></td>
7912   </tr>
7913 
7914   <tr>
7915    <td>June 13, 2020 14:41</td>
7916    <td><a href="/reviews/8831">[Show]</a></td>
7917   </tr>
7918  </table>
7919 </div>
7920 </root>
7921 };
7922 
7923   auto doc = new Document(cast(string)html);
7924   // this should select the second table row, but...
7925   auto rd = doc.root.querySelector(`div.roundedbox > table > caption.boxheader + tr + tr + tr > td > a[href^=/reviews/]`);
7926   assert(rd !is null);
7927   assert(rd.href == "/reviews/8832");
7928 
7929   rd = doc.querySelector(`div.roundedbox > table > caption.boxheader + tr + tr + tr > td > a[href^=/reviews/]`);
7930   assert(rd !is null);
7931   assert(rd.href == "/reviews/8832");
7932 }
7933 
7934 unittest {
7935 	try {
7936 		auto doc = new XmlDocument("<testxmlns:foo=\"/\"></test>");
7937 		assert(0);
7938 	} catch(Exception e) {
7939 		// good; it should throw an exception, not an error.
7940 	}
7941 }
7942 
7943 /*
7944 Copyright: Adam D. Ruppe, 2010 - 2021
7945 License:   <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
7946 Authors: Adam D. Ruppe, with contributions by Nick Sabalausky, Trass3r, and ketmar among others
7947 
7948         Copyright Adam D. Ruppe 2010-2021.
7949 Distributed under the Boost Software License, Version 1.0.
7950    (See accompanying file LICENSE_1_0.txt or copy at
7951         http://www.boost.org/LICENSE_1_0.txt)
7952 */
7953 
7954