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