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