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