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);
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 		foreach(ele; tree)
2487 			if(s.matchesElement(ele))
2488 				return ele;
2489 		return null;
2490 	}
2491 
2492 	/// If the element matches the given selector. Previously known as `matchesSelector`.
2493 	@scriptable
2494 	bool matches(string selector) {
2495 		/+
2496 		bool caseSensitiveTags = true;
2497 		if(parentDocument && parentDocument.loose)
2498 			caseSensitiveTags = false;
2499 		+/
2500 
2501 		Selector s = Selector(selector);
2502 		return s.matchesElement(this);
2503 	}
2504 
2505 	/// Returns itself or the closest parent that matches the given selector, or null if none found
2506 	/// See_also: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
2507 	@scriptable
2508 	Element closest(string selector) {
2509 		Element e = this;
2510 		while(e !is null) {
2511 			if(e.matches(selector))
2512 				return e;
2513 			e = e.parentNode;
2514 		}
2515 		return null;
2516 	}
2517 
2518 	/**
2519 		Returns elements that match the given CSS selector
2520 
2521 		* -- all, default if nothing else is there
2522 
2523 		tag#id.class.class.class:pseudo[attrib=what][attrib=what] OP selector
2524 
2525 		It is all additive
2526 
2527 		OP
2528 
2529 		space = descendant
2530 		>     = direct descendant
2531 		+     = sibling (E+F Matches any F element immediately preceded by a sibling element E)
2532 
2533 		[foo]        Foo is present as an attribute
2534 		[foo="warning"]   Matches any E element whose "foo" attribute value is exactly equal to "warning".
2535 		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"
2536 		E[lang|="en"] Matches any E element whose "lang" attribute has a hyphen-separated list of values beginning (from the left) with "en".
2537 
2538 		[item$=sdas] ends with
2539 		[item^-sdsad] begins with
2540 
2541 		Quotes are optional here.
2542 
2543 		Pseudos:
2544 			:first-child
2545 			:last-child
2546 			:link (same as a[href] for our purposes here)
2547 
2548 
2549 		There can be commas separating the selector. A comma separated list result is OR'd onto the main.
2550 
2551 
2552 
2553 		This ONLY cares about elements. text, etc, are ignored
2554 
2555 
2556 		There should be two functions: given element, does it match the selector? and given a selector, give me all the elements
2557 
2558 		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..
2559 	*/
2560 	@scriptable
2561 	Element[] querySelectorAll(string selector) {
2562 		// FIXME: this function could probably use some performance attention
2563 		// ... but only mildly so according to the profiler in the big scheme of things; probably negligible in a big app.
2564 
2565 
2566 		bool caseSensitiveTags = true;
2567 		if(parentDocument && parentDocument.loose)
2568 			caseSensitiveTags = false;
2569 
2570 		Element[] ret;
2571 		foreach(sel; parseSelectorString(selector, caseSensitiveTags))
2572 			ret ~= sel.getElements(this);
2573 		return ret;
2574 	}
2575 
2576 	/// ditto
2577 	alias getElementsBySelector = querySelectorAll;
2578 
2579 	/++
2580 		Returns child elements that have the given class name or tag name.
2581 
2582 		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.
2583 
2584 		So these is incompatible with Javascript in the face of live dom mutation and will likely remain so.
2585 	+/
2586 	Element[] getElementsByClassName(string cn) {
2587 		// is this correct?
2588 		return getElementsBySelector("." ~ cn);
2589 	}
2590 
2591 	/// ditto
2592 	Element[] getElementsByTagName(string tag) {
2593 		if(parentDocument && parentDocument.loose)
2594 			tag = tag.toLower();
2595 		Element[] ret;
2596 		foreach(e; tree)
2597 			if(e.tagName == tag)
2598 				ret ~= e;
2599 		return ret;
2600 	}
2601 
2602 
2603 	/* *******************************
2604 	          Attributes
2605 	*********************************/
2606 
2607 	/**
2608 		Gets the given attribute value, or null if the
2609 		attribute is not set.
2610 
2611 		Note that the returned string is decoded, so it no longer contains any xml entities.
2612 	*/
2613 	@scriptable
2614 	string getAttribute(string name) const {
2615 		if(parentDocument && parentDocument.loose)
2616 			name = name.toLower();
2617 		auto e = name in attributes;
2618 		if(e)
2619 			return *e;
2620 		else
2621 			return null;
2622 	}
2623 
2624 	/**
2625 		Sets an attribute. Returns this for easy chaining
2626 	*/
2627 	@scriptable
2628 	Element setAttribute(string name, string value) {
2629 		if(parentDocument && parentDocument.loose)
2630 			name = name.toLower();
2631 
2632 		// I never use this shit legitimately and neither should you
2633 		auto it = name.toLower();
2634 		if(it == "href" || it == "src") {
2635 			auto v = value.strip().toLower();
2636 			if(v.startsWith("vbscript:"))
2637 				value = value[9..$];
2638 			if(v.startsWith("javascript:"))
2639 				value = value[11..$];
2640 		}
2641 
2642 		attributes[name] = value;
2643 
2644 		sendObserverEvent(DomMutationOperations.setAttribute, name, value);
2645 
2646 		return this;
2647 	}
2648 
2649 	/**
2650 		Returns if the attribute exists.
2651 	*/
2652 	@scriptable
2653 	bool hasAttribute(string name) {
2654 		if(parentDocument && parentDocument.loose)
2655 			name = name.toLower();
2656 
2657 		if(name in attributes)
2658 			return true;
2659 		else
2660 			return false;
2661 	}
2662 
2663 	/**
2664 		Removes the given attribute from the element.
2665 	*/
2666 	@scriptable
2667 	Element removeAttribute(string name)
2668 	out(ret) {
2669 		assert(ret is this);
2670 	}
2671 	do {
2672 		if(parentDocument && parentDocument.loose)
2673 			name = name.toLower();
2674 		if(name in attributes)
2675 			attributes.remove(name);
2676 
2677 		sendObserverEvent(DomMutationOperations.removeAttribute, name);
2678 		return this;
2679 	}
2680 
2681 	/**
2682 		Gets or sets the class attribute's contents. Returns
2683 		an empty string if it has no class.
2684 	*/
2685 	@property string className() const {
2686 		auto c = getAttribute("class");
2687 		if(c is null)
2688 			return "";
2689 		return c;
2690 	}
2691 
2692 	/// ditto
2693 	@property Element className(string c) {
2694 		setAttribute("class", c);
2695 		return this;
2696 	}
2697 
2698 	/**
2699 		Provides easy access to common HTML attributes, object style.
2700 
2701 		---
2702 		auto element = Element.make("a");
2703 		a.href = "cool.html"; // this is the same as a.setAttribute("href", "cool.html");
2704 		string where = a.href; // same as a.getAttribute("href");
2705 		---
2706 
2707 	*/
2708 	@property string opDispatch(string name)(string v = null) if(isConvenientAttribute(name)) {
2709 		if(v !is null)
2710 			setAttribute(name, v);
2711 		return getAttribute(name);
2712 	}
2713 
2714 	/**
2715 		Old access to attributes. Use [attrs] instead.
2716 
2717 		DEPRECATED: generally open opDispatch caused a lot of unforeseen trouble with compile time duck typing and UFCS extensions.
2718 		so I want to remove it. A small whitelist of attributes is still allowed, but others are not.
2719 		
2720 		Instead, use element.attrs.attribute, element.attrs["attribute"],
2721 		or element.getAttribute("attribute")/element.setAttribute("attribute").
2722 	*/
2723 	@property string opDispatch(string name)(string v = null) if(!isConvenientAttribute(name)) {
2724 		static assert(0, "Don't use " ~ name ~ " direct on Element, instead use element.attrs.attributeName");
2725 	}
2726 
2727 	/*
2728 	// this would be nice for convenience, but it broke the getter above.
2729 	@property void opDispatch(string name)(bool boolean) if(name != "popFront") {
2730 		if(boolean)
2731 			setAttribute(name, name);
2732 		else
2733 			removeAttribute(name);
2734 	}
2735 	*/
2736 
2737 	/**
2738 		Returns the element's children.
2739 	*/
2740 	@property inout(Element[]) childNodes() inout {
2741 		return children;
2742 	}
2743 
2744 	/++
2745 		HTML5's dataset property. It is an alternate view into attributes with the data- prefix.
2746 		Given `<a data-my-property="cool" />`, we get `assert(a.dataset.myProperty == "cool");`
2747 	+/
2748 	@property DataSet dataset() {
2749 		return DataSet(this);
2750 	}
2751 
2752 	/++
2753 		Gives dot/opIndex access to attributes
2754 		---
2755 		ele.attrs.largeSrc = "foo"; // same as ele.setAttribute("largeSrc", "foo")
2756 		---
2757 	+/
2758 	@property AttributeSet attrs() {
2759 		return AttributeSet(this);
2760 	}
2761 
2762 	/++
2763 		Provides both string and object style (like in Javascript) access to the style attribute.
2764 
2765 		---
2766 		element.style.color = "red"; // translates into setting `color: red;` in the `style` attribute
2767 		---
2768 	+/
2769 	@property ElementStyle style() {
2770 		return ElementStyle(this);
2771 	}
2772 
2773 	/++
2774 		This sets the style attribute with a string.
2775 	+/
2776 	@property ElementStyle style(string s) {
2777 		this.setAttribute("style", s);
2778 		return this.style;
2779 	}
2780 
2781 	private void parseAttributes(string[] whichOnes = null) {
2782 /+
2783 		if(whichOnes is null)
2784 			whichOnes = attributes.keys;
2785 		foreach(attr; whichOnes) {
2786 			switch(attr) {
2787 				case "id":
2788 
2789 				break;
2790 				case "class":
2791 
2792 				break;
2793 				case "style":
2794 
2795 				break;
2796 				default:
2797 					// we don't care about it
2798 			}
2799 		}
2800 +/
2801 	}
2802 
2803 
2804 	// 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.
2805 
2806 	// the next few methods are for implementing interactive kind of things
2807 	private CssStyle _computedStyle;
2808 
2809 	/// 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.
2810 	@property CssStyle computedStyle() {
2811 		if(_computedStyle is null) {
2812 			auto style = this.getAttribute("style");
2813 		/* we'll treat shitty old html attributes as css here */
2814 			if(this.hasAttribute("width"))
2815 				style ~= "; width: " ~ this.attrs.width;
2816 			if(this.hasAttribute("height"))
2817 				style ~= "; height: " ~ this.attrs.height;
2818 			if(this.hasAttribute("bgcolor"))
2819 				style ~= "; background-color: " ~ this.attrs.bgcolor;
2820 			if(this.tagName == "body" && this.hasAttribute("text"))
2821 				style ~= "; color: " ~ this.attrs.text;
2822 			if(this.hasAttribute("color"))
2823 				style ~= "; color: " ~ this.attrs.color;
2824 		/* done */
2825 
2826 
2827 			_computedStyle = new CssStyle(null, style); // gives at least something to work with
2828 		}
2829 		return _computedStyle;
2830 	}
2831 
2832 	/// These properties are useless in most cases, but if you write a layout engine on top of this lib, they may be good
2833 	version(browser) {
2834 		void* expansionHook; ///ditto
2835 		int offsetWidth; ///ditto
2836 		int offsetHeight; ///ditto
2837 		int offsetLeft; ///ditto
2838 		int offsetTop; ///ditto
2839 		Element offsetParent; ///ditto
2840 		bool hasLayout; ///ditto
2841 		int zIndex; ///ditto
2842 
2843 		///ditto
2844 		int absoluteLeft() {
2845 			int a = offsetLeft;
2846 			auto p = offsetParent;
2847 			while(p) {
2848 				a += p.offsetLeft;
2849 				p = p.offsetParent;
2850 			}
2851 
2852 			return a;
2853 		}
2854 
2855 		///ditto
2856 		int absoluteTop() {
2857 			int a = offsetTop;
2858 			auto p = offsetParent;
2859 			while(p) {
2860 				a += p.offsetTop;
2861 				p = p.offsetParent;
2862 			}
2863 
2864 			return a;
2865 		}
2866 	}
2867 
2868 	// Back to the regular dom functions
2869 
2870     public:
2871 
2872 
2873 	/* *******************************
2874 	          DOM Mutation
2875 	*********************************/
2876 
2877 	/// Removes all inner content from the tag; all child text and elements are gone.
2878 	void removeAllChildren()
2879 		out {
2880 			assert(this.children.length == 0);
2881 		}
2882 	do {
2883 		foreach(child; children)
2884 			child.parentNode = null;
2885 		children = null;
2886 	}
2887 
2888 	/++
2889 		Adds a sibling element before or after this one in the dom.
2890 
2891 		History: added June 13, 2020
2892 	+/
2893 	Element appendSibling(Element e) {
2894 		parentNode.insertAfter(this, e);
2895 		return e;
2896 	}
2897 
2898 	/// ditto
2899 	Element prependSibling(Element e) {
2900 		parentNode.insertBefore(this, e);
2901 		return e;
2902 	}
2903 
2904 
2905     	/++
2906 		Appends the given element to this one. If it already has a parent, it is removed from that tree and moved to this one.
2907 
2908 		See_also: https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild
2909 
2910 		History:
2911 			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.
2912 	+/
2913 	Element appendChild(Element e)
2914 		in {
2915 			assert(e !is null);
2916 			assert(e !is this);
2917 		}
2918 		out (ret) {
2919 			assert((cast(DocumentFragment) this !is null) || (e.parentNode is this), e.toString);// e.parentNode ? e.parentNode.toString : "null");
2920 			assert(e.parentDocument is this.parentDocument);
2921 			assert(e is ret);
2922 		}
2923 	do {
2924 		if(e.parentNode !is null)
2925 			e.parentNode.removeChild(e);
2926 
2927 		selfClosed = false;
2928 		if(auto frag = cast(DocumentFragment) e)
2929 			children ~= frag.children;
2930 		else
2931 			children ~= e;
2932 
2933 		e.parentNode = this;
2934 
2935 		/+
2936 		foreach(item; e.tree)
2937 			item.parentDocument = this.parentDocument;
2938 		+/
2939 
2940 		sendObserverEvent(DomMutationOperations.appendChild, null, null, e);
2941 
2942 		return e;
2943 	}
2944 
2945 	/// Inserts the second element to this node, right before the first param
2946 	Element insertBefore(in Element where, Element what)
2947 		in {
2948 			assert(where !is null);
2949 			assert(where.parentNode is this);
2950 			assert(what !is null);
2951 			assert(what.parentNode is null);
2952 		}
2953 		out (ret) {
2954 			assert(where.parentNode is this);
2955 			assert(what.parentNode is this);
2956 
2957 			assert(what.parentDocument is this.parentDocument);
2958 			assert(ret is what);
2959 		}
2960 	do {
2961 		foreach(i, e; children) {
2962 			if(e is where) {
2963 				if(auto frag = cast(DocumentFragment) what) {
2964 					children = children[0..i] ~ frag.children ~ children[i..$];
2965 					foreach(child; frag.children)
2966 						child.parentNode = this;
2967 				} else {
2968 					children = children[0..i] ~ what ~ children[i..$];
2969 				}
2970 				what.parentNode = this;
2971 				return what;
2972 			}
2973 		}
2974 
2975 		return what;
2976 
2977 		assert(0);
2978 	}
2979 
2980 	/++
2981 		Inserts the given element `what` as a sibling of the `this` element, after the element `where` in the parent node.
2982 	+/
2983 	Element insertAfter(in Element where, Element what)
2984 		in {
2985 			assert(where !is null);
2986 			assert(where.parentNode is this);
2987 			assert(what !is null);
2988 			assert(what.parentNode is null);
2989 		}
2990 		out (ret) {
2991 			assert(where.parentNode is this);
2992 			assert(what.parentNode is this);
2993 			assert(what.parentDocument is this.parentDocument);
2994 			assert(ret is what);
2995 		}
2996 	do {
2997 		foreach(i, e; children) {
2998 			if(e is where) {
2999 				if(auto frag = cast(DocumentFragment) what) {
3000 					children = children[0 .. i + 1] ~ what.children ~ children[i + 1 .. $];
3001 					foreach(child; frag.children)
3002 						child.parentNode = this;
3003 				} else
3004 					children = children[0 .. i + 1] ~ what ~ children[i + 1 .. $];
3005 				what.parentNode = this;
3006 				return what;
3007 			}
3008 		}
3009 
3010 		return what;
3011 
3012 		assert(0);
3013 	}
3014 
3015 	/// swaps one child for a new thing. Returns the old child which is now parentless.
3016 	Element swapNode(Element child, Element replacement)
3017 		in {
3018 			assert(child !is null);
3019 			assert(replacement !is null);
3020 			assert(child.parentNode is this);
3021 		}
3022 		out(ret) {
3023 			assert(ret is child);
3024 			assert(ret.parentNode is null);
3025 			assert(replacement.parentNode is this);
3026 			assert(replacement.parentDocument is this.parentDocument);
3027 		}
3028 	do {
3029 		foreach(ref c; this.children)
3030 			if(c is child) {
3031 				c.parentNode = null;
3032 				c = replacement;
3033 				c.parentNode = this;
3034 				return child;
3035 			}
3036 		assert(0);
3037 	}
3038 
3039 
3040 	/++
3041 		Appends the given to the node.
3042 
3043 
3044 		Calling `e.appendText(" hi")` on `<example>text <b>bold</b></example>`
3045 		yields `<example>text <b>bold</b> hi</example>`.
3046 
3047 		See_Also:
3048 			[firstInnerText], [directText], [innerText], [appendChild]
3049 	+/
3050 	@scriptable
3051 	Element appendText(string text) {
3052 		Element e = new TextNode(parentDocument, text);
3053 		appendChild(e);
3054 		return this;
3055 	}
3056 
3057 	/++
3058 		Returns child elements which are of a tag type (excludes text, comments, etc.).
3059 
3060 
3061 		childElements of `<example>text <b>bold</b></example>` is just the `<b>` tag.
3062 
3063 		Params:
3064 			tagName = filter results to only the child elements with the given tag name.
3065 	+/
3066 	@property Element[] childElements(string tagName = null) {
3067 		Element[] ret;
3068 		foreach(c; children)
3069 			if(c.nodeType == 1 && (tagName is null || c.tagName == tagName))
3070 				ret ~= c;
3071 		return ret;
3072 	}
3073 
3074 	/++
3075 		Appends the given html to the element, returning the elements appended
3076 
3077 
3078 		This is similar to `element.innerHTML += "html string";` in Javascript.
3079 	+/
3080 	@scriptable
3081 	Element[] appendHtml(string html) {
3082 		Document d = new Document("<root>" ~ html ~ "</root>");
3083 		return stealChildren(d.root);
3084 	}
3085 
3086 
3087 	/++
3088 		Inserts a child under this element after the element `where`.
3089 	+/
3090 	void insertChildAfter(Element child, Element where)
3091 		in {
3092 			assert(child !is null);
3093 			assert(where !is null);
3094 			assert(where.parentNode is this);
3095 			assert(!selfClosed);
3096 			//assert(isInArray(where, children));
3097 		}
3098 		out {
3099 			assert(child.parentNode is this);
3100 			assert(where.parentNode is this);
3101 			//assert(isInArray(where, children));
3102 			//assert(isInArray(child, children));
3103 		}
3104 	do {
3105 		foreach(ref i, c; children) {
3106 			if(c is where) {
3107 				i++;
3108 				if(auto frag = cast(DocumentFragment) child) {
3109 					children = children[0..i] ~ child.children ~ children[i..$];
3110 					//foreach(child; frag.children)
3111 						//child.parentNode = this;
3112 				} else
3113 					children = children[0..i] ~ child ~ children[i..$];
3114 				child.parentNode = this;
3115 				break;
3116 			}
3117 		}
3118 	}
3119 
3120 	/++
3121 		Reparents all the child elements of `e` to `this`, leaving `e` childless.
3122 
3123 		Params:
3124 			e = the element whose children you want to steal
3125 			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.
3126 	+/
3127 	Element[] stealChildren(Element e, Element position = null)
3128 		in {
3129 			assert(!selfClosed);
3130 			assert(e !is null);
3131 			//if(position !is null)
3132 				//assert(isInArray(position, children));
3133 		}
3134 		out (ret) {
3135 			assert(e.children.length == 0);
3136 			// all the parentNode is this checks fail because DocumentFragments do not appear in the parent tree, they are invisible...
3137 			version(none)
3138 			debug foreach(child; ret) {
3139 				assert(child.parentNode is this);
3140 				assert(child.parentDocument is this.parentDocument);
3141 			}
3142 		}
3143 	do {
3144 		foreach(c; e.children) {
3145 			c.parentNode = this;
3146 		}
3147 		if(position is null)
3148 			children ~= e.children;
3149 		else {
3150 			foreach(i, child; children) {
3151 				if(child is position) {
3152 					children = children[0..i] ~
3153 						e.children ~
3154 						children[i..$];
3155 					break;
3156 				}
3157 			}
3158 		}
3159 
3160 		auto ret = e.children[];
3161 		e.children.length = 0;
3162 
3163 		return ret;
3164 	}
3165 
3166     	/// Puts the current element first in our children list. The given element must not have a parent already.
3167 	Element prependChild(Element e)
3168 		in {
3169 			assert(e.parentNode is null);
3170 			assert(!selfClosed);
3171 		}
3172 		out {
3173 			assert(e.parentNode is this);
3174 			assert(e.parentDocument is this.parentDocument);
3175 			assert(children[0] is e);
3176 		}
3177 	do {
3178 		if(auto frag = cast(DocumentFragment) e) {
3179 			children = e.children ~ children;
3180 			foreach(child; frag.children)
3181 				child.parentNode = this;
3182 		} else
3183 			children = e ~ children;
3184 		e.parentNode = this;
3185 		return e;
3186 	}
3187 
3188 
3189 	/**
3190 		Returns a string containing all child elements, formatted such that it could be pasted into
3191 		an XML file.
3192 	*/
3193 	@property string innerHTML(Appender!string where = appender!string()) const {
3194 		if(children is null)
3195 			return "";
3196 
3197 		auto start = where.data.length;
3198 
3199 		foreach(child; children) {
3200 			assert(child !is null);
3201 
3202 			child.writeToAppender(where);
3203 		}
3204 
3205 		return where.data[start .. $];
3206 	}
3207 
3208 	/**
3209 		Takes some html and replaces the element's children with the tree made from the string.
3210 	*/
3211 	@property Element innerHTML(string html, bool strict = false) {
3212 		if(html.length)
3213 			selfClosed = false;
3214 
3215 		if(html.length == 0) {
3216 			// I often say innerHTML = ""; as a shortcut to clear it out,
3217 			// so let's optimize that slightly.
3218 			removeAllChildren();
3219 			return this;
3220 		}
3221 
3222 		auto doc = new Document();
3223 		doc.parseUtf8("<innerhtml>" ~ html ~ "</innerhtml>", strict, strict); // FIXME: this should preserve the strictness of the parent document
3224 
3225 		children = doc.root.children;
3226 		foreach(c; children) {
3227 			c.parentNode = this;
3228 		}
3229 
3230 		doc.root.children = null;
3231 
3232 		return this;
3233 	}
3234 
3235 	/// ditto
3236 	@property Element innerHTML(Html html) {
3237 		return this.innerHTML = html.source;
3238 	}
3239 
3240 	/**
3241 		Replaces this node with the given html string, which is parsed
3242 
3243 		Note: this invalidates the this reference, since it is removed
3244 		from the tree.
3245 
3246 		Returns the new children that replace this.
3247 	*/
3248 	@property Element[] outerHTML(string html) {
3249 		auto doc = new Document();
3250 		doc.parseUtf8("<innerhtml>" ~ html ~ "</innerhtml>"); // FIXME: needs to preserve the strictness
3251 
3252 		children = doc.root.children;
3253 		foreach(c; children) {
3254 			c.parentNode = this;
3255 		}
3256 
3257 		stripOut();
3258 
3259 		return doc.root.children;
3260 	}
3261 
3262 	/++
3263 		Returns all the html for this element, including the tag itself.
3264 
3265 		This is equivalent to calling toString().
3266 	+/
3267 	@property string outerHTML() {
3268 		return this.toString();
3269 	}
3270 
3271 	/// This sets the inner content of the element *without* trying to parse it.
3272 	/// You can inject any code in there; this serves as an escape hatch from the dom.
3273 	///
3274 	/// The only times you might actually need it are for < style > and < script > tags in html.
3275 	/// Other than that, innerHTML and/or innerText should do the job.
3276 	@property void innerRawSource(string rawSource) {
3277 		children.length = 0;
3278 		auto rs = new RawSource(parentDocument, rawSource);
3279 		children ~= rs;
3280 		rs.parentNode = this;
3281 	}
3282 
3283 	/++
3284 		Replaces the element `find`, which must be a child of `this`, with the element `replace`, which must have no parent.
3285 	+/
3286 	Element replaceChild(Element find, Element replace)
3287 		in {
3288 			assert(find !is null);
3289 			assert(find.parentNode is this);
3290 			assert(replace !is null);
3291 			assert(replace.parentNode is null);
3292 		}
3293 		out(ret) {
3294 			assert(ret is replace);
3295 			assert(replace.parentNode is this);
3296 			assert(replace.parentDocument is this.parentDocument);
3297 			assert(find.parentNode is null);
3298 		}
3299 	do {
3300 		// FIXME
3301 		//if(auto frag = cast(DocumentFragment) replace)
3302 			//return this.replaceChild(frag, replace.children);
3303 		for(int i = 0; i < children.length; i++) {
3304 			if(children[i] is find) {
3305 				replace.parentNode = this;
3306 				children[i].parentNode = null;
3307 				children[i] = replace;
3308 				return replace;
3309 			}
3310 		}
3311 
3312 		throw new Exception("no such child ");// ~  find.toString ~ " among " ~ typeid(this).toString);//.toString ~ " magic \n\n\n" ~ find.parentNode.toString);
3313 	}
3314 
3315 	/**
3316 		Replaces the given element with a whole group.
3317 	*/
3318 	void replaceChild(Element find, Element[] replace)
3319 		in {
3320 			assert(find !is null);
3321 			assert(replace !is null);
3322 			assert(find.parentNode is this);
3323 			debug foreach(r; replace)
3324 				assert(r.parentNode is null);
3325 		}
3326 		out {
3327 			assert(find.parentNode is null);
3328 			assert(children.length >= replace.length);
3329 			debug foreach(child; children)
3330 				assert(child !is find);
3331 			debug foreach(r; replace)
3332 				assert(r.parentNode is this);
3333 		}
3334 	do {
3335 		if(replace.length == 0) {
3336 			removeChild(find);
3337 			return;
3338 		}
3339 		assert(replace.length);
3340 		for(int i = 0; i < children.length; i++) {
3341 			if(children[i] is find) {
3342 				children[i].parentNode = null; // this element should now be dead
3343 				children[i] = replace[0];
3344 				foreach(e; replace) {
3345 					e.parentNode = this;
3346 				}
3347 
3348 				children = .insertAfter(children, i, replace[1..$]);
3349 
3350 				return;
3351 			}
3352 		}
3353 
3354 		throw new Exception("no such child");
3355 	}
3356 
3357 
3358 	/**
3359 		Removes the given child from this list.
3360 
3361 		Returns the removed element.
3362 	*/
3363 	Element removeChild(Element c)
3364 		in {
3365 			assert(c !is null);
3366 			assert(c.parentNode is this);
3367 		}
3368 		out {
3369 			debug foreach(child; children)
3370 				assert(child !is c);
3371 			assert(c.parentNode is null);
3372 		}
3373 	do {
3374 		foreach(i, e; children) {
3375 			if(e is c) {
3376 				children = children[0..i] ~ children [i+1..$];
3377 				c.parentNode = null;
3378 				return c;
3379 			}
3380 		}
3381 
3382 		throw new Exception("no such child");
3383 	}
3384 
3385 	/// This removes all the children from this element, returning the old list.
3386 	Element[] removeChildren()
3387 		out (ret) {
3388 			assert(children.length == 0);
3389 			debug foreach(r; ret)
3390 				assert(r.parentNode is null);
3391 		}
3392 	do {
3393 		Element[] oldChildren = children.dup;
3394 		foreach(c; oldChildren)
3395 			c.parentNode = null;
3396 
3397 		children.length = 0;
3398 
3399 		return oldChildren;
3400 	}
3401 
3402 	/**
3403 		Fetch the inside text, with all tags stripped out.
3404 
3405 		<p>cool <b>api</b> &amp; code dude<p>
3406 		innerText of that is "cool api & code dude".
3407 
3408 		This does not match what real innerText does!
3409 		http://perfectionkills.com/the-poor-misunderstood-innerText/
3410 
3411 		It is more like [textContent].
3412 
3413 		See_Also:
3414 			[visibleText], which is closer to what the real `innerText`
3415 			does.
3416 	*/
3417 	@scriptable
3418 	@property string innerText() const {
3419 		string s;
3420 		foreach(child; children) {
3421 			if(child.nodeType != NodeType.Text)
3422 				s ~= child.innerText;
3423 			else
3424 				s ~= child.nodeValue();
3425 		}
3426 		return s;
3427 	}
3428 
3429 	/// ditto
3430 	alias textContent = innerText;
3431 
3432 	/++
3433 		Gets the element's visible text, similar to how it would look assuming
3434 		the document was HTML being displayed by a browser. This means it will
3435 		attempt whitespace normalization (unless it is a `<pre>` tag), add `\n`
3436 		characters for `<br>` tags, and I reserve the right to make it process
3437 		additional css and tags in the future.
3438 
3439 		If you need specific output, use the more stable [textContent] property
3440 		or iterate yourself with [tree] or a recursive function with [children].
3441 
3442 		History:
3443 			Added March 25, 2022 (dub v10.8)
3444 	+/
3445 	string visibleText() const {
3446 		return this.visibleTextHelper(this.tagName == "pre");
3447 	}
3448 
3449 	private string visibleTextHelper(bool pre) const {
3450 		string result;
3451 		foreach(thing; this.children) {
3452 			if(thing.nodeType == NodeType.Text)
3453 				result ~= pre ? thing.nodeValue : normalizeWhitespace(thing.nodeValue);
3454 			else if(thing.tagName == "br")
3455 				result ~= "\n";
3456 			else
3457 				result ~= thing.visibleTextHelper(pre || thing.tagName == "pre");
3458 		}
3459 		return result;
3460 	}
3461 
3462 	/**
3463 		Sets the inside text, replacing all children. You don't
3464 		have to worry about entity encoding.
3465 	*/
3466 	@scriptable
3467 	@property void innerText(string text) {
3468 		selfClosed = false;
3469 		Element e = new TextNode(parentDocument, text);
3470 		children = [e];
3471 		e.parentNode = this;
3472 	}
3473 
3474 	/**
3475 		Strips this node out of the document, replacing it with the given text
3476 	*/
3477 	@property void outerText(string text) {
3478 		parentNode.replaceChild(this, new TextNode(parentDocument, text));
3479 	}
3480 
3481 	/**
3482 		Same result as innerText; the tag with all inner tags stripped out
3483 	*/
3484 	@property string outerText() const {
3485 		return innerText;
3486 	}
3487 
3488 
3489 	/* *******************************
3490 	          Miscellaneous
3491 	*********************************/
3492 
3493 	/// This is a full clone of the element. Alias for cloneNode(true) now. Don't extend it.
3494 	@property Element cloned()
3495 	/+
3496 		out(ret) {
3497 			// FIXME: not sure why these fail...
3498 			assert(ret.children.length == this.children.length, format("%d %d", ret.children.length, this.children.length));
3499 			assert(ret.tagName == this.tagName);
3500 		}
3501 	do {
3502 	+/
3503 	{
3504 		return this.cloneNode(true);
3505 	}
3506 
3507 	/// 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.
3508 	Element cloneNode(bool deepClone) {
3509 		auto e = Element.make(this.tagName);
3510 		e.attributes = this.attributes.aadup;
3511 		e.selfClosed = this.selfClosed;
3512 
3513 		if(deepClone) {
3514 			foreach(child; children) {
3515 				e.appendChild(child.cloneNode(true));
3516 			}
3517 		}
3518 		
3519 
3520 		return e;
3521 	}
3522 
3523 	/// W3C DOM interface. Only really meaningful on [TextNode] instances, but the interface is present on the base class.
3524 	string nodeValue() const {
3525 		return "";
3526 	}
3527 
3528 	// should return int
3529 	///.
3530 	@property int nodeType() const {
3531 		return 1;
3532 	}
3533 
3534 
3535 	invariant () {
3536 		debug assert(tagName.indexOf(" ") == -1);
3537 
3538 		// commented cuz it gets into recursive pain and eff dat.
3539 		/+
3540 		if(children !is null)
3541 		foreach(child; children) {
3542 		//	assert(parentNode !is null);
3543 			assert(child !is null);
3544 			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));
3545 			assert(child !is this);
3546 			//assert(child !is parentNode);
3547 		}
3548 		+/
3549 
3550 		/+
3551 		// this isn't helping
3552 		if(parent_ && parent_.asElement) {
3553 			bool found = false;
3554 			foreach(child; parent_.asElement.children)
3555 				if(child is this)
3556 					found = true;
3557 			assert(found, format("%s lists %s as parent, but it is not in children", typeid(this), typeid(this.parent_.asElement)));
3558 		}
3559 		+/
3560 
3561 		/+ // only depend on parentNode's accuracy if you shuffle things around and use the top elements - where the contracts guarantee it on out
3562 		if(parentNode !is null) {
3563 			// if you have a parent, you should share the same parentDocument; this is appendChild()'s job
3564 			auto lol = cast(TextNode) this;
3565 			assert(parentDocument is parentNode.parentDocument, lol is null ? this.tagName : lol.contents);
3566 		}
3567 		+/
3568 		//assert(parentDocument !is null); // no more; if it is present, we use it, but it is not required
3569 		// reason is so you can create these without needing a reference to the document
3570 	}
3571 
3572 	/**
3573 		Turns the whole element, including tag, attributes, and children, into a string which could be pasted into
3574 		an XML file.
3575 	*/
3576 	override string toString() const {
3577 		return writeToAppender();
3578 	}
3579 
3580 	/++
3581 		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`.
3582 
3583 
3584 		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.
3585 
3586 		History:
3587 			Added December 3, 2021 (dub v10.5)
3588 
3589 	+/
3590 	public bool isEmpty() const {
3591 		foreach(child; this.children) {
3592 			// any non-text node is of course not empty since that's a tag
3593 			if(child.nodeType != NodeType.Text)
3594 				return false;
3595 			// or a text node is empty if it is is a null or empty string, so this length check fixes that
3596 			if(child.nodeValue.length)
3597 				return false;
3598 		}
3599 
3600 		return true;
3601 	}
3602 
3603 	protected string toPrettyStringIndent(bool insertComments, int indentationLevel, string indentWith) const {
3604 		if(indentWith is null)
3605 			return null;
3606 
3607 		// at the top we don't have anything to really do
3608 		//if(parent_ is null)
3609 			//return null;
3610 
3611 			// I've used isEmpty before but this other check seems better....
3612 			//|| this.isEmpty())
3613 
3614 		string s;
3615 
3616 		if(insertComments) s ~= "<!--";
3617 		s ~= "\n";
3618 		foreach(indent; 0 .. indentationLevel)
3619 			s ~= indentWith;
3620 		if(insertComments) s ~= "-->";
3621 
3622 		return s;
3623 	}
3624 
3625 	/++
3626 		Writes out with formatting. Be warned: formatting changes the contents. Use ONLY
3627 		for eyeball debugging.
3628 
3629 		$(PITFALL
3630 			This function is not stable. Its interface and output may change without
3631 			notice. The only promise I make is that it will continue to make a best-
3632 			effort attempt at being useful for debugging by human eyes.
3633 
3634 			I have used it in the past for diffing html documents, but even then, it
3635 			might change between versions. If it is useful, great, but beware; this
3636 			use is at your own risk.
3637 		)
3638 
3639 		History:
3640 			On November 19, 2021, I changed this to `final`. If you were overriding it,
3641 			change our override to `toPrettyStringImpl` instead. It now just calls
3642 			`toPrettyStringImpl.strip` to be an entry point for a stand-alone call.
3643 
3644 			If you are calling it as part of another implementation, you might want to
3645 			change that call to `toPrettyStringImpl` as well.
3646 
3647 			I am NOT considering this a breaking change since this function is documented
3648 			to only be used for eyeball debugging anyway, which means the exact format is
3649 			not specified and the override behavior can generally not be relied upon.
3650 
3651 			(And I find it extremely unlikely anyone was subclassing anyway, but if you were,
3652 			email me, and we'll see what we can do. I'd like to know at least.)
3653 
3654 			I reserve the right to make future changes in the future without considering
3655 			them breaking as well.
3656 	+/
3657 	final string toPrettyString(bool insertComments = false, int indentationLevel = 0, string indentWith = "\t") const {
3658 		return toPrettyStringImpl(insertComments, indentationLevel, indentWith).strip;
3659 	}
3660 
3661 	string toPrettyStringImpl(bool insertComments = false, int indentationLevel = 0, string indentWith = "\t") const {
3662 
3663 		// first step is to concatenate any consecutive text nodes to simplify
3664 		// the white space analysis. this changes the tree! but i'm allowed since
3665 		// the comment always says it changes the comments
3666 		//
3667 		// actually i'm not allowed cuz it is const so i will cheat and lie
3668 		/+
3669 		TextNode lastTextChild = null;
3670 		for(int a = 0; a < this.children.length; a++) {
3671 			auto child = this.children[a];
3672 			if(auto tn = cast(TextNode) child) {
3673 				if(lastTextChild) {
3674 					lastTextChild.contents ~= tn.contents;
3675 					for(int b = a; b < this.children.length - 1; b++)
3676 						this.children[b] = this.children[b + 1];
3677 					this.children = this.children[0 .. $-1];
3678 				} else {
3679 					lastTextChild = tn;
3680 				}
3681 			} else {
3682 				lastTextChild = null;
3683 			}
3684 		}
3685 		+/
3686 
3687 		auto inlineElements = (parentDocument is null ? null : parentDocument.inlineElements);
3688 
3689 		const(Element)[] children;
3690 
3691 		TextNode lastTextChild = null;
3692 		for(int a = 0; a < this.children.length; a++) {
3693 			auto child = this.children[a];
3694 			if(auto tn = cast(const(TextNode)) child) {
3695 				if(lastTextChild !is null) {
3696 					lastTextChild.contents ~= tn.contents;
3697 				} else {
3698 					lastTextChild = new TextNode("");
3699 					lastTextChild.parentNode = cast(Element) this;
3700 					lastTextChild.contents ~= tn.contents;
3701 					children ~= lastTextChild;
3702 				}
3703 			} else {
3704 				lastTextChild = null;
3705 				children ~= child;
3706 			}
3707 		}
3708 
3709 		string s = toPrettyStringIndent(insertComments, indentationLevel, indentWith);
3710 
3711 		s ~= "<";
3712 		s ~= tagName;
3713 
3714 		// i sort these for consistent output. might be more legible
3715 		// but especially it keeps it the same for diff purposes.
3716 		import std.algorithm : sort;
3717 		auto keys = sort(attributes.keys);
3718 		foreach(n; keys) {
3719 			auto v = attributes[n];
3720 			s ~= " ";
3721 			s ~= n;
3722 			s ~= "=\"";
3723 			s ~= htmlEntitiesEncode(v);
3724 			s ~= "\"";
3725 		}
3726 
3727 		if(selfClosed){
3728 			s ~= " />";
3729 			return s;
3730 		}
3731 
3732 		s ~= ">";
3733 
3734 		// for simple `<collection><item>text</item><item>text</item></collection>`, let's
3735 		// just keep them on the same line
3736 
3737 		if(isEmpty) {
3738 			// no work needed, this is empty so don't indent just for a blank line
3739 		} else if(children.length == 1 && children[0].isEmpty) {
3740 			// just one empty one, can put it inline too
3741 			s ~= children[0].toString();
3742 		} else if(tagName.isInArray(inlineElements) || allAreInlineHtml(children, inlineElements)) {
3743 			foreach(child; children) {
3744 				s ~= child.toString();//toPrettyString(false, 0, null);
3745 			}
3746 		} else {
3747 			foreach(child; children) {
3748 				assert(child !is null);
3749 
3750 				s ~= child.toPrettyStringImpl(insertComments, indentationLevel + 1, indentWith);
3751 			}
3752 
3753 			s ~= toPrettyStringIndent(insertComments, indentationLevel, indentWith);
3754 		}
3755 
3756 		s ~= "</";
3757 		s ~= tagName;
3758 		s ~= ">";
3759 
3760 		return s;
3761 	}
3762 
3763 	/+
3764 	/// Writes out the opening tag only, if applicable.
3765 	string writeTagOnly(Appender!string where = appender!string()) const {
3766 	+/
3767 
3768 	/// This is the actual implementation used by toString. You can pass it a preallocated buffer to save some time.
3769 	/// Note: the ordering of attributes in the string is undefined.
3770 	/// Returns the string it creates.
3771 	string writeToAppender(Appender!string where = appender!string()) const {
3772 		assert(tagName !is null);
3773 
3774 		where.reserve((this.children.length + 1) * 512);
3775 
3776 		auto start = where.data.length;
3777 
3778 		where.put("<");
3779 		where.put(tagName);
3780 
3781 		import std.algorithm : sort;
3782 		auto keys = sort(attributes.keys);
3783 		foreach(n; keys) {
3784 			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.
3785 			//assert(v !is null);
3786 			where.put(" ");
3787 			where.put(n);
3788 			where.put("=\"");
3789 			htmlEntitiesEncode(v, where);
3790 			where.put("\"");
3791 		}
3792 
3793 		if(selfClosed){
3794 			where.put(" />");
3795 			return where.data[start .. $];
3796 		}
3797 
3798 		where.put('>');
3799 
3800 		innerHTML(where);
3801 
3802 		where.put("</");
3803 		where.put(tagName);
3804 		where.put('>');
3805 
3806 		return where.data[start .. $];
3807 	}
3808 
3809 	/**
3810 		Returns a lazy range of all its children, recursively.
3811 	*/
3812 	@property ElementStream tree() {
3813 		return new ElementStream(this);
3814 	}
3815 
3816 	// I moved these from Form because they are generally useful.
3817 	// Ideally, I'd put them in arsd.html and use UFCS, but that doesn't work with the opDispatch here.
3818 	// FIXME: add overloads for other label types...
3819 	/++
3820 		Adds a form field to this element, normally a `<input>` but `type` can also be `"textarea"`.
3821 
3822 		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.
3823 	+/
3824 	/// Tags: HTML, HTML5
3825 	Element addField(string label, string name, string type = "text", FormFieldOptions fieldOptions = FormFieldOptions.none) {
3826 		auto fs = this;
3827 		auto i = fs.addChild("label");
3828 
3829 		if(!(type == "checkbox" || type == "radio"))
3830 			i.addChild("span", label);
3831 
3832 		Element input;
3833 		if(type == "textarea")
3834 			input = i.addChild("textarea").
3835 			setAttribute("name", name).
3836 			setAttribute("rows", "6");
3837 		else
3838 			input = i.addChild("input").
3839 			setAttribute("name", name).
3840 			setAttribute("type", type);
3841 
3842 		if(type == "checkbox" || type == "radio")
3843 			i.addChild("span", label);
3844 
3845 		// 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.
3846 		fieldOptions.applyToElement(input);
3847 		return i;
3848 	}
3849 
3850 	/// ditto
3851 	Element addField(Element label, string name, string type = "text", FormFieldOptions fieldOptions = FormFieldOptions.none) {
3852 		auto fs = this;
3853 		auto i = fs.addChild("label");
3854 		i.addChild(label);
3855 		Element input;
3856 		if(type == "textarea")
3857 			input = i.addChild("textarea").
3858 			setAttribute("name", name).
3859 			setAttribute("rows", "6");
3860 		else
3861 			input = i.addChild("input").
3862 			setAttribute("name", name).
3863 			setAttribute("type", type);
3864 
3865 		// 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.
3866 		fieldOptions.applyToElement(input);
3867 		return i;
3868 	}
3869 
3870 	/// ditto
3871 	Element addField(string label, string name, FormFieldOptions fieldOptions) {
3872 		return addField(label, name, "text", fieldOptions);
3873 	}
3874 
3875 	/// ditto
3876 	Element addField(string label, string name, string[string] options, FormFieldOptions fieldOptions = FormFieldOptions.none) {
3877 		auto fs = this;
3878 		auto i = fs.addChild("label");
3879 		i.addChild("span", label);
3880 		auto sel = i.addChild("select").setAttribute("name", name);
3881 
3882 		foreach(k, opt; options)
3883 			sel.addChild("option", opt, k);
3884 
3885 		// FIXME: implement requirements somehow
3886 
3887 		return i;
3888 	}
3889 
3890 	/// ditto
3891 	Element addSubmitButton(string label = null) {
3892 		auto t = this;
3893 		auto holder = t.addChild("div");
3894 		holder.addClass("submit-holder");
3895 		auto i = holder.addChild("input");
3896 		i.type = "submit";
3897 		if(label.length)
3898 			i.value = label;
3899 		return holder;
3900 	}
3901 
3902 }
3903 // computedStyle could argubaly be removed to bring size down
3904 //pragma(msg, __traits(classInstanceSize, Element));
3905 //pragma(msg, Element.tupleof);
3906 
3907 // FIXME: since Document loosens the input requirements, it should probably be the sub class...
3908 /// Specializes Document for handling generic XML. (always uses strict mode, uses xml mime type and file header)
3909 /// Group: core_functionality
3910 class XmlDocument : Document {
3911 	this(string data) {
3912 		selfClosedElements = null;
3913 		inlineElements = null;
3914 		contentType = "text/xml; charset=utf-8";
3915 		_prolog = `<?xml version="1.0" encoding="UTF-8"?>` ~ "\n";
3916 
3917 		parseStrict(data);
3918 	}
3919 }
3920 
3921 
3922 
3923 
3924 import std.string;
3925 
3926 /* domconvenience follows { */
3927 
3928 /// finds comments that match the given txt. Case insensitive, strips whitespace.
3929 /// Group: core_functionality
3930 Element[] findComments(Document document, string txt) {
3931 	return findComments(document.root, txt);
3932 }
3933 
3934 /// ditto
3935 Element[] findComments(Element element, string txt) {
3936 	txt = txt.strip().toLower();
3937 	Element[] ret;
3938 
3939 	foreach(comment; element.getElementsByTagName("#comment")) {
3940 		string t = comment.nodeValue().strip().toLower();
3941 		if(t == txt)
3942 			ret ~= comment;
3943 	}
3944 
3945 	return ret;
3946 }
3947 
3948 /// An option type that propagates null. See: [Element.optionSelector]
3949 /// Group: implementations
3950 struct MaybeNullElement(SomeElementType) {
3951 	this(SomeElementType ele) {
3952 		this.element = ele;
3953 	}
3954 	SomeElementType element;
3955 
3956 	/// Forwards to the element, wit a null check inserted that propagates null.
3957 	auto opDispatch(string method, T...)(T args) {
3958 		alias type = typeof(__traits(getMember, element, method)(args));
3959 		static if(is(type : Element)) {
3960 			if(element is null)
3961 				return MaybeNullElement!type(null);
3962 			return __traits(getMember, element, method)(args);
3963 		} else static if(is(type == string)) {
3964 			if(element is null)
3965 				return cast(string) null;
3966 			return __traits(getMember, element, method)(args);
3967 		} else static if(is(type == void)) {
3968 			if(element is null)
3969 				return;
3970 			__traits(getMember, element, method)(args);
3971 		} else {
3972 			static assert(0);
3973 		}
3974 	}
3975 
3976 	/// Allows implicit casting to the wrapped element.
3977 	alias element this;
3978 }
3979 
3980 /++
3981 	A collection of elements which forwards methods to the children.
3982 +/
3983 /// Group: implementations
3984 struct ElementCollection {
3985 	///
3986 	this(Element e) {
3987 		elements = [e];
3988 	}
3989 
3990 	///
3991 	this(Element e, string selector) {
3992 		elements = e.querySelectorAll(selector);
3993 	}
3994 
3995 	///
3996 	this(Element[] e) {
3997 		elements = e;
3998 	}
3999 
4000 	Element[] elements;
4001 	//alias elements this; // let it implicitly convert to the underlying array
4002 
4003 	///
4004 	ElementCollection opIndex(string selector) {
4005 		ElementCollection ec;
4006 		foreach(e; elements)
4007 			ec.elements ~= e.getElementsBySelector(selector);
4008 		return ec;
4009 	}
4010 
4011 	///
4012 	Element opIndex(int i) {
4013 		return elements[i];
4014 	}
4015 
4016 	/// if you slice it, give the underlying array for easy forwarding of the
4017 	/// collection to range expecting algorithms or looping over.
4018 	Element[] opSlice() {
4019 		return elements;
4020 	}
4021 
4022 	/// And input range primitives so we can foreach over this
4023 	void popFront() {
4024 		elements = elements[1..$];
4025 	}
4026 
4027 	/// ditto
4028 	Element front() {
4029 		return elements[0];
4030 	}
4031 
4032 	/// ditto
4033 	bool empty() {
4034 		return !elements.length;
4035 	}
4036 
4037 	/++
4038 		Collects strings from the collection, concatenating them together
4039 		Kinda like running reduce and ~= on it.
4040 
4041 		---
4042 		document["p"].collect!"innerText";
4043 		---
4044 	+/
4045 	string collect(string method)(string separator = "") {
4046 		string text;
4047 		foreach(e; elements) {
4048 			text ~= mixin("e." ~ method);
4049 			text ~= separator;
4050 		}
4051 		return text;
4052 	}
4053 
4054 	/// Forward method calls to each individual [Element|element] of the collection
4055 	/// returns this so it can be chained.
4056 	ElementCollection opDispatch(string name, T...)(T t) {
4057 		foreach(e; elements) {
4058 			mixin("e." ~ name)(t);
4059 		}
4060 		return this;
4061 	}
4062 
4063 	/++
4064 		Calls [Element.wrapIn] on each member of the collection, but clones the argument `what` for each one.
4065 	+/
4066 	ElementCollection wrapIn(Element what) {
4067 		foreach(e; elements) {
4068 			e.wrapIn(what.cloneNode(false));
4069 		}
4070 
4071 		return this;
4072 	}
4073 
4074 	/// Concatenates two ElementCollection together.
4075 	ElementCollection opBinary(string op : "~")(ElementCollection rhs) {
4076 		return ElementCollection(this.elements ~ rhs.elements);
4077 	}
4078 }
4079 
4080 
4081 /// this puts in operators and opDispatch to handle string indexes and properties, forwarding to get and set functions.
4082 /// Group: implementations
4083 mixin template JavascriptStyleDispatch() {
4084 	///
4085 	string opDispatch(string name)(string v = null) if(name != "popFront") { // popFront will make this look like a range. Do not want.
4086 		if(v !is null)
4087 			return set(name, v);
4088 		return get(name);
4089 	}
4090 
4091 	///
4092 	string opIndex(string key) const {
4093 		return get(key);
4094 	}
4095 
4096 	///
4097 	string opIndexAssign(string value, string field) {
4098 		return set(field, value);
4099 	}
4100 
4101 	// FIXME: doesn't seem to work
4102 	string* opBinary(string op)(string key)  if(op == "in") {
4103 		return key in fields;
4104 	}
4105 }
4106 
4107 /// A proxy object to do the Element class' dataset property. See Element.dataset for more info.
4108 ///
4109 /// Do not create this object directly.
4110 /// Group: implementations
4111 struct DataSet {
4112 	///
4113 	this(Element e) {
4114 		this._element = e;
4115 	}
4116 
4117 	private Element _element;
4118 	///
4119 	string set(string name, string value) {
4120 		_element.setAttribute("data-" ~ unCamelCase(name), value);
4121 		return value;
4122 	}
4123 
4124 	///
4125 	string get(string name) const {
4126 		return _element.getAttribute("data-" ~ unCamelCase(name));
4127 	}
4128 
4129 	///
4130 	mixin JavascriptStyleDispatch!();
4131 }
4132 
4133 /// Proxy object for attributes which will replace the main opDispatch eventually
4134 /// Group: implementations
4135 struct AttributeSet {
4136 	///
4137 	this(Element e) {
4138 		this._element = e;
4139 	}
4140 
4141 	private Element _element;
4142 	///
4143 	string set(string name, string value) {
4144 		_element.setAttribute(name, value);
4145 		return value;
4146 	}
4147 
4148 	///
4149 	string get(string name) const {
4150 		return _element.getAttribute(name);
4151 	}
4152 
4153 	///
4154 	mixin JavascriptStyleDispatch!();
4155 }
4156 
4157 
4158 
4159 /// for style, i want to be able to set it with a string like a plain attribute,
4160 /// but also be able to do properties Javascript style.
4161 
4162 /// Group: implementations
4163 struct ElementStyle {
4164 	this(Element parent) {
4165 		_element = parent;
4166 	}
4167 
4168 	Element _element;
4169 
4170 	@property ref inout(string) _attribute() inout {
4171 		auto s = "style" in _element.attributes;
4172 		if(s is null) {
4173 			auto e = cast() _element; // const_cast
4174 			e.attributes["style"] = ""; // we need something to reference
4175 			s = cast(inout) ("style" in e.attributes);
4176 		}
4177 
4178 		assert(s !is null);
4179 		return *s;
4180 	}
4181 
4182 	alias _attribute this; // this is meant to allow element.style = element.style ~ " string "; to still work.
4183 
4184 	string set(string name, string value) {
4185 		if(name.length == 0)
4186 			return value;
4187 		if(name == "cssFloat")
4188 			name = "float";
4189 		else
4190 			name = unCamelCase(name);
4191 		auto r = rules();
4192 		r[name] = value;
4193 
4194 		_attribute = "";
4195 		foreach(k, v; r) {
4196 			if(v is null || v.length == 0) /* css can't do empty rules anyway so we'll use that to remove */
4197 				continue;
4198 			if(_attribute.length)
4199 				_attribute ~= " ";
4200 			_attribute ~= k ~ ": " ~ v ~ ";";
4201 		}
4202 
4203 		_element.setAttribute("style", _attribute); // this is to trigger the observer call
4204 
4205 		return value;
4206 	}
4207 	string get(string name) const {
4208 		if(name == "cssFloat")
4209 			name = "float";
4210 		else
4211 			name = unCamelCase(name);
4212 		auto r = rules();
4213 		if(name in r)
4214 			return r[name];
4215 		return null;
4216 	}
4217 
4218 	string[string] rules() const {
4219 		string[string] ret;
4220 		foreach(rule;  _attribute.split(";")) {
4221 			rule = rule.strip();
4222 			if(rule.length == 0)
4223 				continue;
4224 			auto idx = rule.indexOf(":");
4225 			if(idx == -1)
4226 				ret[rule] = "";
4227 			else {
4228 				auto name = rule[0 .. idx].strip();
4229 				auto value = rule[idx + 1 .. $].strip();
4230 
4231 				ret[name] = value;
4232 			}
4233 		}
4234 
4235 		return ret;
4236 	}
4237 
4238 	mixin JavascriptStyleDispatch!();
4239 }
4240 
4241 /// Converts a camel cased propertyName to a css style dashed property-name
4242 string unCamelCase(string a) {
4243 	string ret;
4244 	foreach(c; a)
4245 		if((c >= 'A' && c <= 'Z'))
4246 			ret ~= "-" ~ toLower("" ~ c)[0];
4247 		else
4248 			ret ~= c;
4249 	return ret;
4250 }
4251 
4252 /// Translates a css style property-name to a camel cased propertyName
4253 string camelCase(string a) {
4254 	string ret;
4255 	bool justSawDash = false;
4256 	foreach(c; a)
4257 		if(c == '-') {
4258 			justSawDash = true;
4259 		} else {
4260 			if(justSawDash) {
4261 				justSawDash = false;
4262 				ret ~= toUpper("" ~ c);
4263 			} else
4264 				ret ~= c;
4265 		}
4266 	return ret;
4267 }
4268 
4269 
4270 
4271 
4272 
4273 
4274 
4275 
4276 
4277 // domconvenience ends }
4278 
4279 
4280 
4281 
4282 
4283 
4284 
4285 
4286 
4287 
4288 
4289 // @safe:
4290 
4291 // NOTE: do *NOT* override toString on Element subclasses. It won't work.
4292 // Instead, override writeToAppender();
4293 
4294 // 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.
4295 
4296 // Stripping them is useful for reading php as html.... but adding them
4297 // is good for building php.
4298 
4299 // I need to maintain compatibility with the way it is now too.
4300 
4301 import std.string;
4302 import std.exception;
4303 import std.uri;
4304 import std.array;
4305 import std.range;
4306 
4307 //import std.stdio;
4308 
4309 // 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
4310 // 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
4311 // most likely a typo so I say kill kill kill.
4312 
4313 
4314 /++
4315 	This might belong in another module, but it represents a file with a mime type and some data.
4316 	Document implements this interface with type = text/html (see Document.contentType for more info)
4317 	and data = document.toString, so you can return Documents anywhere web.d expects FileResources.
4318 +/
4319 /// Group: bonus_functionality
4320 interface FileResource {
4321 	/// the content-type of the file. e.g. "text/html; charset=utf-8" or "image/png"
4322 	@property string contentType() const;
4323 	/// the data
4324 	immutable(ubyte)[] getData() const;
4325 	/++
4326 		filename, return null if none
4327 
4328 		History:
4329 			Added December 25, 2020
4330 	+/
4331 	@property string filename() const;
4332 }
4333 
4334 
4335 
4336 
4337 ///.
4338 /// Group: bonus_functionality
4339 enum NodeType { Text = 3 }
4340 
4341 
4342 /// You can use this to do an easy null check or a dynamic cast+null check on any element.
4343 /// Group: core_functionality
4344 T require(T = Element, string file = __FILE__, int line = __LINE__)(Element e) if(is(T : Element))
4345 	in {}
4346 	out(ret) { assert(ret !is null); }
4347 do {
4348 	auto ret = cast(T) e;
4349 	if(ret is null)
4350 		throw new ElementNotFoundException(T.stringof, "passed value", e, file, line);
4351 	return ret;
4352 }
4353 
4354 
4355 ///.
4356 /// Group: core_functionality
4357 class DocumentFragment : Element {
4358 	///.
4359 	this(Document _parentDocument) {
4360 		tagName = "#fragment";
4361 		super(_parentDocument);
4362 	}
4363 
4364 	/++
4365 		Creates a document fragment from the given HTML. Note that the HTML is assumed to close all tags contained inside it.
4366 
4367 		Since: March 29, 2018 (or git tagged v2.1.0)
4368 	+/
4369 	this(Html html) {
4370 		this(null);
4371 
4372 		this.innerHTML = html.source;
4373 	}
4374 
4375 	///.
4376 	override string writeToAppender(Appender!string where = appender!string()) const {
4377 		return this.innerHTML(where);
4378 	}
4379 
4380 	override string toPrettyStringImpl(bool insertComments, int indentationLevel, string indentWith) const {
4381 		string s;
4382 		foreach(child; children)
4383 			s ~= child.toPrettyStringImpl(insertComments, indentationLevel, indentWith);
4384 		return s;
4385 	}
4386 
4387 	/// DocumentFragments don't really exist in a dom, so they ignore themselves in parent nodes
4388 	/*
4389 	override inout(Element) parentNode() inout {
4390 		return children.length ? children[0].parentNode : null;
4391 	}
4392 	*/
4393 	/+
4394 	override Element parentNode(Element p) {
4395 		this.parentNode = p;
4396 		foreach(child; children)
4397 			child.parentNode = p;
4398 		return p;
4399 	}
4400 	+/
4401 }
4402 
4403 /// Given text, encode all html entities on it - &, <, >, and ". This function also
4404 /// encodes all 8 bit characters as entities, thus ensuring the resultant text will work
4405 /// even if your charset isn't set right. You can suppress with by setting encodeNonAscii = false
4406 ///
4407 /// The output parameter can be given to append to an existing buffer. You don't have to
4408 /// pass one; regardless, the return value will be usable for you, with just the data encoded.
4409 /// Group: core_functionality
4410 string htmlEntitiesEncode(string data, Appender!string output = appender!string(), bool encodeNonAscii = true) {
4411 	// if there's no entities, we can save a lot of time by not bothering with the
4412 	// decoding loop. This check cuts the net toString time by better than half in my test.
4413 	// let me know if it made your tests worse though, since if you use an entity in just about
4414 	// every location, the check will add time... but I suspect the average experience is like mine
4415 	// since the check gives up as soon as it can anyway.
4416 
4417 	bool shortcut = true;
4418 	foreach(char c; data) {
4419 		// non ascii chars are always higher than 127 in utf8; we'd better go to the full decoder if we see it.
4420 		if(c == '<' || c == '>' || c == '"' || c == '&' || (encodeNonAscii && cast(uint) c > 127)) {
4421 			shortcut = false; // there's actual work to be done
4422 			break;
4423 		}
4424 	}
4425 
4426 	if(shortcut) {
4427 		output.put(data);
4428 		return data;
4429 	}
4430 
4431 	auto start = output.data.length;
4432 
4433 	output.reserve(data.length + 64); // grab some extra space for the encoded entities
4434 
4435 	foreach(dchar d; data) {
4436 		if(d == '&')
4437 			output.put("&amp;");
4438 		else if (d == '<')
4439 			output.put("&lt;");
4440 		else if (d == '>')
4441 			output.put("&gt;");
4442 		else if (d == '\"')
4443 			output.put("&quot;");
4444 //		else if (d == '\'')
4445 //			output.put("&#39;"); // if you are in an attribute, it might be important to encode for the same reason as double quotes
4446 			// FIXME: should I encode apostrophes too? as &#39;... I could also do space but if your html is so bad that it doesn't
4447 			// quote attributes at all, maybe you deserve the xss. Encoding spaces will make everything really ugly so meh
4448 			// idk about apostrophes though. Might be worth it, might not.
4449 		else if (!encodeNonAscii || (d < 128 && d > 0))
4450 			output.put(d);
4451 		else
4452 			output.put("&#" ~ std.conv.to!string(cast(int) d) ~ ";");
4453 	}
4454 
4455 	//assert(output !is null); // this fails on empty attributes.....
4456 	return output.data[start .. $];
4457 
4458 //	data = data.replace("\u00a0", "&nbsp;");
4459 }
4460 
4461 /// An alias for htmlEntitiesEncode; it works for xml too
4462 /// Group: core_functionality
4463 string xmlEntitiesEncode(string data) {
4464 	return htmlEntitiesEncode(data);
4465 }
4466 
4467 /// This helper function is used for decoding html entities. It has a hard-coded list of entities and characters.
4468 /// Group: core_functionality
4469 dchar parseEntity(in dchar[] entity) {
4470 
4471 	char[128] buffer;
4472 	int bpos;
4473 	foreach(char c; entity[1 .. $-1])
4474 		buffer[bpos++] = c;
4475 	char[] entityAsString = buffer[0 .. bpos];
4476 
4477 	int min = 0;
4478 	int max = cast(int) availableEntities.length;
4479 
4480 	keep_looking:
4481 	if(min + 1 < max) {
4482 		int spot = (max - min) / 2 + min;
4483 		if(availableEntities[spot] == entityAsString) {
4484 			return availableEntitiesValues[spot];
4485 		} else if(entityAsString < availableEntities[spot]) {
4486 			max = spot;
4487 			goto keep_looking;
4488 		} else {
4489 			min = spot;
4490 			goto keep_looking;
4491 		}
4492 	}
4493 
4494 	switch(entity[1..$-1]) {
4495 		case "quot":
4496 			return '"';
4497 		case "apos":
4498 			return '\'';
4499 		case "lt":
4500 			return '<';
4501 		case "gt":
4502 			return '>';
4503 		case "amp":
4504 			return '&';
4505 		// the next are html rather than xml
4506 
4507 		// and handling numeric entities
4508 		default:
4509 			if(entity[1] == '#') {
4510 				if(entity[2] == 'x' /*|| (!strict && entity[2] == 'X')*/) {
4511 					auto hex = entity[3..$-1];
4512 
4513 					auto p = intFromHex(to!string(hex).toLower());
4514 					return cast(dchar) p;
4515 				} else {
4516 					auto decimal = entity[2..$-1];
4517 
4518 					// dealing with broken html entities
4519 					while(decimal.length && (decimal[0] < '0' || decimal[0] >   '9'))
4520 						decimal = decimal[1 .. $];
4521 
4522 					while(decimal.length && (decimal[$-1] < '0' || decimal[$-1] >   '9'))
4523 						decimal = decimal[0 .. $ - 1];
4524 
4525 					if(decimal.length == 0)
4526 						return ' '; // this is really broken html
4527 					// done with dealing with broken stuff
4528 
4529 					auto p = std.conv.to!int(decimal);
4530 					return cast(dchar) p;
4531 				}
4532 			} else
4533 				return '\ufffd'; // replacement character diamond thing
4534 	}
4535 
4536 	assert(0);
4537 }
4538 
4539 unittest {
4540 	// not in the binary search
4541 	assert(parseEntity("&quot;"d) == '"');
4542 
4543 	// numeric value
4544 	assert(parseEntity("&#x0534;") == '\u0534');
4545 
4546 	// not found at all
4547 	assert(parseEntity("&asdasdasd;"d) == '\ufffd');
4548 
4549 	// random values in the bin search
4550 	assert(parseEntity("&Tab;"d) == '\t');
4551 	assert(parseEntity("&raquo;"d) == '\&raquo;');
4552 
4553 	// near the middle and edges of the bin search
4554 	assert(parseEntity("&ascr;"d) == '\U0001d4b6');
4555 	assert(parseEntity("&ast;"d) == '\u002a');
4556 	assert(parseEntity("&AElig;"d) == '\u00c6');
4557 	assert(parseEntity("&zwnj;"d) == '\u200c');
4558 }
4559 
4560 import std.utf;
4561 import std.stdio;
4562 
4563 /// This takes a string of raw HTML and decodes the entities into a nice D utf-8 string.
4564 /// By default, it uses loose mode - it will try to return a useful string from garbage input too.
4565 /// Set the second parameter to true if you'd prefer it to strictly throw exceptions on garbage input.
4566 /// Group: core_functionality
4567 string htmlEntitiesDecode(string data, bool strict = false) {
4568 	// this check makes a *big* difference; about a 50% improvement of parse speed on my test.
4569 	if(data.indexOf("&") == -1) // all html entities begin with &
4570 		return data; // if there are no entities in here, we can return the original slice and save some time
4571 
4572 	char[] a; // this seems to do a *better* job than appender!
4573 
4574 	char[4] buffer;
4575 
4576 	bool tryingEntity = false;
4577 	bool tryingNumericEntity = false;
4578 	bool tryingHexEntity = false;
4579 	dchar[16] entityBeingTried;
4580 	int entityBeingTriedLength = 0;
4581 	int entityAttemptIndex = 0;
4582 
4583 	foreach(dchar ch; data) {
4584 		if(tryingEntity) {
4585 			entityAttemptIndex++;
4586 			entityBeingTried[entityBeingTriedLength++] = ch;
4587 
4588 			if(entityBeingTriedLength == 2 && ch == '#') {
4589 				tryingNumericEntity = true;
4590 				continue;
4591 			} else if(tryingNumericEntity && entityBeingTriedLength == 3 && ch == 'x') {
4592 				tryingHexEntity = true;
4593 				continue;
4594 			}
4595 
4596 			// I saw some crappy html in the wild that looked like &0&#1111; this tries to handle that.
4597 			if(ch == '&') {
4598 				if(strict)
4599 					throw new Exception("unterminated entity; & inside another at " ~ to!string(entityBeingTried[0 .. entityBeingTriedLength]));
4600 
4601 				// if not strict, let's try to parse both.
4602 
4603 				if(entityBeingTried[0 .. entityBeingTriedLength] == "&&") {
4604 					a ~= "&"; // double amp means keep the first one, still try to parse the next one
4605 				} else {
4606 					auto ch2 = parseEntity(entityBeingTried[0 .. entityBeingTriedLength]);
4607 					if(ch2 == '\ufffd') { // either someone put this in intentionally (lol) or we failed to get it
4608 						// but either way, just abort and keep the plain text
4609 						foreach(char c; entityBeingTried[0 .. entityBeingTriedLength - 1]) // cut off the & we're on now
4610 							a ~= c;
4611 					} else {
4612 						a ~= buffer[0.. std.utf.encode(buffer, ch2)];
4613 					}
4614 				}
4615 
4616 				// tryingEntity is still true
4617 				goto new_entity;
4618 			} else
4619 			if(ch == ';') {
4620 				tryingEntity = false;
4621 				a ~= buffer[0.. std.utf.encode(buffer, parseEntity(entityBeingTried[0 .. entityBeingTriedLength]))];
4622 			} else if(ch == ' ') {
4623 				// e.g. you &amp i
4624 				if(strict)
4625 					throw new Exception("unterminated entity at " ~ to!string(entityBeingTried[0 .. entityBeingTriedLength]));
4626 				else {
4627 					tryingEntity = false;
4628 					a ~= to!(char[])(entityBeingTried[0 .. entityBeingTriedLength - 1]);
4629 					a ~= buffer[0 .. std.utf.encode(buffer, ch)];
4630 				}
4631 			} else {
4632 				if(tryingNumericEntity) {
4633 					if(ch < '0' || ch > '9') {
4634 						if(tryingHexEntity) {
4635 							if(ch < 'A')
4636 								goto trouble;
4637 							if(ch > 'Z' && ch < 'a')
4638 								goto trouble;
4639 							if(ch > 'z')
4640 								goto trouble;
4641 						} else {
4642 							trouble:
4643 							if(strict)
4644 								throw new Exception("unterminated entity at " ~ to!string(entityBeingTried[0 .. entityBeingTriedLength]));
4645 							tryingEntity = false;
4646 							a ~= buffer[0.. std.utf.encode(buffer, parseEntity(entityBeingTried[0 .. entityBeingTriedLength]))];
4647 							a ~= ch;
4648 							continue;
4649 						}
4650 					}
4651 				}
4652 
4653 
4654 				if(entityAttemptIndex >= 9) {
4655 					done:
4656 					if(strict)
4657 						throw new Exception("unterminated entity at " ~ to!string(entityBeingTried[0 .. entityBeingTriedLength]));
4658 					else {
4659 						tryingEntity = false;
4660 						a ~= to!(char[])(entityBeingTried[0 .. entityBeingTriedLength]);
4661 					}
4662 				}
4663 			}
4664 		} else {
4665 			if(ch == '&') {
4666 				new_entity:
4667 				tryingEntity = true;
4668 				tryingNumericEntity = false;
4669 				tryingHexEntity = false;
4670 				entityBeingTriedLength = 0;
4671 				entityBeingTried[entityBeingTriedLength++] = ch;
4672 				entityAttemptIndex = 0;
4673 			} else {
4674 				a ~= buffer[0 .. std.utf.encode(buffer, ch)];
4675 			}
4676 		}
4677 	}
4678 
4679 	if(tryingEntity) {
4680 		if(strict)
4681 			throw new Exception("unterminated entity at " ~ to!string(entityBeingTried[0 .. entityBeingTriedLength]));
4682 
4683 		// otherwise, let's try to recover, at least so we don't drop any data
4684 		a ~= to!string(entityBeingTried[0 .. entityBeingTriedLength]);
4685 		// FIXME: what if we have "cool &amp"? should we try to parse it?
4686 	}
4687 
4688 	return cast(string) a; // assumeUnique is actually kinda slow, lol
4689 }
4690 
4691 unittest {
4692 	// error recovery
4693 	assert(htmlEntitiesDecode("&lt;&foo") == "<&foo"); // unterminated turned back to thing
4694 	assert(htmlEntitiesDecode("&lt&foo") == "<&foo"); // semi-terminated... parse and carry on (is this really sane?)
4695 	assert(htmlEntitiesDecode("loc&#61en_us&tracknum&#61;111") == "loc=en_us&tracknum=111"); // a bit of both, seen in a real life email
4696 	assert(htmlEntitiesDecode("&amp test") == "&amp test"); // unterminated, just abort
4697 
4698 	// in strict mode all of these should fail
4699 	try { assert(htmlEntitiesDecode("&lt;&foo", true) == "<&foo"); assert(0); } catch(Exception e) { }
4700 	try { assert(htmlEntitiesDecode("&lt&foo", true) == "<&foo"); assert(0); } catch(Exception e) { }
4701 	try { assert(htmlEntitiesDecode("loc&#61en_us&tracknum&#61;111", true) == "<&foo"); assert(0); } catch(Exception e) { }
4702 	try { assert(htmlEntitiesDecode("&amp test", true) == "& test"); assert(0); } catch(Exception e) { }
4703 
4704 	// correct cases that should pass the same in strict or loose mode
4705 	foreach(strict; [false, true]) {
4706 		assert(htmlEntitiesDecode("&amp;hello&raquo; win", strict) == "&hello\&raquo; win");
4707 	}
4708 }
4709 
4710 /// Group: implementations
4711 abstract class SpecialElement : Element {
4712 	this(Document _parentDocument) {
4713 		super(_parentDocument);
4714 	}
4715 
4716 	///.
4717 	override Element appendChild(Element e) {
4718 		assert(0, "Cannot append to a special node");
4719 	}
4720 
4721 	///.
4722 	@property override int nodeType() const {
4723 		return 100;
4724 	}
4725 }
4726 
4727 ///.
4728 /// Group: implementations
4729 class RawSource : SpecialElement {
4730 	///.
4731 	this(Document _parentDocument, string s) {
4732 		super(_parentDocument);
4733 		source = s;
4734 		tagName = "#raw";
4735 	}
4736 
4737 	///.
4738 	override string nodeValue() const {
4739 		return this.toString();
4740 	}
4741 
4742 	///.
4743 	override string writeToAppender(Appender!string where = appender!string()) const {
4744 		where.put(source);
4745 		return source;
4746 	}
4747 
4748 	override string toPrettyStringImpl(bool, int, string) const {
4749 		return source;
4750 	}
4751 
4752 
4753 	override RawSource cloneNode(bool deep) {
4754 		return new RawSource(parentDocument, source);
4755 	}
4756 
4757 	///.
4758 	string source;
4759 }
4760 
4761 /// Group: implementations
4762 abstract class ServerSideCode : SpecialElement {
4763 	this(Document _parentDocument, string type) {
4764 		super(_parentDocument);
4765 		tagName = "#" ~ type;
4766 	}
4767 
4768 	///.
4769 	override string nodeValue() const {
4770 		return this.source;
4771 	}
4772 
4773 	///.
4774 	override string writeToAppender(Appender!string where = appender!string()) const {
4775 		auto start = where.data.length;
4776 		where.put("<");
4777 		where.put(source);
4778 		where.put(">");
4779 		return where.data[start .. $];
4780 	}
4781 
4782 	override string toPrettyStringImpl(bool, int, string) const {
4783 		return "<" ~ source ~ ">";
4784 	}
4785 
4786 	///.
4787 	string source;
4788 }
4789 
4790 ///.
4791 /// Group: implementations
4792 class PhpCode : ServerSideCode {
4793 	///.
4794 	this(Document _parentDocument, string s) {
4795 		super(_parentDocument, "php");
4796 		source = s;
4797 	}
4798 
4799 	override PhpCode cloneNode(bool deep) {
4800 		return new PhpCode(parentDocument, source);
4801 	}
4802 }
4803 
4804 ///.
4805 /// Group: implementations
4806 class AspCode : ServerSideCode {
4807 	///.
4808 	this(Document _parentDocument, string s) {
4809 		super(_parentDocument, "asp");
4810 		source = s;
4811 	}
4812 
4813 	override AspCode cloneNode(bool deep) {
4814 		return new AspCode(parentDocument, source);
4815 	}
4816 }
4817 
4818 ///.
4819 /// Group: implementations
4820 class BangInstruction : SpecialElement {
4821 	///.
4822 	this(Document _parentDocument, string s) {
4823 		super(_parentDocument);
4824 		source = s;
4825 		tagName = "#bpi";
4826 	}
4827 
4828 	///.
4829 	override string nodeValue() const {
4830 		return this.source;
4831 	}
4832 
4833 	override BangInstruction cloneNode(bool deep) {
4834 		return new BangInstruction(parentDocument, source);
4835 	}
4836 
4837 	///.
4838 	override string writeToAppender(Appender!string where = appender!string()) const {
4839 		auto start = where.data.length;
4840 		where.put("<!");
4841 		where.put(source);
4842 		where.put(">");
4843 		return where.data[start .. $];
4844 	}
4845 
4846 	override string toPrettyStringImpl(bool, int, string) const {
4847 		string s;
4848 		s ~= "<!";
4849 		s ~= source;
4850 		s ~= ">";
4851 		return s;
4852 	}
4853 
4854 	///.
4855 	string source;
4856 }
4857 
4858 ///.
4859 /// Group: implementations
4860 class QuestionInstruction : SpecialElement {
4861 	///.
4862 	this(Document _parentDocument, string s) {
4863 		super(_parentDocument);
4864 		source = s;
4865 		tagName = "#qpi";
4866 	}
4867 
4868 	override QuestionInstruction cloneNode(bool deep) {
4869 		return new QuestionInstruction(parentDocument, source);
4870 	}
4871 
4872 	///.
4873 	override string nodeValue() const {
4874 		return this.source;
4875 	}
4876 
4877 	///.
4878 	override string writeToAppender(Appender!string where = appender!string()) const {
4879 		auto start = where.data.length;
4880 		where.put("<");
4881 		where.put(source);
4882 		where.put(">");
4883 		return where.data[start .. $];
4884 	}
4885 
4886 	override string toPrettyStringImpl(bool, int, string) const {
4887 		string s;
4888 		s ~= "<";
4889 		s ~= source;
4890 		s ~= ">";
4891 		return s;
4892 	}
4893 
4894 
4895 	///.
4896 	string source;
4897 }
4898 
4899 ///.
4900 /// Group: implementations
4901 class HtmlComment : SpecialElement {
4902 	///.
4903 	this(Document _parentDocument, string s) {
4904 		super(_parentDocument);
4905 		source = s;
4906 		tagName = "#comment";
4907 	}
4908 
4909 	override HtmlComment cloneNode(bool deep) {
4910 		return new HtmlComment(parentDocument, source);
4911 	}
4912 
4913 	///.
4914 	override string nodeValue() const {
4915 		return this.source;
4916 	}
4917 
4918 	///.
4919 	override string writeToAppender(Appender!string where = appender!string()) const {
4920 		auto start = where.data.length;
4921 		where.put("<!--");
4922 		where.put(source);
4923 		where.put("-->");
4924 		return where.data[start .. $];
4925 	}
4926 
4927 	override string toPrettyStringImpl(bool, int, string) const {
4928 		string s;
4929 		s ~= "<!--";
4930 		s ~= source;
4931 		s ~= "-->";
4932 		return s;
4933 	}
4934 
4935 
4936 	///.
4937 	string source;
4938 }
4939 
4940 
4941 
4942 
4943 ///.
4944 /// Group: implementations
4945 class TextNode : Element {
4946   public:
4947 	///.
4948 	this(Document _parentDocument, string e) {
4949 		super(_parentDocument);
4950 		contents = e;
4951 		tagName = "#text";
4952 	}
4953 
4954 	///
4955 	this(string e) {
4956 		this(null, e);
4957 	}
4958 
4959 	string opDispatch(string name)(string v = null) if(0) { return null; } // text nodes don't have attributes
4960 
4961 	///.
4962 	static TextNode fromUndecodedString(Document _parentDocument, string html) {
4963 		auto e = new TextNode(_parentDocument, "");
4964 		e.contents = htmlEntitiesDecode(html, _parentDocument is null ? false : !_parentDocument.loose);
4965 		return e;
4966 	}
4967 
4968 	///.
4969 	override @property TextNode cloneNode(bool deep) {
4970 		auto n = new TextNode(parentDocument, contents);
4971 		return n;
4972 	}
4973 
4974 	///.
4975 	override string nodeValue() const {
4976 		return this.contents; //toString();
4977 	}
4978 
4979 	///.
4980 	@property override int nodeType() const {
4981 		return NodeType.Text;
4982 	}
4983 
4984 	///.
4985 	override string writeToAppender(Appender!string where = appender!string()) const {
4986 		string s;
4987 		if(contents.length)
4988 			s = htmlEntitiesEncode(contents, where);
4989 		else
4990 			s = "";
4991 
4992 		assert(s !is null);
4993 		return s;
4994 	}
4995 
4996 	override string toPrettyStringImpl(bool insertComments = false, int indentationLevel = 0, string indentWith = "\t") const {
4997 		string s;
4998 
4999 		string contents = this.contents;
5000 		// we will first collapse the whitespace per html
5001 		// sort of. note this can break stuff yo!!!!
5002 		if(this.parentNode is null || this.parentNode.tagName != "pre") {
5003 			string n = "";
5004 			bool lastWasWhitespace = indentationLevel > 0;
5005 			foreach(char c; contents) {
5006 				if(c.isSimpleWhite) {
5007 					if(!lastWasWhitespace)
5008 						n ~= ' ';
5009 					lastWasWhitespace = true;
5010 				} else {
5011 					n ~= c;
5012 					lastWasWhitespace = false;
5013 				}
5014 			}
5015 
5016 			contents = n;
5017 		}
5018 
5019 		if(this.parentNode !is null && this.parentNode.tagName != "p") {
5020 			contents = contents.strip;
5021 		}
5022 
5023 		auto e = htmlEntitiesEncode(contents);
5024 		import std.algorithm.iteration : splitter;
5025 		bool first = true;
5026 		foreach(line; splitter(e, "\n")) {
5027 			if(first) {
5028 				s ~= toPrettyStringIndent(insertComments, indentationLevel, indentWith);
5029 				first = false;
5030 			} else {
5031 				s ~= "\n";
5032 				if(insertComments)
5033 					s ~= "<!--";
5034 				foreach(i; 0 .. indentationLevel)
5035 					s ~= "\t";
5036 				if(insertComments)
5037 					s ~= "-->";
5038 			}
5039 			s ~= line.stripRight;
5040 		}
5041 		return s;
5042 	}
5043 
5044 	///.
5045 	override Element appendChild(Element e) {
5046 		assert(0, "Cannot append to a text node");
5047 	}
5048 
5049 	///.
5050 	string contents;
5051 	// alias contents content; // I just mistype this a lot,
5052 }
5053 
5054 /**
5055 	There are subclasses of Element offering improved helper
5056 	functions for the element in HTML.
5057 */
5058 
5059 /++
5060 	Represents a HTML link. This provides some convenience methods for manipulating query strings, but otherwise is sthe same Element interface.
5061 
5062 	Please note this object may not be used for all `<a>` tags.
5063 +/
5064 /// Group: implementations
5065 class Link : Element {
5066 
5067 	/++
5068 		Constructs `<a href="that href">that text</a>`.
5069 	+/
5070 	this(string href, string text) {
5071 		super("a");
5072 		setAttribute("href", href);
5073 		innerText = text;
5074 	}
5075 
5076 	/// ditto
5077 	this(Document _parentDocument) {
5078 		super(_parentDocument);
5079 		this.tagName = "a";
5080 	}
5081 
5082 /+
5083 	/// Returns everything in the href EXCEPT the query string
5084 	@property string targetSansQuery() {
5085 
5086 	}
5087 
5088 	///.
5089 	@property string domainName() {
5090 
5091 	}
5092 
5093 	///.
5094 	@property string path
5095 +/
5096 	/// This gets a variable from the URL's query string.
5097 	string getValue(string name) {
5098 		auto vars = variablesHash();
5099 		if(name in vars)
5100 			return vars[name];
5101 		return null;
5102 	}
5103 
5104 	private string[string] variablesHash() {
5105 		string href = getAttribute("href");
5106 		if(href is null)
5107 			return null;
5108 
5109 		auto ques = href.indexOf("?");
5110 		string str = "";
5111 		if(ques != -1) {
5112 			str = href[ques+1..$];
5113 
5114 			auto fragment = str.indexOf("#");
5115 			if(fragment != -1)
5116 				str = str[0..fragment];
5117 		}
5118 
5119 		string[] variables = str.split("&");
5120 
5121 		string[string] hash;
5122 
5123 		foreach(var; variables) {
5124 			auto index = var.indexOf("=");
5125 			if(index == -1)
5126 				hash[var] = "";
5127 			else {
5128 				hash[decodeComponent(var[0..index])] = decodeComponent(var[index + 1 .. $]);
5129 			}
5130 		}
5131 
5132 		return hash;
5133 	}
5134 
5135 	/// Replaces all the stuff after a ? in the link at once with the given assoc array values.
5136 	/*private*/ void updateQueryString(string[string] vars) {
5137 		string href = getAttribute("href");
5138 
5139 		auto question = href.indexOf("?");
5140 		if(question != -1)
5141 			href = href[0..question];
5142 
5143 		string frag = "";
5144 		auto fragment = href.indexOf("#");
5145 		if(fragment != -1) {
5146 			frag = href[fragment..$];
5147 			href = href[0..fragment];
5148 		}
5149 
5150 		string query = "?";
5151 		bool first = true;
5152 		foreach(name, value; vars) {
5153 			if(!first)
5154 				query ~= "&";
5155 			else
5156 				first = false;
5157 
5158 			query ~= encodeComponent(name);
5159 			if(value.length)
5160 				query ~= "=" ~ encodeComponent(value);
5161 		}
5162 
5163 		if(query != "?")
5164 			href ~= query;
5165 
5166 		href ~= frag;
5167 
5168 		setAttribute("href", href);
5169 	}
5170 
5171 	/// Sets or adds the variable with the given name to the given value
5172 	/// It automatically URI encodes the values and takes care of the ? and &.
5173 	override void setValue(string name, string variable) {
5174 		auto vars = variablesHash();
5175 		vars[name] = variable;
5176 
5177 		updateQueryString(vars);
5178 	}
5179 
5180 	/// Removes the given variable from the query string
5181 	void removeValue(string name) {
5182 		auto vars = variablesHash();
5183 		vars.remove(name);
5184 
5185 		updateQueryString(vars);
5186 	}
5187 
5188 	/*
5189 	///.
5190 	override string toString() {
5191 
5192 	}
5193 
5194 	///.
5195 	override string getAttribute(string name) {
5196 		if(name == "href") {
5197 
5198 		} else
5199 			return super.getAttribute(name);
5200 	}
5201 	*/
5202 }
5203 
5204 /++
5205 	Represents a HTML form. This slightly specializes Element to add a few more convenience methods for adding and extracting form data.
5206 
5207 	Please note this object may not be used for all `<form>` tags.
5208 +/
5209 /// Group: implementations
5210 class Form : Element {
5211 
5212 	///.
5213 	this(Document _parentDocument) {
5214 		super(_parentDocument);
5215 		tagName = "form";
5216 	}
5217 
5218 	/// Overrides of the base class implementations that more confirm to *my* conventions when writing form html.
5219 	override Element addField(string label, string name, string type = "text", FormFieldOptions fieldOptions = FormFieldOptions.none) {
5220 		auto t = this.querySelector("fieldset div");
5221 		if(t is null)
5222 			return super.addField(label, name, type, fieldOptions);
5223 		else
5224 			return t.addField(label, name, type, fieldOptions);
5225 	}
5226 
5227 	/// ditto
5228 	override Element addField(string label, string name, FormFieldOptions fieldOptions) {
5229 		auto type = "text";
5230 		auto t = this.querySelector("fieldset div");
5231 		if(t is null)
5232 			return super.addField(label, name, type, fieldOptions);
5233 		else
5234 			return t.addField(label, name, type, fieldOptions);
5235 	}
5236 
5237 	/// ditto
5238 	override Element addField(string label, string name, string[string] options, FormFieldOptions fieldOptions = FormFieldOptions.none) {
5239 		auto t = this.querySelector("fieldset div");
5240 		if(t is null)
5241 			return super.addField(label, name, options, fieldOptions);
5242 		else
5243 			return t.addField(label, name, options, fieldOptions);
5244 	}
5245 
5246 	/// ditto
5247 	override void setValue(string field, string value) {
5248 		setValue(field, value, true);
5249 	}
5250 
5251 	// FIXME: doesn't handle arrays; multiple fields can have the same name
5252 
5253 	/// Set's the form field's value. For input boxes, this sets the value attribute. For
5254 	/// textareas, it sets the innerText. For radio boxes and select boxes, it removes
5255 	/// the checked/selected attribute from all, and adds it to the one matching the value.
5256 	/// For checkboxes, if the value is non-null and not empty, it checks the box.
5257 
5258 	/// If you set a value that doesn't exist, it throws an exception if makeNew is false.
5259 	/// Otherwise, it makes a new input with type=hidden to keep the value.
5260 	void setValue(string field, string value, bool makeNew) {
5261 		auto eles = getField(field);
5262 		if(eles.length == 0) {
5263 			if(makeNew) {
5264 				addInput(field, value);
5265 				return;
5266 			} else
5267 				throw new Exception("form field does not exist");
5268 		}
5269 
5270 		if(eles.length == 1) {
5271 			auto e = eles[0];
5272 			switch(e.tagName) {
5273 				default: assert(0);
5274 				case "textarea":
5275 					e.innerText = value;
5276 				break;
5277 				case "input":
5278 					string type = e.getAttribute("type");
5279 					if(type is null) {
5280 						e.value = value;
5281 						return;
5282 					}
5283 					switch(type) {
5284 						case "checkbox":
5285 						case "radio":
5286 							if(value.length && value != "false")
5287 								e.setAttribute("checked", "checked");
5288 							else
5289 								e.removeAttribute("checked");
5290 						break;
5291 						default:
5292 							e.value = value;
5293 							return;
5294 					}
5295 				break;
5296 				case "select":
5297 					bool found = false;
5298 					foreach(child; e.tree) {
5299 						if(child.tagName != "option")
5300 							continue;
5301 						string val = child.getAttribute("value");
5302 						if(val is null)
5303 							val = child.innerText;
5304 						if(val == value) {
5305 							child.setAttribute("selected", "selected");
5306 							found = true;
5307 						} else
5308 							child.removeAttribute("selected");
5309 					}
5310 
5311 					if(!found) {
5312 						e.addChild("option", value)
5313 						.setAttribute("selected", "selected");
5314 					}
5315 				break;
5316 			}
5317 		} else {
5318 			// assume radio boxes
5319 			foreach(e; eles) {
5320 				string val = e.getAttribute("value");
5321 				//if(val is null)
5322 				//	throw new Exception("don't know what to do with radio boxes with null value");
5323 				if(val == value)
5324 					e.setAttribute("checked", "checked");
5325 				else
5326 					e.removeAttribute("checked");
5327 			}
5328 		}
5329 	}
5330 
5331 	/// This takes an array of strings and adds hidden <input> elements for each one of them. Unlike setValue,
5332 	/// it makes no attempt to find and modify existing elements in the form to the new values.
5333 	void addValueArray(string key, string[] arrayOfValues) {
5334 		foreach(arr; arrayOfValues)
5335 			addChild("input", key, arr);
5336 	}
5337 
5338 	/// Gets the value of the field; what would be given if it submitted right now. (so
5339 	/// it handles select boxes and radio buttons too). For checkboxes, if a value isn't
5340 	/// given, but it is checked, it returns "checked", since null and "" are indistinguishable
5341 	string getValue(string field) {
5342 		auto eles = getField(field);
5343 		if(eles.length == 0)
5344 			return "";
5345 		if(eles.length == 1) {
5346 			auto e = eles[0];
5347 			switch(e.tagName) {
5348 				default: assert(0);
5349 				case "input":
5350 					if(e.type == "checkbox") {
5351 						if(e.checked)
5352 							return e.value.length ? e.value : "checked";
5353 						return "";
5354 					} else
5355 						return e.value;
5356 				case "textarea":
5357 					return e.innerText;
5358 				case "select":
5359 					foreach(child; e.tree) {
5360 						if(child.tagName != "option")
5361 							continue;
5362 						if(child.selected)
5363 							return child.value;
5364 					}
5365 				break;
5366 			}
5367 		} else {
5368 			// assuming radio
5369 			foreach(e; eles) {
5370 				if(e.checked)
5371 					return e.value;
5372 			}
5373 		}
5374 
5375 		return "";
5376 	}
5377 
5378 	// FIXME: doesn't handle multiple elements with the same name (except radio buttons)
5379 	/++
5380 		Returns the form's contents in application/x-www-form-urlencoded format.
5381 
5382 		Bugs:
5383 			Doesn't handle repeated elements of the same name nor files.
5384 	+/
5385 	string getPostableData() {
5386 		bool[string] namesDone;
5387 
5388 		string ret;
5389 		bool outputted = false;
5390 
5391 		foreach(e; getElementsBySelector("[name]")) {
5392 			if(e.name in namesDone)
5393 				continue;
5394 
5395 			if(outputted)
5396 				ret ~= "&";
5397 			else
5398 				outputted = true;
5399 
5400 			ret ~= std.uri.encodeComponent(e.name) ~ "=" ~ std.uri.encodeComponent(getValue(e.name));
5401 
5402 			namesDone[e.name] = true;
5403 		}
5404 
5405 		return ret;
5406 	}
5407 
5408 	/// Gets the actual elements with the given name
5409 	Element[] getField(string name) {
5410 		Element[] ret;
5411 		foreach(e; tree) {
5412 			if(e.name == name)
5413 				ret ~= e;
5414 		}
5415 		return ret;
5416 	}
5417 
5418 	/// Grabs the <label> with the given for tag, if there is one.
5419 	Element getLabel(string forId) {
5420 		foreach(e; tree)
5421 			if(e.tagName == "label" && e.getAttribute("for") == forId)
5422 				return e;
5423 		return null;
5424 	}
5425 
5426 	/// Adds a new INPUT field to the end of the form with the given attributes.
5427 	Element addInput(string name, string value, string type = "hidden") {
5428 		auto e = new Element(parentDocument, "input", null, true);
5429 		e.name = name;
5430 		e.value = value;
5431 		e.type = type;
5432 
5433 		appendChild(e);
5434 
5435 		return e;
5436 	}
5437 
5438 	/// Removes the given field from the form. It finds the element and knocks it right out.
5439 	void removeField(string name) {
5440 		foreach(e; getField(name))
5441 			e.parentNode.removeChild(e);
5442 	}
5443 
5444 	/+
5445 	/// Returns all form members.
5446 	@property Element[] elements() {
5447 
5448 	}
5449 
5450 	///.
5451 	string opDispatch(string name)(string v = null)
5452 		// filter things that should actually be attributes on the form
5453 		if( name != "method" && name != "action" && name != "enctype"
5454 		 && name != "style"  && name != "name" && name != "id" && name != "class")
5455 	{
5456 
5457 	}
5458 	+/
5459 /+
5460 	void submit() {
5461 		// take its elements and submit them through http
5462 	}
5463 +/
5464 }
5465 
5466 import std.conv;
5467 
5468 /++
5469 	Represents a HTML table. Has some convenience methods for working with tabular data.
5470 +/
5471 /// Group: implementations
5472 class Table : Element {
5473 
5474 	/// You can make this yourself but you'd generally get one of these object out of a html parse or [Element.make] call.
5475 	this(Document _parentDocument) {
5476 		super(_parentDocument);
5477 		tagName = "table";
5478 	}
5479 
5480 	/++
5481 		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`
5482 
5483 		The element is $(I not) appended to the table.
5484 	+/
5485 	Element th(T)(T t) {
5486 		Element e;
5487 		if(parentDocument !is null)
5488 			e = parentDocument.createElement("th");
5489 		else
5490 			e = Element.make("th");
5491 		static if(is(T == Html))
5492 			e.innerHTML = t;
5493 		else static if(is(T : Element))
5494 			e.appendChild(t);
5495 		else
5496 			e.innerText = to!string(t);
5497 		return e;
5498 	}
5499 
5500 	/// ditto
5501 	Element td(T)(T t) {
5502 		Element e;
5503 		if(parentDocument !is null)
5504 			e = parentDocument.createElement("td");
5505 		else
5506 			e = Element.make("td");
5507 		static if(is(T == Html))
5508 			e.innerHTML = t;
5509 		else static if(is(T : Element))
5510 			e.appendChild(t);
5511 		else
5512 			e.innerText = to!string(t);
5513 		return e;
5514 	}
5515 
5516 	/++
5517 		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.
5518 	+/
5519 	Element appendHeaderRow(T...)(T t) {
5520 		return appendRowInternal("th", "thead", t);
5521 	}
5522 
5523 	/// ditto
5524 	Element appendFooterRow(T...)(T t) {
5525 		return appendRowInternal("td", "tfoot", t);
5526 	}
5527 
5528 	/// ditto
5529 	Element appendRow(T...)(T t) {
5530 		return appendRowInternal("td", "tbody", t);
5531 	}
5532 
5533 	/++
5534 		Takes each argument as a class name and calls [Element.addClass] for each element in the column associated with that index.
5535 
5536 		Please note this does not use the html `<col>` element.
5537 	+/
5538 	void addColumnClasses(string[] classes...) {
5539 		auto grid = getGrid();
5540 		foreach(row; grid)
5541 		foreach(i, cl; classes) {
5542 			if(cl.length)
5543 			if(i < row.length)
5544 				row[i].addClass(cl);
5545 		}
5546 	}
5547 
5548 	private Element appendRowInternal(T...)(string innerType, string findType, T t) {
5549 		Element row = Element.make("tr");
5550 
5551 		foreach(e; t) {
5552 			static if(is(typeof(e) : Element)) {
5553 				if(e.tagName == "td" || e.tagName == "th")
5554 					row.appendChild(e);
5555 				else {
5556 					Element a = Element.make(innerType);
5557 
5558 					a.appendChild(e);
5559 
5560 					row.appendChild(a);
5561 				}
5562 			} else static if(is(typeof(e) == Html)) {
5563 				Element a = Element.make(innerType);
5564 				a.innerHTML = e.source;
5565 				row.appendChild(a);
5566 			} else static if(is(typeof(e) == Element[])) {
5567 				Element a = Element.make(innerType);
5568 				foreach(ele; e)
5569 					a.appendChild(ele);
5570 				row.appendChild(a);
5571 			} else static if(is(typeof(e) == string[])) {
5572 				foreach(ele; e) {
5573 					Element a = Element.make(innerType);
5574 					a.innerText = to!string(ele);
5575 					row.appendChild(a);
5576 				}
5577 			} else {
5578 				Element a = Element.make(innerType);
5579 				a.innerText = to!string(e);
5580 				row.appendChild(a);
5581 			}
5582 		}
5583 
5584 		foreach(e; children) {
5585 			if(e.tagName == findType) {
5586 				e.appendChild(row);
5587 				return row;
5588 			}
5589 		}
5590 
5591 		// the type was not found if we are here... let's add it so it is well-formed
5592 		auto lol = this.addChild(findType);
5593 		lol.appendChild(row);
5594 
5595 		return row;
5596 	}
5597 
5598 	/// Returns the `<caption>` element of the table, creating one if it isn't there.
5599 	Element captionElement() {
5600 		Element cap;
5601 		foreach(c; children) {
5602 			if(c.tagName == "caption") {
5603 				cap = c;
5604 				break;
5605 			}
5606 		}
5607 
5608 		if(cap is null) {
5609 			cap = Element.make("caption");
5610 			appendChild(cap);
5611 		}
5612 
5613 		return cap;
5614 	}
5615 
5616 	/// Returns or sets the text inside the `<caption>` element, creating that element if it isnt' there.
5617 	@property string caption() {
5618 		return captionElement().innerText;
5619 	}
5620 
5621 	/// ditto
5622 	@property void caption(string text) {
5623 		captionElement().innerText = text;
5624 	}
5625 
5626 	/// Gets the logical layout of the table as a rectangular grid of
5627 	/// cells. It considers rowspan and colspan. A cell with a large
5628 	/// span is represented in the grid by being referenced several times.
5629 	/// The tablePortition parameter can get just a <thead>, <tbody>, or
5630 	/// <tfoot> portion if you pass one.
5631 	///
5632 	/// Note: the rectangular grid might include null cells.
5633 	///
5634 	/// This is kinda expensive so you should call once when you want the grid,
5635 	/// then do lookups on the returned array.
5636 	TableCell[][] getGrid(Element tablePortition = null)
5637 		in {
5638 			if(tablePortition is null)
5639 				assert(tablePortition is null);
5640 			else {
5641 				assert(tablePortition !is null);
5642 				assert(tablePortition.parentNode is this);
5643 				assert(
5644 					tablePortition.tagName == "tbody"
5645 					||
5646 					tablePortition.tagName == "tfoot"
5647 					||
5648 					tablePortition.tagName == "thead"
5649 				);
5650 			}
5651 		}
5652 	do {
5653 		if(tablePortition is null)
5654 			tablePortition = this;
5655 
5656 		TableCell[][] ret;
5657 
5658 		// FIXME: will also return rows of sub tables!
5659 		auto rows = tablePortition.getElementsByTagName("tr");
5660 		ret.length = rows.length;
5661 
5662 		int maxLength = 0;
5663 
5664 		int insertCell(int row, int position, TableCell cell) {
5665 			if(row >= ret.length)
5666 				return position; // not supposed to happen - a rowspan is prolly too big.
5667 
5668 			if(position == -1) {
5669 				position++;
5670 				foreach(item; ret[row]) {
5671 					if(item is null)
5672 						break;
5673 					position++;
5674 				}
5675 			}
5676 
5677 			if(position < ret[row].length)
5678 				ret[row][position] = cell;
5679 			else
5680 				foreach(i; ret[row].length .. position + 1) {
5681 					if(i == position)
5682 						ret[row] ~= cell;
5683 					else
5684 						ret[row] ~= null;
5685 				}
5686 			return position;
5687 		}
5688 
5689 		foreach(i, rowElement; rows) {
5690 			auto row = cast(TableRow) rowElement;
5691 			assert(row !is null);
5692 			assert(i < ret.length);
5693 
5694 			int position = 0;
5695 			foreach(cellElement; rowElement.childNodes) {
5696 				auto cell = cast(TableCell) cellElement;
5697 				if(cell is null)
5698 					continue;
5699 
5700 				// FIXME: colspan == 0 or rowspan == 0
5701 				// is supposed to mean fill in the rest of
5702 				// the table, not skip it
5703 				foreach(int j; 0 .. cell.colspan) {
5704 					foreach(int k; 0 .. cell.rowspan)
5705 						// if the first row, always append.
5706 						insertCell(k + cast(int) i, k == 0 ? -1 : position, cell);
5707 					position++;
5708 				}
5709 			}
5710 
5711 			if(ret[i].length > maxLength)
5712 				maxLength = cast(int) ret[i].length;
5713 		}
5714 
5715 		// want to ensure it's rectangular
5716 		foreach(ref r; ret) {
5717 			foreach(i; r.length .. maxLength)
5718 				r ~= null;
5719 		}
5720 
5721 		return ret;
5722 	}
5723 }
5724 
5725 /// Represents a table row element - a <tr>
5726 /// Group: implementations
5727 class TableRow : Element {
5728 	///.
5729 	this(Document _parentDocument) {
5730 		super(_parentDocument);
5731 		tagName = "tr";
5732 	}
5733 
5734 	// FIXME: the standard says there should be a lot more in here,
5735 	// but meh, I never use it and it's a pain to implement.
5736 }
5737 
5738 /// Represents anything that can be a table cell - <td> or <th> html.
5739 /// Group: implementations
5740 class TableCell : Element {
5741 	///.
5742 	this(Document _parentDocument, string _tagName) {
5743 		super(_parentDocument, _tagName);
5744 	}
5745 
5746 	/// Gets and sets the row/colspan attributes as integers
5747 	@property int rowspan() const {
5748 		int ret = 1;
5749 		auto it = getAttribute("rowspan");
5750 		if(it.length)
5751 			ret = to!int(it);
5752 		return ret;
5753 	}
5754 
5755 	/// ditto
5756 	@property int colspan() const {
5757 		int ret = 1;
5758 		auto it = getAttribute("colspan");
5759 		if(it.length)
5760 			ret = to!int(it);
5761 		return ret;
5762 	}
5763 
5764 	/// ditto
5765 	@property int rowspan(int i) {
5766 		setAttribute("rowspan", to!string(i));
5767 		return i;
5768 	}
5769 
5770 	/// ditto
5771 	@property int colspan(int i) {
5772 		setAttribute("colspan", to!string(i));
5773 		return i;
5774 	}
5775 
5776 }
5777 
5778 
5779 /// This is thrown on parse errors.
5780 /// Group: implementations
5781 class MarkupException : Exception {
5782 
5783 	///.
5784 	this(string message, string file = __FILE__, size_t line = __LINE__) {
5785 		super(message, file, line);
5786 	}
5787 }
5788 
5789 /// This is used when you are using one of the require variants of navigation, and no matching element can be found in the tree.
5790 /// Group: implementations
5791 class ElementNotFoundException : Exception {
5792 
5793 	/// type == kind of element you were looking for and search == a selector describing the search.
5794 	this(string type, string search, Element searchContext, string file = __FILE__, size_t line = __LINE__) {
5795 		this.searchContext = searchContext;
5796 		super("Element of type '"~type~"' matching {"~search~"} not found.", file, line);
5797 	}
5798 
5799 	Element searchContext;
5800 }
5801 
5802 /// The html struct is used to differentiate between regular text nodes and html in certain functions
5803 ///
5804 /// Easiest way to construct it is like this: `auto html = Html("<p>hello</p>");`
5805 /// Group: core_functionality
5806 struct Html {
5807 	/// This string holds the actual html. Use it to retrieve the contents.
5808 	string source;
5809 }
5810 
5811 // for the observers
5812 enum DomMutationOperations {
5813 	setAttribute,
5814 	removeAttribute,
5815 	appendChild, // tagname, attributes[], innerHTML
5816 	insertBefore,
5817 	truncateChildren,
5818 	removeChild,
5819 	appendHtml,
5820 	replaceHtml,
5821 	appendText,
5822 	replaceText,
5823 	replaceTextOnly
5824 }
5825 
5826 // and for observers too
5827 struct DomMutationEvent {
5828 	DomMutationOperations operation;
5829 	Element target;
5830 	Element related; // what this means differs with the operation
5831 	Element related2;
5832 	string relatedString;
5833 	string relatedString2;
5834 }
5835 
5836 
5837 private immutable static string[] htmlSelfClosedElements = [
5838 	// html 4
5839 	"area","base","br","col","hr","img","input","link","meta","param",
5840 
5841 	// html 5
5842 	"embed","source","track","wbr"
5843 ];
5844 
5845 private immutable static string[] htmlInlineElements = [
5846 	"span", "strong", "em", "b", "i", "a"
5847 ];
5848 
5849 
5850 static import std.conv;
5851 
5852 /// helper function for decoding html entities
5853 int intFromHex(string hex) {
5854 	int place = 1;
5855 	int value = 0;
5856 	for(sizediff_t a = hex.length - 1; a >= 0; a--) {
5857 		int v;
5858 		char q = hex[a];
5859 		if( q >= '0' && q <= '9')
5860 			v = q - '0';
5861 		else if (q >= 'a' && q <= 'f')
5862 			v = q - 'a' + 10;
5863 		else if (q >= 'A' && q <= 'F')
5864 			v = q - 'A' + 10;
5865 		else throw new Exception("Illegal hex character: " ~ q);
5866 
5867 		value += v * place;
5868 
5869 		place *= 16;
5870 	}
5871 
5872 	return value;
5873 }
5874 
5875 
5876 // CSS selector handling
5877 
5878 // EXTENSIONS
5879 // dd - dt means get the dt directly before that dd (opposite of +)                  NOT IMPLEMENTED
5880 // dd -- dt means rewind siblings until you hit a dt, go as far as you need to       NOT IMPLEMENTED
5881 // 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")
5882 // dt << dl  means go as far up as needed to find a dl (you have an element and want its containers)      NOT IMPLEMENTED
5883 // :first  means to stop at the first hit, don't do more (so p + p == p ~ p:first
5884 
5885 
5886 
5887 // CSS4 draft currently says you can change the subject (the element actually returned) by putting a ! at the end of it.
5888 // That might be useful to implement, though I do have parent selectors too.
5889 
5890 		///.
5891 		static immutable string[] selectorTokens = [
5892 			// It is important that the 2 character possibilities go first here for accurate lexing
5893 		    "~=", "*=", "|=", "^=", "$=", "!=",
5894 		    "::", ">>",
5895 		    "<<", // my any-parent extension (reciprocal of whitespace)
5896 		    // " - ", // previous-sibling extension (whitespace required to disambiguate tag-names)
5897 		    ".", ">", "+", "*", ":", "[", "]", "=", "\"", "#", ",", " ", "~", "<", "(", ")"
5898 		]; // other is white space or a name.
5899 
5900 		///.
5901 		sizediff_t idToken(string str, sizediff_t position) {
5902 			sizediff_t tid = -1;
5903 			char c = str[position];
5904 			foreach(a, token; selectorTokens)
5905 
5906 				if(c == token[0]) {
5907 					if(token.length > 1) {
5908 						if(position + 1 >= str.length   ||   str[position+1] != token[1])
5909 							continue; // not this token
5910 					}
5911 					tid = a;
5912 					break;
5913 				}
5914 			return tid;
5915 		}
5916 
5917 	/// Parts of the CSS selector implementation
5918 	// look, ma, no phobos!
5919 	// new lexer by ketmar
5920 	string[] lexSelector (string selstr) {
5921 
5922 		static sizediff_t idToken (string str, size_t stpos) {
5923 			char c = str[stpos];
5924 			foreach (sizediff_t tidx, immutable token; selectorTokens) {
5925 				if (c == token[0]) {
5926 					if (token.length > 1) {
5927 						assert(token.length == 2, token); // we don't have 3-char tokens yet
5928 						if (str.length-stpos < 2 || str[stpos+1] != token[1]) continue;
5929 					}
5930 					return tidx;
5931 				}
5932 			}
5933 			return -1;
5934 		}
5935 
5936 		// skip spaces and comments
5937 		static string removeLeadingBlanks (string str) {
5938 			size_t curpos = 0;
5939 			while (curpos < str.length) {
5940 				immutable char ch = str[curpos];
5941 				// this can overflow on 4GB strings on 32-bit; 'cmon, don't be silly, nobody cares!
5942 				if (ch == '/' && str.length-curpos > 1 && str[curpos+1] == '*') {
5943 					// comment
5944 					curpos += 2;
5945 					while (curpos < str.length) {
5946 						if (str[curpos] == '*' && str.length-curpos > 1 && str[curpos+1] == '/') {
5947 							curpos += 2;
5948 							break;
5949 						}
5950 						++curpos;
5951 					}
5952 				} else if (ch < 32) { // The < instead of <= is INTENTIONAL. See note from adr below.
5953 					++curpos;
5954 
5955 					// FROM ADR: This does NOT catch ' '! Spaces have semantic meaning in CSS! While
5956 					// "foo bar" is clear, and can only have one meaning, consider ".foo .bar".
5957 					// That is not the same as ".foo.bar". If the space is stripped, important
5958 					// information is lost, despite the tokens being separatable anyway.
5959 					//
5960 					// The parser really needs to be aware of the presence of a space.
5961 				} else {
5962 					break;
5963 				}
5964 			}
5965 			return str[curpos..$];
5966 		}
5967 
5968 		static bool isBlankAt() (string str, size_t pos) {
5969 			// we should consider unicode spaces too, but... unicode sux anyway.
5970 			return
5971 				(pos < str.length && // in string
5972 				 (str[pos] <= 32 || // space
5973 					(str.length-pos > 1 && str[pos] == '/' && str[pos+1] == '*'))); // comment
5974 		}
5975 
5976 		string[] tokens;
5977 		// lexx it!
5978 		while ((selstr = removeLeadingBlanks(selstr)).length > 0) {
5979 			if(selstr[0] == '\"' || selstr[0] == '\'') {
5980 				auto end = selstr[0];
5981 				auto pos = 1;
5982 				bool escaping;
5983 				while(pos < selstr.length && !escaping && selstr[pos] != end) {
5984 					if(escaping)
5985 						escaping = false;
5986 					else if(selstr[pos] == '\\')
5987 						escaping = true;
5988 					pos++;
5989 				}
5990 
5991 				// FIXME: do better unescaping
5992 				tokens ~= selstr[1 .. pos].replace(`\"`, `"`).replace(`\'`, `'`).replace(`\\`, `\`);
5993 				if(pos+1 >= selstr.length)
5994 					assert(0, selstr);
5995 				selstr = selstr[pos + 1.. $];
5996 				continue;
5997 			}
5998 
5999 
6000 			// no tokens starts with escape
6001 			immutable tid = idToken(selstr, 0);
6002 			if (tid >= 0) {
6003 				// special token
6004 				tokens ~= selectorTokens[tid]; // it's funnier this way
6005 				selstr = selstr[selectorTokens[tid].length..$];
6006 				continue;
6007 			}
6008 			// from start to space or special token
6009 			size_t escapePos = size_t.max;
6010 			size_t curpos = 0; // i can has chizburger^w escape at the start
6011 			while (curpos < selstr.length) {
6012 				if (selstr[curpos] == '\\') {
6013 					// this is escape, just skip it and next char
6014 					if (escapePos == size_t.max) escapePos = curpos;
6015 					curpos = (selstr.length-curpos >= 2 ? curpos+2 : selstr.length);
6016 				} else {
6017 					if (isBlankAt(selstr, curpos) || idToken(selstr, curpos) >= 0) break;
6018 					++curpos;
6019 				}
6020 			}
6021 			// identifier
6022 			if (escapePos != size_t.max) {
6023 				// i hate it when it happens
6024 				string id = selstr[0..escapePos];
6025 				while (escapePos < curpos) {
6026 					if (curpos-escapePos < 2) break;
6027 					id ~= selstr[escapePos+1]; // escaped char
6028 					escapePos += 2;
6029 					immutable stp = escapePos;
6030 					while (escapePos < curpos && selstr[escapePos] != '\\') ++escapePos;
6031 					if (escapePos > stp) id ~= selstr[stp..escapePos];
6032 				}
6033 				if (id.length > 0) tokens ~= id;
6034 			} else {
6035 				tokens ~= selstr[0..curpos];
6036 			}
6037 			selstr = selstr[curpos..$];
6038 		}
6039 		return tokens;
6040 	}
6041 	version(unittest_domd_lexer) unittest {
6042 		assert(lexSelector(r" test\=me  /*d*/") == [r"test=me"]);
6043 		assert(lexSelector(r"div/**/. id") == ["div", ".", "id"]);
6044 		assert(lexSelector(r" < <") == ["<", "<"]);
6045 		assert(lexSelector(r" <<") == ["<<"]);
6046 		assert(lexSelector(r" <</") == ["<<", "/"]);
6047 		assert(lexSelector(r" <</*") == ["<<"]);
6048 		assert(lexSelector(r" <\</*") == ["<", "<"]);
6049 		assert(lexSelector(r"heh\") == ["heh"]);
6050 		assert(lexSelector(r"alice \") == ["alice"]);
6051 		assert(lexSelector(r"alice,is#best") == ["alice", ",", "is", "#", "best"]);
6052 	}
6053 
6054 	/// ditto
6055 	struct SelectorPart {
6056 		string tagNameFilter; ///.
6057 		string[] attributesPresent; /// [attr]
6058 		string[2][] attributesEqual; /// [attr=value]
6059 		string[2][] attributesStartsWith; /// [attr^=value]
6060 		string[2][] attributesEndsWith; /// [attr$=value]
6061 		// split it on space, then match to these
6062 		string[2][] attributesIncludesSeparatedBySpaces; /// [attr~=value]
6063 		// split it on dash, then match to these
6064 		string[2][] attributesIncludesSeparatedByDashes; /// [attr|=value]
6065 		string[2][] attributesInclude; /// [attr*=value]
6066 		string[2][] attributesNotEqual; /// [attr!=value] -- extension by me
6067 
6068 		string[] hasSelectors; /// :has(this)
6069 		string[] notSelectors; /// :not(this)
6070 
6071 		string[] isSelectors; /// :is(this)
6072 		string[] whereSelectors; /// :where(this)
6073 
6074 		ParsedNth[] nthOfType; /// .
6075 		ParsedNth[] nthLastOfType; /// .
6076 		ParsedNth[] nthChild; /// .
6077 
6078 		bool firstChild; ///.
6079 		bool lastChild; ///.
6080 
6081 		bool firstOfType; /// .
6082 		bool lastOfType; /// .
6083 
6084 		bool emptyElement; ///.
6085 		bool whitespaceOnly; ///
6086 		bool oddChild; ///.
6087 		bool evenChild; ///.
6088 
6089 		bool scopeElement; /// the css :scope thing; matches just the `this` element. NOT IMPLEMENTED
6090 
6091 		bool rootElement; ///.
6092 
6093 		int separation = -1; /// -1 == only itself; the null selector, 0 == tree, 1 == childNodes, 2 == childAfter, 3 == youngerSibling, 4 == parentOf
6094 
6095 		bool isCleanSlateExceptSeparation() {
6096 			auto cp = this;
6097 			cp.separation = -1;
6098 			return cp is SelectorPart.init;
6099 		}
6100 
6101 		///.
6102 		string toString() {
6103 			string ret;
6104 			switch(separation) {
6105 				default: assert(0);
6106 				case -1: break;
6107 				case 0: ret ~= " "; break;
6108 				case 1: ret ~= " > "; break;
6109 				case 2: ret ~= " + "; break;
6110 				case 3: ret ~= " ~ "; break;
6111 				case 4: ret ~= " < "; break;
6112 			}
6113 			ret ~= tagNameFilter;
6114 			foreach(a; attributesPresent) ret ~= "[" ~ a ~ "]";
6115 			foreach(a; attributesEqual) ret ~= "[" ~ a[0] ~ "=\"" ~ a[1] ~ "\"]";
6116 			foreach(a; attributesEndsWith) ret ~= "[" ~ a[0] ~ "$=\"" ~ a[1] ~ "\"]";
6117 			foreach(a; attributesStartsWith) ret ~= "[" ~ a[0] ~ "^=\"" ~ a[1] ~ "\"]";
6118 			foreach(a; attributesNotEqual) ret ~= "[" ~ a[0] ~ "!=\"" ~ a[1] ~ "\"]";
6119 			foreach(a; attributesInclude) ret ~= "[" ~ a[0] ~ "*=\"" ~ a[1] ~ "\"]";
6120 			foreach(a; attributesIncludesSeparatedByDashes) ret ~= "[" ~ a[0] ~ "|=\"" ~ a[1] ~ "\"]";
6121 			foreach(a; attributesIncludesSeparatedBySpaces) ret ~= "[" ~ a[0] ~ "~=\"" ~ a[1] ~ "\"]";
6122 
6123 			foreach(a; notSelectors) ret ~= ":not(" ~ a ~ ")";
6124 			foreach(a; hasSelectors) ret ~= ":has(" ~ a ~ ")";
6125 
6126 			foreach(a; isSelectors) ret ~= ":is(" ~ a ~ ")";
6127 			foreach(a; whereSelectors) ret ~= ":where(" ~ a ~ ")";
6128 
6129 			foreach(a; nthChild) ret ~= ":nth-child(" ~ a.toString ~ ")";
6130 			foreach(a; nthOfType) ret ~= ":nth-of-type(" ~ a.toString ~ ")";
6131 			foreach(a; nthLastOfType) ret ~= ":nth-last-of-type(" ~ a.toString ~ ")";
6132 
6133 			if(firstChild) ret ~= ":first-child";
6134 			if(lastChild) ret ~= ":last-child";
6135 			if(firstOfType) ret ~= ":first-of-type";
6136 			if(lastOfType) ret ~= ":last-of-type";
6137 			if(emptyElement) ret ~= ":empty";
6138 			if(whitespaceOnly) ret ~= ":whitespace-only";
6139 			if(oddChild) ret ~= ":odd-child";
6140 			if(evenChild) ret ~= ":even-child";
6141 			if(rootElement) ret ~= ":root";
6142 			if(scopeElement) ret ~= ":scope";
6143 
6144 			return ret;
6145 		}
6146 
6147 		// USEFUL
6148 		/// Returns true if the given element matches this part
6149 		bool matchElement(Element e) {
6150 			// FIXME: this can be called a lot of times, and really add up in times according to the profiler.
6151 			// Each individual call is reasonably fast already, but it adds up.
6152 			if(e is null) return false;
6153 			if(e.nodeType != 1) return false;
6154 
6155 			if(tagNameFilter != "" && tagNameFilter != "*")
6156 				if(e.tagName != tagNameFilter)
6157 					return false;
6158 			if(firstChild) {
6159 				if(e.parentNode is null)
6160 					return false;
6161 				if(e.parentNode.childElements[0] !is e)
6162 					return false;
6163 			}
6164 			if(lastChild) {
6165 				if(e.parentNode is null)
6166 					return false;
6167 				auto ce = e.parentNode.childElements;
6168 				if(ce[$-1] !is e)
6169 					return false;
6170 			}
6171 			if(firstOfType) {
6172 				if(e.parentNode is null)
6173 					return false;
6174 				auto ce = e.parentNode.childElements;
6175 				foreach(c; ce) {
6176 					if(c.tagName == e.tagName) {
6177 						if(c is e)
6178 							return true;
6179 						else
6180 							return false;
6181 					}
6182 				}
6183 			}
6184 			if(lastOfType) {
6185 				if(e.parentNode is null)
6186 					return false;
6187 				auto ce = e.parentNode.childElements;
6188 				foreach_reverse(c; ce) {
6189 					if(c.tagName == e.tagName) {
6190 						if(c is e)
6191 							return true;
6192 						else
6193 							return false;
6194 					}
6195 				}
6196 			}
6197 			/+
6198 			if(scopeElement) {
6199 				if(e !is this_)
6200 					return false;
6201 			}
6202 			+/
6203 			if(emptyElement) {
6204 				if(e.isEmpty())
6205 					return false;
6206 			}
6207 			if(whitespaceOnly) {
6208 				if(e.innerText.strip.length)
6209 					return false;
6210 			}
6211 			if(rootElement) {
6212 				if(e.parentNode !is null)
6213 					return false;
6214 			}
6215 			if(oddChild || evenChild) {
6216 				if(e.parentNode is null)
6217 					return false;
6218 				foreach(i, child; e.parentNode.childElements) {
6219 					if(child is e) {
6220 						if(oddChild && !(i&1))
6221 							return false;
6222 						if(evenChild && (i&1))
6223 							return false;
6224 						break;
6225 					}
6226 				}
6227 			}
6228 
6229 			bool matchWithSeparator(string attr, string value, string separator) {
6230 				foreach(s; attr.split(separator))
6231 					if(s == value)
6232 						return true;
6233 				return false;
6234 			}
6235 
6236 			foreach(a; attributesPresent)
6237 				if(a !in e.attributes)
6238 					return false;
6239 			foreach(a; attributesEqual)
6240 				if(a[0] !in e.attributes || e.attributes[a[0]] != a[1])
6241 					return false;
6242 			foreach(a; attributesNotEqual)
6243 				// FIXME: maybe it should say null counts... this just bit me.
6244 				// I did [attr][attr!=value] to work around.
6245 				//
6246 				// if it's null, it's not equal, right?
6247 				//if(a[0] !in e.attributes || e.attributes[a[0]] == a[1])
6248 				if(e.getAttribute(a[0]) == a[1])
6249 					return false;
6250 			foreach(a; attributesInclude)
6251 				if(a[0] !in e.attributes || (e.attributes[a[0]].indexOf(a[1]) == -1))
6252 					return false;
6253 			foreach(a; attributesStartsWith)
6254 				if(a[0] !in e.attributes || !e.attributes[a[0]].startsWith(a[1]))
6255 					return false;
6256 			foreach(a; attributesEndsWith)
6257 				if(a[0] !in e.attributes || !e.attributes[a[0]].endsWith(a[1]))
6258 					return false;
6259 			foreach(a; attributesIncludesSeparatedBySpaces)
6260 				if(a[0] !in e.attributes || !matchWithSeparator(e.attributes[a[0]], a[1], " "))
6261 					return false;
6262 			foreach(a; attributesIncludesSeparatedByDashes)
6263 				if(a[0] !in e.attributes || !matchWithSeparator(e.attributes[a[0]], a[1], "-"))
6264 					return false;
6265 			foreach(a; hasSelectors) {
6266 				if(e.querySelector(a) is null)
6267 					return false;
6268 			}
6269 			foreach(a; notSelectors) {
6270 				auto sel = Selector(a);
6271 				if(sel.matchesElement(e))
6272 					return false;
6273 			}
6274 			foreach(a; isSelectors) {
6275 				auto sel = Selector(a);
6276 				if(!sel.matchesElement(e))
6277 					return false;
6278 			}
6279 			foreach(a; whereSelectors) {
6280 				auto sel = Selector(a);
6281 				if(!sel.matchesElement(e))
6282 					return false;
6283 			}
6284 
6285 			foreach(a; nthChild) {
6286 				if(e.parentNode is null)
6287 					return false;
6288 
6289 				auto among = e.parentNode.childElements;
6290 
6291 				if(!a.solvesFor(among, e))
6292 					return false;
6293 			}
6294 			foreach(a; nthOfType) {
6295 				if(e.parentNode is null)
6296 					return false;
6297 
6298 				auto among = e.parentNode.childElements(e.tagName);
6299 
6300 				if(!a.solvesFor(among, e))
6301 					return false;
6302 			}
6303 			foreach(a; nthLastOfType) {
6304 				if(e.parentNode is null)
6305 					return false;
6306 
6307 				auto among = retro(e.parentNode.childElements(e.tagName));
6308 
6309 				if(!a.solvesFor(among, e))
6310 					return false;
6311 			}
6312 
6313 			return true;
6314 		}
6315 	}
6316 
6317 	struct ParsedNth {
6318 		int multiplier;
6319 		int adder;
6320 
6321 		string of;
6322 
6323 		this(string text) {
6324 			auto original = text;
6325 			consumeWhitespace(text);
6326 			if(text.startsWith("odd")) {
6327 				multiplier = 2;
6328 				adder = 1;
6329 
6330 				text = text[3 .. $];
6331 			} else if(text.startsWith("even")) {
6332 				multiplier = 2;
6333 				adder = 1;
6334 
6335 				text = text[4 .. $];
6336 			} else {
6337 				int n = (text.length && text[0] == 'n') ? 1 : parseNumber(text);
6338 				consumeWhitespace(text);
6339 				if(text.length && text[0] == 'n') {
6340 					multiplier = n;
6341 					text = text[1 .. $];
6342 					consumeWhitespace(text);
6343 					if(text.length) {
6344 						if(text[0] == '+') {
6345 							text = text[1 .. $];
6346 							adder = parseNumber(text);
6347 						} else if(text[0] == '-') {
6348 							text = text[1 .. $];
6349 							adder = -parseNumber(text);
6350 						} else if(text[0] == 'o') {
6351 							// continue, this is handled below
6352 						} else
6353 							throw new Exception("invalid css string at " ~ text ~ " in " ~ original);
6354 					}
6355 				} else {
6356 					adder = n;
6357 				}
6358 			}
6359 
6360 			consumeWhitespace(text);
6361 			if(text.startsWith("of")) {
6362 				text = text[2 .. $];
6363 				consumeWhitespace(text);
6364 				of = text[0 .. $];
6365 			}
6366 		}
6367 
6368 		string toString() {
6369 			return format("%dn%s%d%s%s", multiplier, adder >= 0 ? "+" : "", adder, of.length ? " of " : "", of);
6370 		}
6371 
6372 		bool solvesFor(R)(R elements, Element e) {
6373 			int idx = 1;
6374 			bool found = false;
6375 			foreach(ele; elements) {
6376 				if(of.length) {
6377 					auto sel = Selector(of);
6378 					if(!sel.matchesElement(ele))
6379 						continue;
6380 				}
6381 				if(ele is e) {
6382 					found = true;
6383 					break;
6384 				}
6385 				idx++;
6386 			}
6387 			if(!found) return false;
6388 
6389 			// multiplier* n + adder = idx
6390 			// if there is a solution for integral n, it matches
6391 
6392 			idx -= adder;
6393 			if(multiplier) {
6394 				if(idx % multiplier == 0)
6395 					return true;
6396 			} else {
6397 				return idx == 0;
6398 			}
6399 			return false;
6400 		}
6401 
6402 		private void consumeWhitespace(ref string text) {
6403 			while(text.length && text[0] == ' ')
6404 				text = text[1 .. $];
6405 		}
6406 
6407 		private int parseNumber(ref string text) {
6408 			consumeWhitespace(text);
6409 			if(text.length == 0) return 0;
6410 			bool negative = text[0] == '-';
6411 			if(text[0] == '+')
6412 				text = text[1 .. $];
6413 			if(negative) text = text[1 .. $];
6414 			int i = 0;
6415 			while(i < text.length && (text[i] >= '0' && text[i] <= '9'))
6416 				i++;
6417 			if(i == 0)
6418 				return 0;
6419 			int cool = to!int(text[0 .. i]);
6420 			text = text[i .. $];
6421 			return negative ? -cool : cool;
6422 		}
6423 	}
6424 
6425 	// USEFUL
6426 	/// ditto
6427 	Element[] getElementsBySelectorParts(Element start, SelectorPart[] parts) {
6428 		Element[] ret;
6429 		if(!parts.length) {
6430 			return [start]; // the null selector only matches the start point; it
6431 				// is what terminates the recursion
6432 		}
6433 
6434 		auto part = parts[0];
6435 		//writeln("checking ", part, " against ", start, " with ", part.separation);
6436 		switch(part.separation) {
6437 			default: assert(0);
6438 			case -1:
6439 			case 0: // tree
6440 				foreach(e; start.tree) {
6441 					if(part.separation == 0 && start is e)
6442 						continue; // space doesn't match itself!
6443 					if(part.matchElement(e)) {
6444 						ret ~= getElementsBySelectorParts(e, parts[1..$]);
6445 					}
6446 				}
6447 			break;
6448 			case 1: // children
6449 				foreach(e; start.childNodes) {
6450 					if(part.matchElement(e)) {
6451 						ret ~= getElementsBySelectorParts(e, parts[1..$]);
6452 					}
6453 				}
6454 			break;
6455 			case 2: // next-sibling
6456 				auto e = start.nextSibling("*");
6457 				if(part.matchElement(e))
6458 					ret ~= getElementsBySelectorParts(e, parts[1..$]);
6459 			break;
6460 			case 3: // younger sibling
6461 				auto tmp = start.parentNode;
6462 				if(tmp !is null) {
6463 					sizediff_t pos = -1;
6464 					auto children = tmp.childElements;
6465 					foreach(i, child; children) {
6466 						if(child is start) {
6467 							pos = i;
6468 							break;
6469 						}
6470 					}
6471 					assert(pos != -1);
6472 					foreach(e; children[pos+1..$]) {
6473 						if(part.matchElement(e))
6474 							ret ~= getElementsBySelectorParts(e, parts[1..$]);
6475 					}
6476 				}
6477 			break;
6478 			case 4: // immediate parent node, an extension of mine to walk back up the tree
6479 				auto e = start.parentNode;
6480 				if(part.matchElement(e)) {
6481 					ret ~= getElementsBySelectorParts(e, parts[1..$]);
6482 				}
6483 				/*
6484 					Example of usefulness:
6485 
6486 					Consider you have an HTML table. If you want to get all rows that have a th, you can do:
6487 
6488 					table th < tr
6489 
6490 					Get all th descendants of the table, then walk back up the tree to fetch their parent tr nodes
6491 				*/
6492 			break;
6493 			case 5: // any parent note, another extension of mine to go up the tree (backward of the whitespace operator)
6494 				/*
6495 					Like with the < operator, this is best used to find some parent of a particular known element.
6496 
6497 					Say you have an anchor inside a
6498 				*/
6499 		}
6500 
6501 		return ret;
6502 	}
6503 
6504 	/++
6505 		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.
6506 
6507 		See_Also:
6508 			$(LIST
6509 				* [Element.querySelector]
6510 				* [Element.querySelectorAll]
6511 				* [Element.matches]
6512 				* [Element.closest]
6513 				* [Document.querySelector]
6514 				* [Document.querySelectorAll]
6515 			)
6516 	+/
6517 	/// Group: core_functionality
6518 	struct Selector {
6519 		SelectorComponent[] components;
6520 		string original;
6521 		/++
6522 			Parses the selector string and constructs the usable structure.
6523 		+/
6524 		this(string cssSelector) {
6525 			components = parseSelectorString(cssSelector);
6526 			original = cssSelector;
6527 		}
6528 
6529 		/++
6530 			Returns true if the given element matches this selector,
6531 			considered relative to an arbitrary element.
6532 
6533 			You can do a form of lazy [Element.querySelectorAll|querySelectorAll] by using this
6534 			with [std.algorithm.iteration.filter]:
6535 
6536 			---
6537 			Selector sel = Selector("foo > bar");
6538 			auto lazySelectorRange = element.tree.filter!(e => sel.matchElement(e))(document.root);
6539 			---
6540 		+/
6541 		bool matchesElement(Element e, Element relativeTo = null) {
6542 			foreach(component; components)
6543 				if(component.matchElement(e, relativeTo))
6544 					return true;
6545 
6546 			return false;
6547 		}
6548 
6549 		/++
6550 			Reciprocal of [Element.querySelectorAll]
6551 		+/
6552 		Element[] getMatchingElements(Element start) {
6553 			Element[] ret;
6554 			foreach(component; components)
6555 				ret ~= getElementsBySelectorParts(start, component.parts);
6556 			return removeDuplicates(ret);
6557 		}
6558 
6559 		/++
6560 			Like [getMatchingElements], but returns a lazy range. Be careful
6561 			about mutating the dom as you iterate through this.
6562 		+/
6563 		auto getMatchingElementsLazy(Element start, Element relativeTo = null) {
6564 			import std.algorithm.iteration;
6565 			return start.tree.filter!(a => this.matchesElement(a, relativeTo));
6566 		}
6567 
6568 
6569 		/// Returns the string this was built from
6570 		string toString() {
6571 			return original;
6572 		}
6573 
6574 		/++
6575 			Returns a string from the parsed result
6576 
6577 
6578 			(may not match the original, this is mostly for debugging right now but in the future might be useful for pretty-printing)
6579 		+/
6580 		string parsedToString() {
6581 			string ret;
6582 
6583 			foreach(idx, component; components) {
6584 				if(idx) ret ~= ", ";
6585 				ret ~= component.toString();
6586 			}
6587 
6588 			return ret;
6589 		}
6590 	}
6591 
6592 	///.
6593 	struct SelectorComponent {
6594 		///.
6595 		SelectorPart[] parts;
6596 
6597 		///.
6598 		string toString() {
6599 			string ret;
6600 			foreach(part; parts)
6601 				ret ~= part.toString();
6602 			return ret;
6603 		}
6604 
6605 		// USEFUL
6606 		///.
6607 		Element[] getElements(Element start) {
6608 			return removeDuplicates(getElementsBySelectorParts(start, parts));
6609 		}
6610 
6611 		// USEFUL (but not implemented)
6612 		/// If relativeTo == null, it assumes the root of the parent document.
6613 		bool matchElement(Element e, Element relativeTo = null) {
6614 			if(e is null) return false;
6615 			Element where = e;
6616 			int lastSeparation = -1;
6617 
6618 			auto lparts = parts;
6619 
6620 			if(parts.length && parts[0].separation > 0) {
6621 				// if it starts with a non-trivial separator, inject
6622 				// a "*" matcher to act as a root. for cases like document.querySelector("> body")
6623 				// which implies html
6624 
6625 				// however, if it is a child-matching selector and there are no children,
6626 				// bail out early as it obviously cannot match.
6627 				bool hasNonTextChildren = false;
6628 				foreach(c; e.children)
6629 					if(c.nodeType != 3) {
6630 						hasNonTextChildren = true;
6631 						break;
6632 					}
6633 				if(!hasNonTextChildren)
6634 					return false;
6635 
6636 				// there is probably a MUCH better way to do this.
6637 				auto dummy = SelectorPart.init;
6638 				dummy.tagNameFilter = "*";
6639 				dummy.separation = 0;
6640 				lparts = dummy ~ lparts;
6641 			}
6642 
6643 			foreach(part; retro(lparts)) {
6644 
6645 				 // writeln("matching ", where, " with ", part, " via ", lastSeparation);
6646 				 // writeln(parts);
6647 
6648 				if(lastSeparation == -1) {
6649 					if(!part.matchElement(where))
6650 						return false;
6651 				} else if(lastSeparation == 0) { // generic parent
6652 					// need to go up the whole chain
6653 					where = where.parentNode;
6654 
6655 					while(where !is null) {
6656 						if(part.matchElement(where))
6657 							break;
6658 
6659 						if(where is relativeTo)
6660 							return false;
6661 
6662 						where = where.parentNode;
6663 					}
6664 
6665 					if(where is null)
6666 						return false;
6667 				} else if(lastSeparation == 1) { // the > operator
6668 					where = where.parentNode;
6669 
6670 					if(!part.matchElement(where))
6671 						return false;
6672 				} else if(lastSeparation == 2) { // the + operator
6673 				//writeln("WHERE", where, " ", part);
6674 					where = where.previousSibling("*");
6675 
6676 					if(!part.matchElement(where))
6677 						return false;
6678 				} else if(lastSeparation == 3) { // the ~ operator
6679 					where = where.previousSibling("*");
6680 					while(where !is null) {
6681 						if(part.matchElement(where))
6682 							break;
6683 
6684 						if(where is relativeTo)
6685 							return false;
6686 
6687 						where = where.previousSibling("*");
6688 					}
6689 
6690 					if(where is null)
6691 						return false;
6692 				} else if(lastSeparation == 4) { // my bad idea extension < operator, don't use this anymore
6693 					// FIXME
6694 				}
6695 
6696 				lastSeparation = part.separation;
6697 
6698 				if(where is relativeTo)
6699 					return false; // at end of line, if we aren't done by now, the match fails
6700 			}
6701 			return true; // if we got here, it is a success
6702 		}
6703 
6704 		// the string should NOT have commas. Use parseSelectorString for that instead
6705 		///.
6706 		static SelectorComponent fromString(string selector) {
6707 			return parseSelector(lexSelector(selector));
6708 		}
6709 	}
6710 
6711 	///.
6712 	SelectorComponent[] parseSelectorString(string selector, bool caseSensitiveTags = true) {
6713 		SelectorComponent[] ret;
6714 		auto tokens = lexSelector(selector); // this will parse commas too
6715 		// and now do comma-separated slices (i haz phobosophobia!)
6716 		int parensCount = 0;
6717 		while (tokens.length > 0) {
6718 			size_t end = 0;
6719 			while (end < tokens.length && (parensCount > 0 || tokens[end] != ",")) {
6720 				if(tokens[end] == "(") parensCount++;
6721 				if(tokens[end] == ")") parensCount--;
6722 				++end;
6723 			}
6724 			if (end > 0) ret ~= parseSelector(tokens[0..end], caseSensitiveTags);
6725 			if (tokens.length-end < 2) break;
6726 			tokens = tokens[end+1..$];
6727 		}
6728 		return ret;
6729 	}
6730 
6731 	///.
6732 	SelectorComponent parseSelector(string[] tokens, bool caseSensitiveTags = true) {
6733 		SelectorComponent s;
6734 
6735 		SelectorPart current;
6736 		void commit() {
6737 			// might as well skip null items
6738 			if(!current.isCleanSlateExceptSeparation()) {
6739 				s.parts ~= current;
6740 				current = current.init; // start right over
6741 			}
6742 		}
6743 		enum State {
6744 			Starting,
6745 			ReadingClass,
6746 			ReadingId,
6747 			ReadingAttributeSelector,
6748 			ReadingAttributeComparison,
6749 			ExpectingAttributeCloser,
6750 			ReadingPseudoClass,
6751 			ReadingAttributeValue,
6752 
6753 			SkippingFunctionalSelector,
6754 		}
6755 		State state = State.Starting;
6756 		string attributeName, attributeValue, attributeComparison;
6757 		int parensCount;
6758 		foreach(idx, token; tokens) {
6759 			string readFunctionalSelector() {
6760 				string s;
6761 				if(tokens[idx + 1] != "(")
6762 					throw new Exception("parse error");
6763 				int pc = 1;
6764 				foreach(t; tokens[idx + 2 .. $]) {
6765 					if(t == "(")
6766 						pc++;
6767 					if(t == ")")
6768 						pc--;
6769 					if(pc == 0)
6770 						break;
6771 					s ~= t;
6772 				}
6773 
6774 				return s;
6775 			}
6776 
6777 			sizediff_t tid = -1;
6778 			foreach(i, item; selectorTokens)
6779 				if(token == item) {
6780 					tid = i;
6781 					break;
6782 				}
6783 			final switch(state) {
6784 				case State.Starting: // fresh, might be reading an operator or a tagname
6785 					if(tid == -1) {
6786 						if(!caseSensitiveTags)
6787 							token = token.toLower();
6788 
6789 						if(current.isCleanSlateExceptSeparation()) {
6790 							current.tagNameFilter = token;
6791 							// default thing, see comment under "*" below
6792 							if(current.separation == -1) current.separation = 0;
6793 						} else {
6794 							// if it was already set, we must see two thingies
6795 							// separated by whitespace...
6796 							commit();
6797 							current.separation = 0; // tree
6798 							current.tagNameFilter = token;
6799 						}
6800 					} else {
6801 						// Selector operators
6802 						switch(token) {
6803 							case "*":
6804 								current.tagNameFilter = "*";
6805 								// the idea here is if we haven't actually set a separation
6806 								// yet (e.g. the > operator), it should assume the generic
6807 								// whitespace (descendant) mode to avoid matching self with -1
6808 								if(current.separation == -1) current.separation = 0;
6809 							break;
6810 							case " ":
6811 								// If some other separation has already been set,
6812 								// this is irrelevant whitespace, so we should skip it.
6813 								// this happens in the case of "foo > bar" for example.
6814 								if(current.isCleanSlateExceptSeparation() && current.separation > 0)
6815 									continue;
6816 								commit();
6817 								current.separation = 0; // tree
6818 							break;
6819 							case ">>":
6820 								commit();
6821 								current.separation = 0; // alternate syntax for tree from html5 css
6822 							break;
6823 							case ">":
6824 								commit();
6825 								current.separation = 1; // child
6826 							break;
6827 							case "+":
6828 								commit();
6829 								current.separation = 2; // sibling directly after
6830 							break;
6831 							case "~":
6832 								commit();
6833 								current.separation = 3; // any sibling after
6834 							break;
6835 							case "<":
6836 								commit();
6837 								current.separation = 4; // immediate parent of
6838 							break;
6839 							case "[":
6840 								state = State.ReadingAttributeSelector;
6841 								if(current.separation == -1) current.separation = 0;
6842 							break;
6843 							case ".":
6844 								state = State.ReadingClass;
6845 								if(current.separation == -1) current.separation = 0;
6846 							break;
6847 							case "#":
6848 								state = State.ReadingId;
6849 								if(current.separation == -1) current.separation = 0;
6850 							break;
6851 							case ":":
6852 							case "::":
6853 								state = State.ReadingPseudoClass;
6854 								if(current.separation == -1) current.separation = 0;
6855 							break;
6856 
6857 							default:
6858 								assert(0, token);
6859 						}
6860 					}
6861 				break;
6862 				case State.ReadingClass:
6863 					current.attributesIncludesSeparatedBySpaces ~= ["class", token];
6864 					state = State.Starting;
6865 				break;
6866 				case State.ReadingId:
6867 					current.attributesEqual ~= ["id", token];
6868 					state = State.Starting;
6869 				break;
6870 				case State.ReadingPseudoClass:
6871 					switch(token) {
6872 						case "first-of-type":
6873 							current.firstOfType = true;
6874 						break;
6875 						case "last-of-type":
6876 							current.lastOfType = true;
6877 						break;
6878 						case "only-of-type":
6879 							current.firstOfType = true;
6880 							current.lastOfType = true;
6881 						break;
6882 						case "first-child":
6883 							current.firstChild = true;
6884 						break;
6885 						case "last-child":
6886 							current.lastChild = true;
6887 						break;
6888 						case "only-child":
6889 							current.firstChild = true;
6890 							current.lastChild = true;
6891 						break;
6892 						case "scope":
6893 							current.scopeElement = true;
6894 						break;
6895 						case "empty":
6896 							// one with no children
6897 							current.emptyElement = true;
6898 						break;
6899 						case "whitespace-only":
6900 							current.whitespaceOnly = true;
6901 						break;
6902 						case "link":
6903 							current.attributesPresent ~= "href";
6904 						break;
6905 						case "root":
6906 							current.rootElement = true;
6907 						break;
6908 						case "nth-child":
6909 							current.nthChild ~= ParsedNth(readFunctionalSelector());
6910 							state = State.SkippingFunctionalSelector;
6911 						continue;
6912 						case "nth-of-type":
6913 							current.nthOfType ~= ParsedNth(readFunctionalSelector());
6914 							state = State.SkippingFunctionalSelector;
6915 						continue;
6916 						case "nth-last-of-type":
6917 							current.nthLastOfType ~= ParsedNth(readFunctionalSelector());
6918 							state = State.SkippingFunctionalSelector;
6919 						continue;
6920 						case "is":
6921 							state = State.SkippingFunctionalSelector;
6922 							current.isSelectors ~= readFunctionalSelector();
6923 						continue; // now the rest of the parser skips past the parens we just handled
6924 						case "where":
6925 							state = State.SkippingFunctionalSelector;
6926 							current.whereSelectors ~= readFunctionalSelector();
6927 						continue; // now the rest of the parser skips past the parens we just handled
6928 						case "not":
6929 							state = State.SkippingFunctionalSelector;
6930 							current.notSelectors ~= readFunctionalSelector();
6931 						continue; // now the rest of the parser skips past the parens we just handled
6932 						case "has":
6933 							state = State.SkippingFunctionalSelector;
6934 							current.hasSelectors ~= readFunctionalSelector();
6935 						continue; // now the rest of the parser skips past the parens we just handled
6936 						// back to standards though not quite right lol
6937 						case "disabled":
6938 							current.attributesPresent ~= "disabled";
6939 						break;
6940 						case "checked":
6941 							current.attributesPresent ~= "checked";
6942 						break;
6943 
6944 						case "visited", "active", "hover", "target", "focus", "selected":
6945 							current.attributesPresent ~= "nothing";
6946 							// FIXME
6947 						/+
6948 						// extensions not implemented
6949 						//case "text": // takes the text in the element and wraps it in an element, returning it
6950 						+/
6951 							goto case;
6952 						case "before", "after":
6953 							current.attributesPresent ~= "FIXME";
6954 
6955 						break;
6956 						// My extensions
6957 						case "odd-child":
6958 							current.oddChild = true;
6959 						break;
6960 						case "even-child":
6961 							current.evenChild = true;
6962 						break;
6963 						default:
6964 							//if(token.indexOf("lang") == -1)
6965 							//assert(0, token);
6966 						break;
6967 					}
6968 					state = State.Starting;
6969 				break;
6970 				case State.SkippingFunctionalSelector:
6971 					if(token == "(") {
6972 						parensCount++;
6973 					} else if(token == ")") {
6974 						parensCount--;
6975 					}
6976 
6977 					if(parensCount == 0)
6978 						state = State.Starting;
6979 				break;
6980 				case State.ReadingAttributeSelector:
6981 					attributeName = token;
6982 					attributeComparison = null;
6983 					attributeValue = null;
6984 					state = State.ReadingAttributeComparison;
6985 				break;
6986 				case State.ReadingAttributeComparison:
6987 					// FIXME: these things really should be quotable in the proper lexer...
6988 					if(token != "]") {
6989 						if(token.indexOf("=") == -1) {
6990 							// not a comparison; consider it
6991 							// part of the attribute
6992 							attributeValue ~= token;
6993 						} else {
6994 							attributeComparison = token;
6995 							state = State.ReadingAttributeValue;
6996 						}
6997 						break;
6998 					}
6999 					goto case;
7000 				case State.ExpectingAttributeCloser:
7001 					if(token != "]") {
7002 						// not the closer; consider it part of comparison
7003 						if(attributeComparison == "")
7004 							attributeName ~= token;
7005 						else
7006 							attributeValue ~= token;
7007 						break;
7008 					}
7009 
7010 					// Selector operators
7011 					switch(attributeComparison) {
7012 						default: assert(0);
7013 						case "":
7014 							current.attributesPresent ~= attributeName;
7015 						break;
7016 						case "=":
7017 							current.attributesEqual ~= [attributeName, attributeValue];
7018 						break;
7019 						case "|=":
7020 							current.attributesIncludesSeparatedByDashes ~= [attributeName, attributeValue];
7021 						break;
7022 						case "~=":
7023 							current.attributesIncludesSeparatedBySpaces ~= [attributeName, attributeValue];
7024 						break;
7025 						case "$=":
7026 							current.attributesEndsWith ~= [attributeName, attributeValue];
7027 						break;
7028 						case "^=":
7029 							current.attributesStartsWith ~= [attributeName, attributeValue];
7030 						break;
7031 						case "*=":
7032 							current.attributesInclude ~= [attributeName, attributeValue];
7033 						break;
7034 						case "!=":
7035 							current.attributesNotEqual ~= [attributeName, attributeValue];
7036 						break;
7037 					}
7038 
7039 					state = State.Starting;
7040 				break;
7041 				case State.ReadingAttributeValue:
7042 					attributeValue = token;
7043 					state = State.ExpectingAttributeCloser;
7044 				break;
7045 			}
7046 		}
7047 
7048 		commit();
7049 
7050 		return s;
7051 	}
7052 
7053 ///.
7054 Element[] removeDuplicates(Element[] input) {
7055 	Element[] ret;
7056 
7057 	bool[Element] already;
7058 	foreach(e; input) {
7059 		if(e in already) continue;
7060 		already[e] = true;
7061 		ret ~= e;
7062 	}
7063 
7064 	return ret;
7065 }
7066 
7067 // done with CSS selector handling
7068 
7069 
7070 // FIXME: use the better parser from html.d
7071 /// This is probably not useful to you unless you're writing a browser or something like that.
7072 /// It represents a *computed* style, like what the browser gives you after applying stylesheets, inline styles, and html attributes.
7073 /// From here, you can start to make a layout engine for the box model and have a css aware browser.
7074 class CssStyle {
7075 	///.
7076 	this(string rule, string content) {
7077 		rule = rule.strip();
7078 		content = content.strip();
7079 
7080 		if(content.length == 0)
7081 			return;
7082 
7083 		originatingRule = rule;
7084 		originatingSpecificity = getSpecificityOfRule(rule); // FIXME: if there's commas, this won't actually work!
7085 
7086 		foreach(part; content.split(";")) {
7087 			part = part.strip();
7088 			if(part.length == 0)
7089 				continue;
7090 			auto idx = part.indexOf(":");
7091 			if(idx == -1)
7092 				continue;
7093 				//throw new Exception("Bad css rule (no colon): " ~ part);
7094 
7095 			Property p;
7096 
7097 			p.name = part[0 .. idx].strip();
7098 			p.value = part[idx + 1 .. $].replace("! important", "!important").replace("!important", "").strip(); // FIXME don't drop important
7099 			p.givenExplicitly = true;
7100 			p.specificity = originatingSpecificity;
7101 
7102 			properties ~= p;
7103 		}
7104 
7105 		foreach(property; properties)
7106 			expandShortForm(property, originatingSpecificity);
7107 	}
7108 
7109 	///.
7110 	Specificity getSpecificityOfRule(string rule) {
7111 		Specificity s;
7112 		if(rule.length == 0) { // inline
7113 		//	s.important = 2;
7114 		} else {
7115 			// FIXME
7116 		}
7117 
7118 		return s;
7119 	}
7120 
7121 	string originatingRule; ///.
7122 	Specificity originatingSpecificity; ///.
7123 
7124 	///.
7125 	union Specificity {
7126 		uint score; ///.
7127 		// version(little_endian)
7128 		///.
7129 		struct {
7130 			ubyte tags; ///.
7131 			ubyte classes; ///.
7132 			ubyte ids; ///.
7133 			ubyte important; /// 0 = none, 1 = stylesheet author, 2 = inline style, 3 = user important
7134 		}
7135 	}
7136 
7137 	///.
7138 	struct Property {
7139 		bool givenExplicitly; /// this is false if for example the user said "padding" and this is "padding-left"
7140 		string name; ///.
7141 		string value; ///.
7142 		Specificity specificity; ///.
7143 		// do we care about the original source rule?
7144 	}
7145 
7146 	///.
7147 	Property[] properties;
7148 
7149 	///.
7150 	string opDispatch(string nameGiven)(string value = null) if(nameGiven != "popFront") {
7151 		string name = unCamelCase(nameGiven);
7152 		if(value is null)
7153 			return getValue(name);
7154 		else
7155 			return setValue(name, value, 0x02000000 /* inline specificity */);
7156 	}
7157 
7158 	/// takes dash style name
7159 	string getValue(string name) {
7160 		foreach(property; properties)
7161 			if(property.name == name)
7162 				return property.value;
7163 		return null;
7164 	}
7165 
7166 	/// takes dash style name
7167 	string setValue(string name, string value, Specificity newSpecificity, bool explicit = true) {
7168 		value = value.replace("! important", "!important");
7169 		if(value.indexOf("!important") != -1) {
7170 			newSpecificity.important = 1; // FIXME
7171 			value = value.replace("!important", "").strip();
7172 		}
7173 
7174 		foreach(ref property; properties)
7175 			if(property.name == name) {
7176 				if(newSpecificity.score >= property.specificity.score) {
7177 					property.givenExplicitly = explicit;
7178 					expandShortForm(property, newSpecificity);
7179 					return (property.value = value);
7180 				} else {
7181 					if(name == "display")
7182 					{}//writeln("Not setting ", name, " to ", value, " because ", newSpecificity.score, " < ", property.specificity.score);
7183 					return value; // do nothing - the specificity is too low
7184 				}
7185 			}
7186 
7187 		// it's not here...
7188 
7189 		Property p;
7190 		p.givenExplicitly = true;
7191 		p.name = name;
7192 		p.value = value;
7193 		p.specificity = originatingSpecificity;
7194 
7195 		properties ~= p;
7196 		expandShortForm(p, originatingSpecificity);
7197 
7198 		return value;
7199 	}
7200 
7201 	private void expandQuadShort(string name, string value, Specificity specificity) {
7202 		auto parts = value.split(" ");
7203 		switch(parts.length) {
7204 			case 1:
7205 				setValue(name ~"-left", parts[0], specificity, false);
7206 				setValue(name ~"-right", parts[0], specificity, false);
7207 				setValue(name ~"-top", parts[0], specificity, false);
7208 				setValue(name ~"-bottom", parts[0], specificity, false);
7209 			break;
7210 			case 2:
7211 				setValue(name ~"-left", parts[1], specificity, false);
7212 				setValue(name ~"-right", parts[1], specificity, false);
7213 				setValue(name ~"-top", parts[0], specificity, false);
7214 				setValue(name ~"-bottom", parts[0], specificity, false);
7215 			break;
7216 			case 3:
7217 				setValue(name ~"-top", parts[0], specificity, false);
7218 				setValue(name ~"-right", parts[1], specificity, false);
7219 				setValue(name ~"-bottom", parts[2], specificity, false);
7220 				setValue(name ~"-left", parts[2], specificity, false);
7221 
7222 			break;
7223 			case 4:
7224 				setValue(name ~"-top", parts[0], specificity, false);
7225 				setValue(name ~"-right", parts[1], specificity, false);
7226 				setValue(name ~"-bottom", parts[2], specificity, false);
7227 				setValue(name ~"-left", parts[3], specificity, false);
7228 			break;
7229 			default:
7230 				assert(0, value);
7231 		}
7232 	}
7233 
7234 	///.
7235 	void expandShortForm(Property p, Specificity specificity) {
7236 		switch(p.name) {
7237 			case "margin":
7238 			case "padding":
7239 				expandQuadShort(p.name, p.value, specificity);
7240 			break;
7241 			case "border":
7242 			case "outline":
7243 				setValue(p.name ~ "-left", p.value, specificity, false);
7244 				setValue(p.name ~ "-right", p.value, specificity, false);
7245 				setValue(p.name ~ "-top", p.value, specificity, false);
7246 				setValue(p.name ~ "-bottom", p.value, specificity, false);
7247 			break;
7248 
7249 			case "border-top":
7250 			case "border-bottom":
7251 			case "border-left":
7252 			case "border-right":
7253 			case "outline-top":
7254 			case "outline-bottom":
7255 			case "outline-left":
7256 			case "outline-right":
7257 
7258 			default: {}
7259 		}
7260 	}
7261 
7262 	///.
7263 	override string toString() {
7264 		string ret;
7265 		if(originatingRule.length)
7266 			ret = originatingRule ~ " {";
7267 
7268 		foreach(property; properties) {
7269 			if(!property.givenExplicitly)
7270 				continue; // skip the inferred shit
7271 
7272 			if(originatingRule.length)
7273 				ret ~= "\n\t";
7274 			else
7275 				ret ~= " ";
7276 
7277 			ret ~= property.name ~ ": " ~ property.value ~ ";";
7278 		}
7279 
7280 		if(originatingRule.length)
7281 			ret ~= "\n}\n";
7282 
7283 		return ret;
7284 	}
7285 }
7286 
7287 string cssUrl(string url) {
7288 	return "url(\"" ~ url ~ "\")";
7289 }
7290 
7291 /// This probably isn't useful, unless you're writing a browser or something like that.
7292 /// You might want to look at arsd.html for css macro, nesting, etc., or just use standard css
7293 /// as text.
7294 ///
7295 /// The idea, however, is to represent a kind of CSS object model, complete with specificity,
7296 /// that you can apply to your documents to build the complete computedStyle object.
7297 class StyleSheet {
7298 	///.
7299 	CssStyle[] rules;
7300 
7301 	///.
7302 	this(string source) {
7303 		// FIXME: handle @ rules and probably could improve lexer
7304 		// add nesting?
7305 		int state;
7306 		string currentRule;
7307 		string currentValue;
7308 
7309 		string* currentThing = &currentRule;
7310 		foreach(c; source) {
7311 			handle: switch(state) {
7312 				default: assert(0);
7313 				case 0: // starting - we assume we're reading a rule
7314 					switch(c) {
7315 						case '@':
7316 							state = 4;
7317 						break;
7318 						case '/':
7319 							state = 1;
7320 						break;
7321 						case '{':
7322 							currentThing = &currentValue;
7323 						break;
7324 						case '}':
7325 							if(currentThing is &currentValue) {
7326 								rules ~= new CssStyle(currentRule, currentValue);
7327 
7328 								currentRule = "";
7329 								currentValue = "";
7330 
7331 								currentThing = &currentRule;
7332 							} else {
7333 								// idk what is going on here.
7334 								// check sveit.com to reproduce
7335 								currentRule = "";
7336 								currentValue = "";
7337 							}
7338 						break;
7339 						default:
7340 							(*currentThing) ~= c;
7341 					}
7342 				break;
7343 				case 1: // expecting *
7344 					if(c == '*')
7345 						state = 2;
7346 					else {
7347 						state = 0;
7348 						(*currentThing) ~= "/" ~ c;
7349 					}
7350 				break;
7351 				case 2: // inside comment
7352 					if(c == '*')
7353 						state = 3;
7354 				break;
7355 				case 3: // expecting / to end comment
7356 					if(c == '/')
7357 						state = 0;
7358 					else
7359 						state = 2; // it's just a comment so no need to append
7360 				break;
7361 				case 4:
7362 					if(c == '{')
7363 						state = 5;
7364 					if(c == ';')
7365 						state = 0; // just skipping import
7366 				break;
7367 				case 5:
7368 					if(c == '}')
7369 						state = 0; // skipping font face probably
7370 			}
7371 		}
7372 	}
7373 
7374 	/// Run through the document and apply this stylesheet to it. The computedStyle member will be accurate after this call
7375 	void apply(Document document) {
7376 		foreach(rule; rules) {
7377 			if(rule.originatingRule.length == 0)
7378 				continue; // this shouldn't happen here in a stylesheet
7379 			foreach(element; document.querySelectorAll(rule.originatingRule)) {
7380 				// note: this should be a different object than the inline style
7381 				// since givenExplicitly is likely destroyed here
7382 				auto current = element.computedStyle;
7383 
7384 				foreach(item; rule.properties)
7385 					current.setValue(item.name, item.value, item.specificity);
7386 			}
7387 		}
7388 	}
7389 }
7390 
7391 
7392 /// This is kinda private; just a little utility container for use by the ElementStream class.
7393 final class Stack(T) {
7394 	this() {
7395 		internalLength = 0;
7396 		arr = initialBuffer[];
7397 	}
7398 
7399 	///.
7400 	void push(T t) {
7401 		if(internalLength >= arr.length) {
7402 			auto oldarr = arr;
7403 			if(arr.length < 4096)
7404 				arr = new T[arr.length * 2];
7405 			else
7406 				arr = new T[arr.length + 4096];
7407 			arr[0 .. oldarr.length] = oldarr[];
7408 		}
7409 
7410 		arr[internalLength] = t;
7411 		internalLength++;
7412 	}
7413 
7414 	///.
7415 	T pop() {
7416 		assert(internalLength);
7417 		internalLength--;
7418 		return arr[internalLength];
7419 	}
7420 
7421 	///.
7422 	T peek() {
7423 		assert(internalLength);
7424 		return arr[internalLength - 1];
7425 	}
7426 
7427 	///.
7428 	@property bool empty() {
7429 		return internalLength ? false : true;
7430 	}
7431 
7432 	///.
7433 	private T[] arr;
7434 	private size_t internalLength;
7435 	private T[64] initialBuffer;
7436 	// 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),
7437 	// using this saves us a bunch of trips to the GC. In my last profiling, I got about a 50x improvement in the push()
7438 	// function thanks to this, and push() was actually one of the slowest individual functions in the code!
7439 }
7440 
7441 /// 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.
7442 final class ElementStream {
7443 
7444 	///.
7445 	@property Element front() {
7446 		return current.element;
7447 	}
7448 
7449 	/// Use Element.tree instead.
7450 	this(Element start) {
7451 		current.element = start;
7452 		current.childPosition = -1;
7453 		isEmpty = false;
7454 		stack = new Stack!(Current);
7455 	}
7456 
7457 	/*
7458 		Handle it
7459 		handle its children
7460 
7461 	*/
7462 
7463 	///.
7464 	void popFront() {
7465 	    more:
7466 	    	if(isEmpty) return;
7467 
7468 		// FIXME: the profiler says this function is somewhat slow (noticeable because it can be called a lot of times)
7469 
7470 		current.childPosition++;
7471 		if(current.childPosition >= current.element.children.length) {
7472 			if(stack.empty())
7473 				isEmpty = true;
7474 			else {
7475 				current = stack.pop();
7476 				goto more;
7477 			}
7478 		} else {
7479 			stack.push(current);
7480 			current.element = current.element.children[current.childPosition];
7481 			current.childPosition = -1;
7482 		}
7483 	}
7484 
7485 	/// 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.
7486 	void currentKilled() {
7487 		if(stack.empty) // should never happen
7488 			isEmpty = true;
7489 		else {
7490 			current = stack.pop();
7491 			current.childPosition--; // when it is killed, the parent is brought back a lil so when we popFront, this is then right
7492 		}
7493 	}
7494 
7495 	///.
7496 	@property bool empty() {
7497 		return isEmpty;
7498 	}
7499 
7500 	private:
7501 
7502 	struct Current {
7503 		Element element;
7504 		int childPosition;
7505 	}
7506 
7507 	Current current;
7508 
7509 	Stack!(Current) stack;
7510 
7511 	bool isEmpty;
7512 }
7513 
7514 
7515 
7516 // unbelievable.
7517 // 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.
7518 sizediff_t indexOfBytes(immutable(ubyte)[] haystack, immutable(ubyte)[] needle) {
7519 	static import std.algorithm;
7520 	auto found = std.algorithm.find(haystack, needle);
7521 	if(found.length == 0)
7522 		return -1;
7523 	return haystack.length - found.length;
7524 }
7525 
7526 private T[] insertAfter(T)(T[] arr, int position, T[] what) {
7527 	assert(position < arr.length);
7528 	T[] ret;
7529 	ret.length = arr.length + what.length;
7530 	int a = 0;
7531 	foreach(i; arr[0..position+1])
7532 		ret[a++] = i;
7533 
7534 	foreach(i; what)
7535 		ret[a++] = i;
7536 
7537 	foreach(i; arr[position+1..$])
7538 		ret[a++] = i;
7539 
7540 	return ret;
7541 }
7542 
7543 package bool isInArray(T)(T item, T[] arr) {
7544 	foreach(i; arr)
7545 		if(item == i)
7546 			return true;
7547 	return false;
7548 }
7549 
7550 private string[string] aadup(in string[string] arr) {
7551 	string[string] ret;
7552 	foreach(k, v; arr)
7553 		ret[k] = v;
7554 	return ret;
7555 }
7556 
7557 
7558 
7559 
7560 
7561 
7562 
7563 
7564 
7565 
7566 
7567 
7568 
7569 
7570 
7571 // 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)
7572 
7573 immutable string[] availableEntities =
7574 ["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", 
7575 "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", 
7576 "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", 
7577 "ClockwiseContourIntegral", "ClockwiseContourIntegral", "CloseCurlyDoubleQuote", "CloseCurlyDoubleQuote", "CloseCurlyQuote", "CloseCurlyQuote", "Colon", "Colon", "Colone", "Colone", "Congruent", "Congruent", "Conint", "Conint", "ContourIntegral", "ContourIntegral", "Copf", "Copf", "Coproduct", "Coproduct", "CounterClockwiseContourIntegral", 
7578 "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", 
7579 "DiacriticalAcute", "DiacriticalAcute", "DiacriticalDot", "DiacriticalDot", "DiacriticalDoubleAcute", "DiacriticalDoubleAcute", "DiacriticalGrave", "DiacriticalGrave", "DiacriticalTilde", "DiacriticalTilde", "Diamond", "Diamond", "DifferentialD", "DifferentialD", "Dopf", "Dopf", "Dot", "Dot", "DotDot", "DotDot", "DotEqual", 
7580 "DotEqual", "DoubleContourIntegral", "DoubleContourIntegral", "DoubleDot", "DoubleDot", "DoubleDownArrow", "DoubleDownArrow", "DoubleLeftArrow", "DoubleLeftArrow", "DoubleLeftRightArrow", "DoubleLeftRightArrow", "DoubleLeftTee", "DoubleLeftTee", "DoubleLongLeftArrow", "DoubleLongLeftArrow", "DoubleLongLeftRightArrow", 
7581 "DoubleLongLeftRightArrow", "DoubleLongRightArrow", "DoubleLongRightArrow", "DoubleRightArrow", "DoubleRightArrow", "DoubleRightTee", "DoubleRightTee", "DoubleUpArrow", "DoubleUpArrow", "DoubleUpDownArrow", "DoubleUpDownArrow", "DoubleVerticalBar", "DoubleVerticalBar", "DownArrow", "DownArrow", "DownArrowBar", "DownArrowBar", 
7582 "DownArrowUpArrow", "DownArrowUpArrow", "DownBreve", "DownBreve", "DownLeftRightVector", "DownLeftRightVector", "DownLeftTeeVector", "DownLeftTeeVector", "DownLeftVector", "DownLeftVector", "DownLeftVectorBar", "DownLeftVectorBar", "DownRightTeeVector", "DownRightTeeVector", "DownRightVector", "DownRightVector", "DownRightVectorBar", 
7583 "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", 
7584 "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", 
7585 "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", 
7586 "Gcy", "Gcy", "Gdot", "Gdot", "Gfr", "Gfr", "Gg", "Gg", "Gopf", "Gopf", "GreaterEqual", "GreaterEqual", "GreaterEqualLess", "GreaterEqualLess", "GreaterFullEqual", "GreaterFullEqual", "GreaterGreater", "GreaterGreater", "GreaterLess", "GreaterLess", "GreaterSlantEqual", "GreaterSlantEqual", "GreaterTilde", "GreaterTilde", 
7587 "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", 
7588 "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", 
7589 "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", 
7590 "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", 
7591 "LeftArrowBar", "LeftArrowRightArrow", "LeftArrowRightArrow", "LeftCeiling", "LeftCeiling", "LeftDoubleBracket", "LeftDoubleBracket", "LeftDownTeeVector", "LeftDownTeeVector", "LeftDownVector", "LeftDownVector", "LeftDownVectorBar", "LeftDownVectorBar", "LeftFloor", "LeftFloor", "LeftRightArrow", "LeftRightArrow", "LeftRightVector", 
7592 "LeftRightVector", "LeftTee", "LeftTee", "LeftTeeArrow", "LeftTeeArrow", "LeftTeeVector", "LeftTeeVector", "LeftTriangle", "LeftTriangle", "LeftTriangleBar", "LeftTriangleBar", "LeftTriangleEqual", "LeftTriangleEqual", "LeftUpDownVector", "LeftUpDownVector", "LeftUpTeeVector", "LeftUpTeeVector", "LeftUpVector", "LeftUpVector", 
7593 "LeftUpVectorBar", "LeftUpVectorBar", "LeftVector", "LeftVector", "LeftVectorBar", "LeftVectorBar", "Leftarrow", "Leftarrow", "Leftrightarrow", "Leftrightarrow", "LessEqualGreater", "LessEqualGreater", "LessFullEqual", "LessFullEqual", "LessGreater", "LessGreater", "LessLess", "LessLess", "LessSlantEqual", "LessSlantEqual", 
7594 "LessTilde", "LessTilde", "Lfr", "Lfr", "Ll", "Ll", "Lleftarrow", "Lleftarrow", "Lmidot", "Lmidot", "LongLeftArrow", "LongLeftArrow", "LongLeftRightArrow", "LongLeftRightArrow", "LongRightArrow", "LongRightArrow", "Longleftarrow", "Longleftarrow", "Longleftrightarrow", "Longleftrightarrow", "Longrightarrow", "Longrightarrow", 
7595 "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", 
7596 "NJcy", "NJcy", "Nacute", "Nacute", "Ncaron", "Ncaron", "Ncedil", "Ncedil", "Ncy", "Ncy", "NegativeMediumSpace", "NegativeMediumSpace", "NegativeThickSpace", "NegativeThickSpace", "NegativeThinSpace", "NegativeThinSpace", "NegativeVeryThinSpace", "NegativeVeryThinSpace", "NestedGreaterGreater", "NestedGreaterGreater", 
7597 "NestedLessLess", "NestedLessLess", "NewLine", "NewLine", "Nfr", "Nfr", "NoBreak", "NoBreak", "NonBreakingSpace", "NonBreakingSpace", "Nopf", "Nopf", "Not", "Not", "NotCongruent", "NotCongruent", "NotCupCap", "NotCupCap", "NotDoubleVerticalBar", "NotDoubleVerticalBar", "NotElement", "NotElement", "NotEqual", "NotEqual", 
7598 "NotExists", "NotExists", "NotGreater", "NotGreater", "NotGreaterEqual", "NotGreaterEqual", "NotGreaterLess", "NotGreaterLess", "NotGreaterTilde", "NotGreaterTilde", "NotLeftTriangle", "NotLeftTriangle", "NotLeftTriangleEqual", "NotLeftTriangleEqual", "NotLess", "NotLess", "NotLessEqual", "NotLessEqual", "NotLessGreater", 
7599 "NotLessGreater", "NotLessTilde", "NotLessTilde", "NotPrecedes", "NotPrecedes", "NotPrecedesSlantEqual", "NotPrecedesSlantEqual", "NotReverseElement", "NotReverseElement", "NotRightTriangle", "NotRightTriangle", "NotRightTriangleEqual", "NotRightTriangleEqual", "NotSquareSubsetEqual", "NotSquareSubsetEqual", "NotSquareSupersetEqual", 
7600 "NotSquareSupersetEqual", "NotSubsetEqual", "NotSubsetEqual", "NotSucceeds", "NotSucceeds", "NotSucceedsSlantEqual", "NotSucceedsSlantEqual", "NotSupersetEqual", "NotSupersetEqual", "NotTilde", "NotTilde", "NotTildeEqual", "NotTildeEqual", "NotTildeFullEqual", "NotTildeFullEqual", "NotTildeTilde", "NotTildeTilde", "NotVerticalBar", 
7601 "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", 
7602 "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", 
7603 "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", 
7604 "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", 
7605 "ReverseUpEquilibrium", "ReverseUpEquilibrium", "Rfr", "Rfr", "Rho", "Rho", "RightAngleBracket", "RightAngleBracket", "RightArrow", "RightArrow", "RightArrowBar", "RightArrowBar", "RightArrowLeftArrow", "RightArrowLeftArrow", "RightCeiling", "RightCeiling", "RightDoubleBracket", "RightDoubleBracket", "RightDownTeeVector", 
7606 "RightDownTeeVector", "RightDownVector", "RightDownVector", "RightDownVectorBar", "RightDownVectorBar", "RightFloor", "RightFloor", "RightTee", "RightTee", "RightTeeArrow", "RightTeeArrow", "RightTeeVector", "RightTeeVector", "RightTriangle", "RightTriangle", "RightTriangleBar", "RightTriangleBar", "RightTriangleEqual", 
7607 "RightTriangleEqual", "RightUpDownVector", "RightUpDownVector", "RightUpTeeVector", "RightUpTeeVector", "RightUpVector", "RightUpVector", "RightUpVectorBar", "RightUpVectorBar", "RightVector", "RightVector", "RightVectorBar", "RightVectorBar", "Rightarrow", "Rightarrow", "Ropf", "Ropf", "RoundImplies", "RoundImplies", 
7608 "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", 
7609 "ShortRightArrow", "ShortRightArrow", "ShortUpArrow", "ShortUpArrow", "Sigma", "Sigma", "SmallCircle", "SmallCircle", "Sopf", "Sopf", "Sqrt", "Sqrt", "Square", "Square", "SquareIntersection", "SquareIntersection", "SquareSubset", "SquareSubset", "SquareSubsetEqual", "SquareSubsetEqual", "SquareSuperset", "SquareSuperset", 
7610 "SquareSupersetEqual", "SquareSupersetEqual", "SquareUnion", "SquareUnion", "Sscr", "Sscr", "Star", "Star", "Sub", "Sub", "Subset", "Subset", "SubsetEqual", "SubsetEqual", "Succeeds", "Succeeds", "SucceedsEqual", "SucceedsEqual", "SucceedsSlantEqual", "SucceedsSlantEqual", "SucceedsTilde", "SucceedsTilde", "SuchThat", 
7611 "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", 
7612 "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", 
7613 "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", 
7614 "UpArrowBar", "UpArrowDownArrow", "UpArrowDownArrow", "UpDownArrow", "UpDownArrow", "UpEquilibrium", "UpEquilibrium", "UpTee", "UpTee", "UpTeeArrow", "UpTeeArrow", "Uparrow", "Uparrow", "Updownarrow", "Updownarrow", "UpperLeftArrow", "UpperLeftArrow", "UpperRightArrow", "UpperRightArrow", "Upsi", "Upsi", "Upsilon", "Upsilon", 
7615 "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", 
7616 "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", 
7617 "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", 
7618 "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", 
7619 "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", 
7620 "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", 
7621 "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", 
7622 "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", 
7623 "bigsqcup", "bigsqcup", "bigstar", "bigstar", "bigtriangledown", "bigtriangledown", "bigtriangleup", "bigtriangleup", "biguplus", "biguplus", "bigvee", "bigvee", "bigwedge", "bigwedge", "bkarow", "bkarow", "blacklozenge", "blacklozenge", "blacksquare", "blacksquare", "blacktriangle", "blacktriangle", "blacktriangledown", 
7624 "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", 
7625 "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", 
7626 "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", 
7627 "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", 
7628 "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", 
7629 "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", 
7630 "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", 
7631 "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", 
7632 "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", 
7633 "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", 
7634 "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", 
7635 "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", 
7636 "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", 
7637 "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", 
7638 "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", 
7639 "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", 
7640 "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", 
7641 "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", 
7642 "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", 
7643 "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", 
7644 "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", 
7645 "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", 
7646 "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", 
7647 "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", 
7648 "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", 
7649 "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", 
7650 "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", 
7651 "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", 
7652 "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", 
7653 "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", 
7654 "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", 
7655 "leftarrow", "leftarrowtail", "leftarrowtail", "leftharpoondown", "leftharpoondown", "leftharpoonup", "leftharpoonup", "leftleftarrows", "leftleftarrows", "leftrightarrow", "leftrightarrow", "leftrightarrows", "leftrightarrows", "leftrightharpoons", "leftrightharpoons", "leftrightsquigarrow", "leftrightsquigarrow", "leftthreetimes", 
7656 "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", 
7657 "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", 
7658 "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", 
7659 "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", 
7660 "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", 
7661 "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", 
7662 "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", 
7663 "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", 
7664 "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", 
7665 "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", 
7666 "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", 
7667 "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", 
7668 "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", 
7669 "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", 
7670 "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", 
7671 "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", 
7672 "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", 
7673 "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", 
7674 "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", 
7675 "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", 
7676 "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", 
7677 "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", 
7678 "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", 
7679 "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", 
7680 "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", 
7681 "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", 
7682 "rightharpoonup", "rightharpoonup", "rightleftarrows", "rightleftarrows", "rightleftharpoons", "rightleftharpoons", "rightrightarrows", "rightrightarrows", "rightsquigarrow", "rightsquigarrow", "rightthreetimes", "rightthreetimes", "ring", "ring", "risingdotseq", "risingdotseq", "rlarr", "rlarr", "rlhar", "rlhar", "rlm", 
7683 "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", 
7684 "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", 
7685 "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", 
7686 "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", 
7687 "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", 
7688 "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", 
7689 "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", 
7690 "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", 
7691 "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", 
7692 "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", 
7693 "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", 
7694 "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", 
7695 "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", 
7696 "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", 
7697 "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", 
7698 "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", 
7699 "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", 
7700 "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", 
7701 "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", 
7702 "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", 
7703 "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", 
7704 "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", 
7705 "zeetrf", "zeta", "zeta", "zfr", "zfr", "zhcy", "zhcy", "zigrarr", "zigrarr", "zopf", "zopf", "zscr", "zscr", "zwj", "zwj", "zwnj", "zwnj", ];
7706 
7707 immutable dchar[] availableEntitiesValues =
7708 ['\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', 
7709 '\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', 
7710 '\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', 
7711 '\u2232', '\u2232', '\u201d', '\u201d', '\u2019', '\u2019', '\u2237', '\u2237', '\u2a74', '\u2a74', '\u2261', '\u2261', '\u222f', '\u222f', '\u222e', '\u222e', '\u2102', '\u2102', '\u2210', '\u2210', '\u2233', 
7712 '\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', 
7713 '\u00b4', '\u00b4', '\u02d9', '\u02d9', '\u02dd', '\u02dd', '\u0060', '\u0060', '\u02dc', '\u02dc', '\u22c4', '\u22c4', '\u2146', '\u2146', '\U0001d53b', '\U0001d53b', '\u00a8', '\u00a8', '\u20dc', '\u20dc', '\u2250', 
7714 '\u2250', '\u222f', '\u222f', '\u00a8', '\u00a8', '\u21d3', '\u21d3', '\u21d0', '\u21d0', '\u21d4', '\u21d4', '\u2ae4', '\u2ae4', '\u27f8', '\u27f8', '\u27fa', 
7715 '\u27fa', '\u27f9', '\u27f9', '\u21d2', '\u21d2', '\u22a8', '\u22a8', '\u21d1', '\u21d1', '\u21d5', '\u21d5', '\u2225', '\u2225', '\u2193', '\u2193', '\u2913', '\u2913', 
7716 '\u21f5', '\u21f5', '\u0311', '\u0311', '\u2950', '\u2950', '\u295e', '\u295e', '\u21bd', '\u21bd', '\u2956', '\u2956', '\u295f', '\u295f', '\u21c1', '\u21c1', '\u2957', 
7717 '\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', 
7718 '\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', 
7719 '\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', 
7720 '\u0413', '\u0413', '\u0120', '\u0120', '\U0001d50a', '\U0001d50a', '\u22d9', '\u22d9', '\U0001d53e', '\U0001d53e', '\u2265', '\u2265', '\u22db', '\u22db', '\u2267', '\u2267', '\u2aa2', '\u2aa2', '\u2277', '\u2277', '\u2a7e', '\u2a7e', '\u2273', '\u2273', 
7721 '\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', 
7722 '\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', 
7723 '\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', 
7724 '\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', 
7725 '\u21e4', '\u21c6', '\u21c6', '\u2308', '\u2308', '\u27e6', '\u27e6', '\u2961', '\u2961', '\u21c3', '\u21c3', '\u2959', '\u2959', '\u230a', '\u230a', '\u2194', '\u2194', '\u294e', 
7726 '\u294e', '\u22a3', '\u22a3', '\u21a4', '\u21a4', '\u295a', '\u295a', '\u22b2', '\u22b2', '\u29cf', '\u29cf', '\u22b4', '\u22b4', '\u2951', '\u2951', '\u2960', '\u2960', '\u21bf', '\u21bf', 
7727 '\u2958', '\u2958', '\u21bc', '\u21bc', '\u2952', '\u2952', '\u21d0', '\u21d0', '\u21d4', '\u21d4', '\u22da', '\u22da', '\u2266', '\u2266', '\u2276', '\u2276', '\u2aa1', '\u2aa1', '\u2a7d', '\u2a7d', 
7728 '\u2272', '\u2272', '\U0001d50f', '\U0001d50f', '\u22d8', '\u22d8', '\u21da', '\u21da', '\u013f', '\u013f', '\u27f5', '\u27f5', '\u27f7', '\u27f7', '\u27f6', '\u27f6', '\u27f8', '\u27f8', '\u27fa', '\u27fa', '\u27f9', '\u27f9', 
7729 '\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', 
7730 '\u040a', '\u040a', '\u0143', '\u0143', '\u0147', '\u0147', '\u0145', '\u0145', '\u041d', '\u041d', '\u200b', '\u200b', '\u200b', '\u200b', '\u200b', '\u200b', '\u200b', '\u200b', '\u226b', '\u226b', 
7731 '\u226a', '\u226a', '\u000a', '\u000a', '\U0001d511', '\U0001d511', '\u2060', '\u2060', '\u00a0', '\u00a0', '\u2115', '\u2115', '\u2aec', '\u2aec', '\u2262', '\u2262', '\u226d', '\u226d', '\u2226', '\u2226', '\u2209', '\u2209', '\u2260', '\u2260', 
7732 '\u2204', '\u2204', '\u226f', '\u226f', '\u2271', '\u2271', '\u2279', '\u2279', '\u2275', '\u2275', '\u22ea', '\u22ea', '\u22ec', '\u22ec', '\u226e', '\u226e', '\u2270', '\u2270', '\u2278', 
7733 '\u2278', '\u2274', '\u2274', '\u2280', '\u2280', '\u22e0', '\u22e0', '\u220c', '\u220c', '\u22eb', '\u22eb', '\u22ed', '\u22ed', '\u22e2', '\u22e2', '\u22e3', 
7734 '\u22e3', '\u2288', '\u2288', '\u2281', '\u2281', '\u22e1', '\u22e1', '\u2289', '\u2289', '\u2241', '\u2241', '\u2244', '\u2244', '\u2247', '\u2247', '\u2249', '\u2249', '\u2224', 
7735 '\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', 
7736 '\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', 
7737 '\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', 
7738 '\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', 
7739 '\u296f', '\u296f', '\u211c', '\u211c', '\u03a1', '\u03a1', '\u27e9', '\u27e9', '\u2192', '\u2192', '\u21e5', '\u21e5', '\u21c4', '\u21c4', '\u2309', '\u2309', '\u27e7', '\u27e7', '\u295d', 
7740 '\u295d', '\u21c2', '\u21c2', '\u2955', '\u2955', '\u230b', '\u230b', '\u22a2', '\u22a2', '\u21a6', '\u21a6', '\u295b', '\u295b', '\u22b3', '\u22b3', '\u29d0', '\u29d0', '\u22b5', 
7741 '\u22b5', '\u294f', '\u294f', '\u295c', '\u295c', '\u21be', '\u21be', '\u2954', '\u2954', '\u21c0', '\u21c0', '\u2953', '\u2953', '\u21d2', '\u21d2', '\u211d', '\u211d', '\u2970', '\u2970', 
7742 '\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', 
7743 '\u2192', '\u2192', '\u2191', '\u2191', '\u03a3', '\u03a3', '\u2218', '\u2218', '\U0001d54a', '\U0001d54a', '\u221a', '\u221a', '\u25a1', '\u25a1', '\u2293', '\u2293', '\u228f', '\u228f', '\u2291', '\u2291', '\u2290', '\u2290', 
7744 '\u2292', '\u2292', '\u2294', '\u2294', '\U0001d4ae', '\U0001d4ae', '\u22c6', '\u22c6', '\u22d0', '\u22d0', '\u22d0', '\u22d0', '\u2286', '\u2286', '\u227b', '\u227b', '\u2ab0', '\u2ab0', '\u227d', '\u227d', '\u227f', '\u227f', '\u220b', 
7745 '\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', 
7746 '\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', 
7747 '\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', 
7748 '\u2912', '\u21c5', '\u21c5', '\u2195', '\u2195', '\u296e', '\u296e', '\u22a5', '\u22a5', '\u21a5', '\u21a5', '\u21d1', '\u21d1', '\u21d5', '\u21d5', '\u2196', '\u2196', '\u2197', '\u2197', '\u03d2', '\u03d2', '\u03a5', '\u03a5', 
7749 '\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', 
7750 '\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', 
7751 '\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', 
7752 '\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', 
7753 '\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', 
7754 '\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', 
7755 '\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', 
7756 '\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', 
7757 '\u2a06', '\u2a06', '\u2605', '\u2605', '\u25bd', '\u25bd', '\u25b3', '\u25b3', '\u2a04', '\u2a04', '\u22c1', '\u22c1', '\u22c0', '\u22c0', '\u290d', '\u290d', '\u29eb', '\u29eb', '\u25aa', '\u25aa', '\u25b4', '\u25b4', '\u25be', 
7758 '\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', 
7759 '\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', 
7760 '\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', 
7761 '\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', 
7762 '\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', 
7763 '\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', 
7764 '\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', 
7765 '\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', 
7766 '\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', 
7767 '\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', 
7768 '\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', 
7769 '\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', 
7770 '\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', 
7771 '\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', 
7772 '\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', 
7773 '\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', 
7774 '\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', 
7775 '\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', 
7776 '\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', 
7777 '\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', 
7778 '\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', 
7779 '\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', 
7780 '\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', 
7781 '\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', 
7782 '\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', 
7783 '\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', 
7784 '\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', 
7785 '\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', 
7786 '\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', 
7787 '\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', 
7788 '\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', 
7789 '\u2190', '\u21a2', '\u21a2', '\u21bd', '\u21bd', '\u21bc', '\u21bc', '\u21c7', '\u21c7', '\u2194', '\u2194', '\u21c6', '\u21c6', '\u21cb', '\u21cb', '\u21ad', '\u21ad', '\u22cb', 
7790 '\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', 
7791 '\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', 
7792 '\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', 
7793 '\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', 
7794 '\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', 
7795 '\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', 
7796 '\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', 
7797 '\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', 
7798 '\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', 
7799 '\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', 
7800 '\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', 
7801 '\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', 
7802 '\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', 
7803 '\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', 
7804 '\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', 
7805 '\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', 
7806 '\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', 
7807 '\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', 
7808 '\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', 
7809 '\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', 
7810 '\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', 
7811 '\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', 
7812 '\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', 
7813 '\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', 
7814 '\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', 
7815 '\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', 
7816 '\u21c0', '\u21c0', '\u21c4', '\u21c4', '\u21cc', '\u21cc', '\u21c9', '\u21c9', '\u219d', '\u219d', '\u22cc', '\u22cc', '\u02da', '\u02da', '\u2253', '\u2253', '\u21c4', '\u21c4', '\u21cc', '\u21cc', '\u200f', 
7817 '\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', 
7818 '\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', 
7819 '\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', 
7820 '\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', 
7821 '\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', 
7822 '\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', 
7823 '\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', 
7824 '\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', 
7825 '\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', 
7826 '\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', 
7827 '\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', 
7828 '\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', 
7829 '\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', 
7830 '\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', 
7831 '\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', 
7832 '\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', 
7833 '\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', 
7834 '\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', 
7835 '\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', 
7836 '\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', 
7837 '\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', 
7838 '\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', 
7839 '\u2128', '\u03b6', '\u03b6', '\U0001d537', '\U0001d537', '\u0436', '\u0436', '\u21dd', '\u21dd', '\U0001d56b', '\U0001d56b', '\U0001d4cf', '\U0001d4cf', '\u200d', '\u200d', '\u200c', '\u200c', ];
7840 
7841 
7842 
7843 
7844 
7845 
7846 
7847 
7848 
7849 
7850 
7851 
7852 
7853 
7854 
7855 
7856 
7857 
7858 
7859 
7860 
7861 
7862 
7863 // dom event support, if you want to use it
7864 
7865 /// used for DOM events
7866 version(dom_with_events)
7867 alias EventHandler = void delegate(Element handlerAttachedTo, Event event);
7868 
7869 /// 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.
7870 version(dom_with_events)
7871 class Event {
7872 	this(string eventName, Element target) {
7873 		this.eventName = eventName;
7874 		this.srcElement = target;
7875 	}
7876 
7877 	/// Prevents the default event handler (if there is one) from being called
7878 	void preventDefault() {
7879 		defaultPrevented = true;
7880 	}
7881 
7882 	/// Stops the event propagation immediately.
7883 	void stopPropagation() {
7884 		propagationStopped = true;
7885 	}
7886 
7887 	bool defaultPrevented;
7888 	bool propagationStopped;
7889 	string eventName;
7890 
7891 	Element srcElement;
7892 	alias srcElement target;
7893 
7894 	Element relatedTarget;
7895 
7896 	int clientX;
7897 	int clientY;
7898 
7899 	int button;
7900 
7901 	bool isBubbling;
7902 
7903 	/// this sends it only to the target. If you want propagation, use dispatch() instead.
7904 	void send() {
7905 		if(srcElement is null)
7906 			return;
7907 
7908 		auto e = srcElement;
7909 
7910 		if(eventName in e.bubblingEventHandlers)
7911 		foreach(handler; e.bubblingEventHandlers[eventName])
7912 			handler(e, this);
7913 
7914 		if(!defaultPrevented)
7915 			if(eventName in e.defaultEventHandlers)
7916 				e.defaultEventHandlers[eventName](e, this);
7917 	}
7918 
7919 	/// this dispatches the element using the capture -> target -> bubble process
7920 	void dispatch() {
7921 		if(srcElement is null)
7922 			return;
7923 
7924 		// first capture, then bubble
7925 
7926 		Element[] chain;
7927 		Element curr = srcElement;
7928 		while(curr) {
7929 			auto l = curr;
7930 			chain ~= l;
7931 			curr = curr.parentNode;
7932 
7933 		}
7934 
7935 		isBubbling = false;
7936 
7937 		foreach(e; chain.retro()) {
7938 			if(eventName in e.capturingEventHandlers)
7939 			foreach(handler; e.capturingEventHandlers[eventName])
7940 				handler(e, this);
7941 
7942 			// the default on capture should really be to always do nothing
7943 
7944 			//if(!defaultPrevented)
7945 			//	if(eventName in e.defaultEventHandlers)
7946 			//		e.defaultEventHandlers[eventName](e.element, this);
7947 
7948 			if(propagationStopped)
7949 				break;
7950 		}
7951 
7952 		isBubbling = true;
7953 		if(!propagationStopped)
7954 		foreach(e; chain) {
7955 			if(eventName in e.bubblingEventHandlers)
7956 			foreach(handler; e.bubblingEventHandlers[eventName])
7957 				handler(e, this);
7958 
7959 			if(propagationStopped)
7960 				break;
7961 		}
7962 
7963 		if(!defaultPrevented)
7964 		foreach(e; chain) {
7965 				if(eventName in e.defaultEventHandlers)
7966 					e.defaultEventHandlers[eventName](e, this);
7967 		}
7968 	}
7969 }
7970 
7971 struct FormFieldOptions {
7972 	// usable for any
7973 
7974 	/// this is a regex pattern used to validate the field
7975 	string pattern;
7976 	/// must the field be filled in? Even with a regex, it can be submitted blank if this is false.
7977 	bool isRequired;
7978 	/// this is displayed as an example to the user
7979 	string placeholder;
7980 
7981 	// usable for numeric ones
7982 
7983 
7984 	// convenience methods to quickly get some options
7985 	@property static FormFieldOptions none() {
7986 		FormFieldOptions f;
7987 		return f;
7988 	}
7989 
7990 	static FormFieldOptions required() {
7991 		FormFieldOptions f;
7992 		f.isRequired = true;
7993 		return f;
7994 	}
7995 
7996 	static FormFieldOptions regex(string pattern, bool required = false) {
7997 		FormFieldOptions f;
7998 		f.pattern = pattern;
7999 		f.isRequired = required;
8000 		return f;
8001 	}
8002 
8003 	static FormFieldOptions fromElement(Element e) {
8004 		FormFieldOptions f;
8005 		if(e.hasAttribute("required"))
8006 			f.isRequired = true;
8007 		if(e.hasAttribute("pattern"))
8008 			f.pattern = e.pattern;
8009 		if(e.hasAttribute("placeholder"))
8010 			f.placeholder = e.placeholder;
8011 		return f;
8012 	}
8013 
8014 	Element applyToElement(Element e) {
8015 		if(this.isRequired)
8016 			e.required = "required";
8017 		if(this.pattern.length)
8018 			e.pattern = this.pattern;
8019 		if(this.placeholder.length)
8020 			e.placeholder = this.placeholder;
8021 		return e;
8022 	}
8023 }
8024 
8025 // this needs to look just like a string, but can expand as needed
8026 version(no_dom_stream)
8027 alias string Utf8Stream;
8028 else
8029 class Utf8Stream {
8030 	protected:
8031 		// these two should be overridden in subclasses to actually do the stream magic
8032 		string getMore() {
8033 			if(getMoreHelper !is null)
8034 				return getMoreHelper();
8035 			return null;
8036 		}
8037 
8038 		bool hasMore() {
8039 			if(hasMoreHelper !is null)
8040 				return hasMoreHelper();
8041 			return false;
8042 		}
8043 		// the rest should be ok
8044 
8045 	public:
8046 		this(string d) {
8047 			this.data = d;
8048 		}
8049 
8050 		this(string delegate() getMoreHelper, bool delegate() hasMoreHelper) {
8051 			this.getMoreHelper = getMoreHelper;
8052 			this.hasMoreHelper = hasMoreHelper;
8053 
8054 			if(hasMore())
8055 				this.data ~= getMore();
8056 
8057 			stdout.flush();
8058 		}
8059 
8060 		@property final size_t length() {
8061 			// the parser checks length primarily directly before accessing the next character
8062 			// so this is the place we'll hook to append more if possible and needed.
8063 			if(lastIdx + 1 >= data.length && hasMore()) {
8064 				data ~= getMore();
8065 			}
8066 			return data.length;
8067 		}
8068 
8069 		final char opIndex(size_t idx) {
8070 			if(idx > lastIdx)
8071 				lastIdx = idx;
8072 			return data[idx];
8073 		}
8074 
8075 		final string opSlice(size_t start, size_t end) {
8076 			if(end > lastIdx)
8077 				lastIdx = end;
8078 			return data[start .. end];
8079 		}
8080 
8081 		final size_t opDollar() {
8082 			return length();
8083 		}
8084 
8085 		final Utf8Stream opBinary(string op : "~")(string s) {
8086 			this.data ~= s;
8087 			return this;
8088 		}
8089 
8090 		final Utf8Stream opOpAssign(string op : "~")(string s) {
8091 			this.data ~= s;
8092 			return this;
8093 		}
8094 
8095 		final Utf8Stream opAssign(string rhs) {
8096 			this.data = rhs;
8097 			return this;
8098 		}
8099 	private:
8100 		string data;
8101 
8102 		size_t lastIdx;
8103 
8104 		bool delegate() hasMoreHelper;
8105 		string delegate() getMoreHelper;
8106 
8107 
8108 		/+
8109 		// used to maybe clear some old stuff
8110 		// you might have to remove elements parsed with it too since they can hold slices into the
8111 		// old stuff, preventing gc
8112 		void dropFront(int bytes) {
8113 			posAdjustment += bytes;
8114 			data = data[bytes .. $];
8115 		}
8116 
8117 		int posAdjustment;
8118 		+/
8119 }
8120 
8121 void fillForm(T)(Form form, T obj, string name) { 
8122 	import arsd.database; 
8123 	fillData((k, v) => form.setValue(k, v), obj, name); 
8124 } 
8125 
8126 /++
8127 	Normalizes the whitespace in the given text according to HTML rules.
8128 
8129 	History:
8130 		Added March 25, 2022 (dub v10.8)
8131 +/
8132 string normalizeWhitespace(string text) {
8133 	string ret;
8134 	ret.reserve(text.length);
8135 	bool lastWasWhite = true;
8136 	foreach(char ch; text) {
8137 		if(ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r') {
8138 			if(lastWasWhite)
8139 				continue;
8140 			lastWasWhite = true;
8141 			ch = ' ';
8142 		} else {
8143 			lastWasWhite = false;
8144 		}
8145 
8146 		ret ~= ch;
8147 	}
8148 
8149 	return ret.stripRight;
8150 }
8151 
8152 unittest {
8153 	assert(normalizeWhitespace("    foo   ") == "foo");
8154 	assert(normalizeWhitespace("    f\n \t oo   ") == "f oo");
8155 }
8156 
8157 unittest {
8158 	Document document;
8159 
8160 	document = new Document("<test> foo \r </test>");
8161 	assert(document.root.visibleText == "foo");
8162 
8163 	document = new Document("<test> foo \r <br>hi</test>");
8164 	assert(document.root.visibleText == "foo\nhi");
8165 
8166 	document = new Document("<test> foo \r <br>hi<pre>hi\nthere\n    indent<br />line</pre></test>");
8167 	assert(document.root.visibleText == "foo\nhihi\nthere\n    indent\nline", document.root.visibleText);
8168 }
8169 
8170 /+
8171 /+
8172 Syntax:
8173 
8174 Tag: tagname#id.class
8175 Tree: Tag(Children, comma, separated...)
8176 Children: Tee or Variable
8177 Variable: $varname with optional |funcname following.
8178 
8179 If a variable has a tree after it, it breaks the variable down:
8180 	* if array, foreach it does the tree
8181 	* if struct, it breaks down the member variables
8182 
8183 stolen from georgy on irc, see: https://github.com/georgy7/stringplate
8184 +/
8185 struct Stringplate {
8186 	/++
8187 
8188 	+/
8189 	this(string s) {
8190 
8191 	}
8192 
8193 	/++
8194 
8195 	+/
8196 	Element expand(T...)(T vars) {
8197 		return null;
8198 	}
8199 }
8200 ///
8201 unittest {
8202 	auto stringplate = Stringplate("#bar(.foo($foo), .baz($baz))");
8203 	assert(stringplate.expand.innerHTML == `<div id="bar"><div class="foo">$foo</div><div class="baz">$baz</div></div>`);
8204 }
8205 +/
8206 
8207 bool allAreInlineHtml(const(Element)[] children, const string[] inlineElements) {
8208 	foreach(child; children) {
8209 		if(child.nodeType == NodeType.Text && child.nodeValue.strip.length) {
8210 			// cool
8211 		} else if(child.tagName.isInArray(inlineElements) && allAreInlineHtml(child.children, inlineElements)) {
8212 			// cool, this is an inline element and none of its children contradict that
8213 		} else {
8214 			// prolly block
8215 			return false;
8216 		}
8217 	}
8218 	return true;
8219 }
8220 
8221 private bool isSimpleWhite(dchar c) {
8222 	return c == ' ' || c == '\r' || c == '\n' || c == '\t';
8223 }
8224 
8225 unittest {
8226 	// Test for issue #120
8227 	string s = `<html>
8228 	<body>
8229 		<P>AN
8230 		<P>bubbles</P>
8231 		<P>giggles</P>
8232 	</body>
8233 </html>`;
8234 	auto doc = new Document();
8235 	doc.parseUtf8(s, false, false);
8236 	auto s2 = doc.toString();
8237 	assert(
8238 			s2.indexOf("bubbles") < s2.indexOf("giggles"),
8239 			"paragraph order incorrect:\n" ~ s2);
8240 }
8241 
8242 unittest {
8243 	// test for suncarpet email dec 24 2019
8244 	// arbitrary id asduiwh
8245 	auto document = new Document("<html>
8246         <head>
8247                 <meta charset=\"utf-8\"></meta>
8248                 <title>Element.querySelector Test</title>
8249         </head>
8250         <body>
8251                 <div id=\"foo\">
8252                         <div>Foo</div>
8253                         <div>Bar</div>
8254                 </div>
8255 		<div id=\"empty\"></div>
8256 		<div id=\"empty-but-text\">test</div>
8257         </body>
8258 </html>");
8259 
8260 	auto doc = document;
8261 
8262 	{
8263 	auto empty = doc.requireElementById("empty");
8264 	assert(empty.querySelector(" > *") is null, empty.querySelector(" > *").toString);
8265 	}
8266 	{
8267 	auto empty = doc.requireElementById("empty-but-text");
8268 	assert(empty.querySelector(" > *") is null, empty.querySelector(" > *").toString);
8269 	}
8270 
8271 	assert(doc.querySelectorAll("div div").length == 2);
8272 	assert(doc.querySelector("div").querySelectorAll("div").length == 2);
8273 	assert(doc.querySelectorAll("> html").length == 0);
8274 	assert(doc.querySelector("head").querySelectorAll("> title").length == 1);
8275 	assert(doc.querySelector("head").querySelectorAll("> meta[charset]").length == 1);
8276 
8277 
8278 	assert(doc.root.matches("html"));
8279 	assert(!doc.root.matches("nothtml"));
8280 	assert(doc.querySelector("#foo > div").matches("div"));
8281 	assert(doc.querySelector("body > #foo").matches("#foo"));
8282 
8283 	assert(doc.root.querySelectorAll(":root > body").length == 0); // the root has no CHILD root!
8284 	assert(doc.querySelectorAll(":root > body").length == 1); // but the DOCUMENT does
8285 	assert(doc.querySelectorAll(" > body").length == 1); //  should mean the same thing
8286 	assert(doc.root.querySelectorAll(" > body").length == 1); // the root of HTML has this
8287 	assert(doc.root.querySelectorAll(" > html").length == 0); // but not this
8288 
8289 	// also confirming the querySelector works via the mdn definition
8290 	auto foo = doc.requireSelector("#foo");
8291 	assert(foo.querySelector("#foo > div") !is null);
8292 	assert(foo.querySelector("body #foo > div") !is null);
8293 
8294 	// this is SUPPOSED to work according to the spec but never has in dom.d since it limits the scope.
8295 	// the new css :scope thing is designed to bring this in. and meh idk if i even care.
8296 	//assert(foo.querySelectorAll("#foo > div").length == 2);
8297 }
8298 
8299 unittest {
8300 	// based on https://developer.mozilla.org/en-US/docs/Web/API/Element/closest example
8301 	auto document = new Document(`<article>
8302   <div id="div-01">Here is div-01
8303     <div id="div-02">Here is div-02
8304       <div id="div-03">Here is div-03</div>
8305     </div>
8306   </div>
8307 </article>`, true, true);
8308 
8309 	auto el = document.getElementById("div-03");
8310 	assert(el.closest("#div-02").id == "div-02");
8311 	assert(el.closest("div div").id == "div-03");
8312 	assert(el.closest("article > div").id == "div-01");
8313 	assert(el.closest(":not(div)").tagName == "article");
8314 
8315 	assert(el.closest("p") is null);
8316 	assert(el.closest("p, div") is el);
8317 }
8318 
8319 unittest {
8320 	// https://developer.mozilla.org/en-US/docs/Web/CSS/:is
8321 	auto document = new Document(`<test>
8322 		<div class="foo"><p>cool</p><span>bar</span></div>
8323 		<main><p>two</p></main>
8324 	</test>`);
8325 
8326 	assert(document.querySelectorAll(":is(.foo, main) p").length == 2);
8327 	assert(document.querySelector("div:where(.foo)") !is null);
8328 }
8329 
8330 unittest {
8331 immutable string html = q{
8332 <root>
8333 <div class="roundedbox">
8334  <table>
8335   <caption class="boxheader">Recent Reviews</caption>
8336   <tr>
8337    <th>Game</th>
8338    <th>User</th>
8339    <th>Rating</th>
8340    <th>Created</th>
8341   </tr>
8342 
8343   <tr>
8344    <td>June 13, 2020 15:10</td>
8345    <td><a href="/reviews/8833">[Show]</a></td>
8346   </tr>
8347 
8348   <tr>
8349    <td>June 13, 2020 15:02</td>
8350    <td><a href="/reviews/8832">[Show]</a></td>
8351   </tr>
8352 
8353   <tr>
8354    <td>June 13, 2020 14:41</td>
8355    <td><a href="/reviews/8831">[Show]</a></td>
8356   </tr>
8357  </table>
8358 </div>
8359 </root>
8360 };
8361 
8362   auto doc = new Document(cast(string)html);
8363   // this should select the second table row, but...
8364   auto rd = doc.root.querySelector(`div.roundedbox > table > caption.boxheader + tr + tr + tr > td > a[href^=/reviews/]`);
8365   assert(rd !is null);
8366   assert(rd.href == "/reviews/8832");
8367 
8368   rd = doc.querySelector(`div.roundedbox > table > caption.boxheader + tr + tr + tr > td > a[href^=/reviews/]`);
8369   assert(rd !is null);
8370   assert(rd.href == "/reviews/8832");
8371 }
8372 
8373 unittest {
8374 	try {
8375 		auto doc = new XmlDocument("<testxmlns:foo=\"/\"></test>");
8376 		assert(0);
8377 	} catch(Exception e) {
8378 		// good; it should throw an exception, not an error.
8379 	}
8380 }
8381 
8382 unittest {
8383 	// toPrettyString is not stable, but these are some best-effort attempts
8384 	// despite these being in a test, I might change these anyway!
8385 	assert(Element.make("a").toPrettyString == "<a></a>");
8386 	assert(Element.make("a", "").toPrettyString(false, 0, " ") == "<a></a>");
8387 	assert(Element.make("a", " ").toPrettyString(false, 0, " ") == "<a> </a>");//, Element.make("a", " ").toPrettyString(false, 0, " "));
8388 	assert(Element.make("a", "b").toPrettyString == "<a>b</a>");
8389 	assert(Element.make("a", "b").toPrettyString(false, 0, "") == "<a>b</a>");
8390 
8391 	{
8392 	auto document = new Document("<html><body><p>hello <a href=\"world\">world</a></p></body></html>");
8393 	auto pretty = document.toPrettyString(false, 0, "  ");
8394 	assert(pretty ==
8395 `<!DOCTYPE html>
8396 <html>
8397   <body>
8398     <p>hello <a href="world">world</a></p>
8399   </body>
8400 </html>`, pretty);
8401 	}
8402 
8403 	{
8404 	auto document = new XmlDocument("<html><body><p>hello <a href=\"world\">world</a></p></body></html>");
8405 	assert(document.toPrettyString(false, 0, "  ") ==
8406 `<?xml version="1.0" encoding="UTF-8"?>
8407 <html>
8408   <body>
8409     <p>
8410       hello
8411       <a href="world">world</a>
8412     </p>
8413   </body>
8414 </html>`);
8415 	}
8416 
8417 	foreach(test; [
8418 		"<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>",
8419 		"<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>",
8420 	] )
8421 	{
8422 	auto document = new XmlDocument(test);
8423 	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>");
8424 	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>");
8425 	auto omg = document.root;
8426 	omg.parent_ = null;
8427 	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>");
8428 	}
8429 
8430 	{
8431 	auto document = new XmlDocument(`<a><b>toto</b><c></c></a>`);
8432 	assert(document.root.toPrettyString(false, 0, null) == `<a><b>toto</b><c></c></a>`);
8433 	assert(document.root.toPrettyString(false, 0, " ") == `<a>
8434  <b>toto</b>
8435  <c></c>
8436 </a>`);
8437 	}
8438 
8439 	{
8440 auto str = `<!DOCTYPE html>
8441 <html>
8442 	<head>
8443 		<title>Test</title>
8444 	</head>
8445 	<body>
8446 		<p>Hello there</p>
8447 		<p>I like <a href="">Links</a></p>
8448 		<div>
8449 			this is indented since there's a block inside
8450 			<p>this is the block</p>
8451 			and this gets its own line
8452 		</div>
8453 	</body>
8454 </html>`;
8455 		auto doc = new Document(str, true, true);
8456 		assert(doc.toPrettyString == str);
8457 	}
8458 
8459 }
8460 
8461 unittest {
8462 	auto document = new Document("broken"); // just ensuring it doesn't crash
8463 }
8464 
8465 /*
8466 Copyright: Adam D. Ruppe, 2010 - 2022
8467 License:   <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
8468 Authors: Adam D. Ruppe, with contributions by Nick Sabalausky, Trass3r, and ketmar among others
8469 
8470         Copyright Adam D. Ruppe 2010-2022.
8471 Distributed under the Boost Software License, Version 1.0.
8472    (See accompanying file LICENSE_1_0.txt or copy at
8473         http://www.boost.org/LICENSE_1_0.txt)
8474 */
8475 
8476