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