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