1 // FIXME: xml namespace support??? 2 // FIXME: https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML 3 // FIXME: parentElement is parentNode that skips DocumentFragment etc but will be hard to work in with my compatibility... 4 5 // FIXME: the scriptable list is quite arbitrary 6 7 8 // xml entity references?! 9 10 /++ 11 This is an html DOM implementation, started with cloning 12 what the browser offers in Javascript, but going well beyond 13 it in convenience. 14 15 If you can do it in Javascript, you can probably do it with 16 this module, and much more. 17 18 --- 19 import arsd.dom; 20 21 void main() { 22 auto document = new Document("<html><p>paragraph</p></html>"); 23 writeln(document.querySelector("p")); 24 document.root.innerHTML = "<p>hey</p>"; 25 writeln(document); 26 } 27 --- 28 29 BTW: this file optionally depends on `arsd.characterencodings`, to 30 help it correctly read files from the internet. You should be able to 31 get characterencodings.d from the same place you got this file. 32 33 If you want it to stand alone, just always use the `Document.parseUtf8` 34 function or the constructor that takes a string. 35 36 Symbol_groups: 37 38 core_functionality = 39 40 These members provide core functionality. The members on these classes 41 will provide most your direct interaction. 42 43 bonus_functionality = 44 45 These provide additional functionality for special use cases. 46 47 implementations = 48 49 These provide implementations of other functionality. 50 +/ 51 module arsd.dom; 52 53 // FIXME: support the css standard namespace thing in the selectors too 54 55 version(with_arsd_jsvar) 56 import arsd.jsvar; 57 else { 58 enum scriptable = "arsd_jsvar_compatible"; 59 } 60 61 // this is only meant to be used at compile time, as a filter for opDispatch 62 // lists the attributes we want to allow without the use of .attr 63 bool isConvenientAttribute(string name) { 64 static immutable list = [ 65 "name", "id", "href", "value", 66 "checked", "selected", "type", 67 "src", "content", "pattern", 68 "placeholder", "required", "alt", 69 "rel", 70 "method", "action", "enctype" 71 ]; 72 foreach(l; list) 73 if(name == l) return true; 74 return false; 75 } 76 77 78 // FIXME: something like <ol>spam <ol> with no closing </ol> should read the second tag as the closer in garbage mode 79 // FIXME: failing to close a paragraph sometimes messes things up too 80 81 // FIXME: it would be kinda cool to have some support for internal DTDs 82 // and maybe XPath as well, to some extent 83 /* 84 we could do 85 meh this sux 86 87 auto xpath = XPath(element); 88 89 // get the first p 90 xpath.p[0].a["href"] 91 */ 92 93 94 /++ 95 The main document interface, including a html or xml parser. 96 97 There's three main ways to create a Document: 98 99 If you want to parse something and inspect the tags, you can use the [this|constructor]: 100 --- 101 // create and parse some HTML in one call 102 auto document = new Document("<html></html>"); 103 104 // or some XML 105 auto document = new Document("<xml></xml>", true, true); // strict mode enabled 106 107 // or better yet: 108 auto document = new XmlDocument("<xml></xml>"); // specialized subclass 109 --- 110 111 If you want to download something and parse it in one call, the [fromUrl] static function can help: 112 --- 113 auto document = Document.fromUrl("http://dlang.org/"); 114 --- 115 (note that this requires my [arsd.characterencodings] and [arsd.http2] libraries) 116 117 And, if you need to inspect things like `<%= foo %>` tags and comments, you can add them to the dom like this, with the [enableAddingSpecialTagsToDom] 118 and [parseUtf8] or [parseGarbage] functions: 119 --- 120 auto document = new Document(); 121 document.enableAddingSpecialTagsToDom(); 122 document.parseUtf8("<example></example>", true, true); // changes the trues to false to switch from xml to html mode 123 --- 124 125 However you parse it, it will put a few things into special variables. 126 127 [root] contains the root document. 128 [prolog] contains the instructions before the root (like `<!DOCTYPE html>`). To keep the original things, you will need to [enableAddingSpecialTagsToDom] first, otherwise the library will return generic strings in there. [piecesBeforeRoot] will have other parsed instructions, if [enableAddingSpecialTagsToDom] is called. 129 [piecesAfterRoot] will contain any xml-looking data after the root tag is closed. 130 131 Most often though, you will not need to look at any of that data, since `Document` itself has methods like [querySelector], [appendChild], and more which will forward to the root [Element] for you. 132 +/ 133 /// Group: core_functionality 134 class Document : FileResource, DomParent { 135 inout(Document) asDocument() inout { return this; } 136 inout(Element) asElement() inout { return null; } 137 138 /++ 139 Convenience method for web scraping. Requires [arsd.http2] to be 140 included in the build as well as [arsd.characterencodings]. 141 142 This will download the file from the given url and create a document 143 off it, using a strict constructor or a [parseGarbage], depending on 144 the value of `strictMode`. 145 +/ 146 static Document fromUrl()(string url, bool strictMode = false) { 147 import arsd.http2; 148 auto client = new HttpClient(); 149 150 auto req = client.navigateTo(Uri(url), HttpVerb.GET); 151 auto res = req.waitForCompletion(); 152 153 auto document = new Document(); 154 if(strictMode) { 155 document.parse(cast(string) res.content, true, true, res.contentTypeCharset); 156 } else { 157 document.parseGarbage(cast(string) res.content); 158 } 159 160 return document; 161 } 162 163 /++ 164 Creates a document with the given source data. If you want HTML behavior, use `caseSensitive` and `struct` set to `false`. For XML mode, set them to `true`. 165 166 Please note that anything after the root element will be found in [piecesAfterRoot]. Comments, processing instructions, and other special tags will be stripped out b default. You can customize this by using the zero-argument constructor and setting callbacks on the [parseSawComment], [parseSawBangInstruction], [parseSawAspCode], [parseSawPhpCode], and [parseSawQuestionInstruction] members, then calling one of the [parseUtf8], [parseGarbage], or [parse] functions. Calling the convenience method, [enableAddingSpecialTagsToDom], will enable all those things at once. 167 168 See_Also: 169 [parseGarbage] 170 [parseUtf8] 171 [parseUrl] 172 +/ 173 this(string data, bool caseSensitive = false, bool strict = false) { 174 parseUtf8(data, caseSensitive, strict); 175 } 176 177 /** 178 Creates an empty document. It has *nothing* in it at all, ready. 179 */ 180 this() { 181 182 } 183 184 /++ 185 This is just something I'm toying with. Right now, you use opIndex to put in css selectors. 186 It returns a struct that forwards calls to all elements it holds, and returns itself so you 187 can chain it. 188 189 Example: document["p"].innerText("hello").addClass("modified"); 190 191 Equivalent to: foreach(e; document.getElementsBySelector("p")) { e.innerText("hello"); e.addClas("modified"); } 192 193 Note: always use function calls (not property syntax) and don't use toString in there for best results. 194 195 You can also do things like: document["p"]["b"] though tbh I'm not sure why since the selector string can do all that anyway. Maybe 196 you could put in some kind of custom filter function tho. 197 +/ 198 ElementCollection opIndex(string selector) { 199 auto e = ElementCollection(this.root); 200 return e[selector]; 201 } 202 203 string _contentType = "text/html; charset=utf-8"; 204 205 /// If you're using this for some other kind of XML, you can 206 /// set the content type here. 207 /// 208 /// Note: this has no impact on the function of this class. 209 /// It is only used if the document is sent via a protocol like HTTP. 210 /// 211 /// This may be called by parse() if it recognizes the data. Otherwise, 212 /// if you don't set it, it assumes text/html; charset=utf-8. 213 @property string contentType(string mimeType) { 214 _contentType = mimeType; 215 return _contentType; 216 } 217 218 /// implementing the FileResource interface, useful for sending via 219 /// http automatically. 220 @property string filename() const { return null; } 221 222 /// implementing the FileResource interface, useful for sending via 223 /// http automatically. 224 override @property string contentType() const { 225 return _contentType; 226 } 227 228 /// implementing the FileResource interface; it calls toString. 229 override immutable(ubyte)[] getData() const { 230 return cast(immutable(ubyte)[]) this.toString(); 231 } 232 233 234 /* 235 /// Concatenates any consecutive text nodes 236 void normalize() { 237 238 } 239 */ 240 241 /// This will set delegates for parseSaw* (note: this overwrites anything else you set, and you setting subsequently will overwrite this) that add those things to the dom tree when it sees them. 242 /// Call this before calling parse(). 243 244 /++ 245 Adds objects to the dom representing things normally stripped out during the default parse, like comments, `<!instructions>`, `<% code%>`, and `<? code?>` all at once. 246 247 Note this will also preserve the prolog and doctype from the original file, if there was one. 248 249 See_Also: 250 [parseSawComment] 251 [parseSawAspCode] 252 [parseSawPhpCode] 253 [parseSawQuestionInstruction] 254 [parseSawBangInstruction] 255 +/ 256 void enableAddingSpecialTagsToDom() { 257 parseSawComment = (string) => true; 258 parseSawAspCode = (string) => true; 259 parseSawPhpCode = (string) => true; 260 parseSawQuestionInstruction = (string) => true; 261 parseSawBangInstruction = (string) => true; 262 } 263 264 /// If the parser sees a html comment, it will call this callback 265 /// <!-- comment --> will call parseSawComment(" comment ") 266 /// Return true if you want the node appended to the document. It will be in a [HtmlComment] object. 267 bool delegate(string) parseSawComment; 268 269 /// If the parser sees <% asp code... %>, it will call this callback. 270 /// It will be passed "% asp code... %" or "%= asp code .. %" 271 /// Return true if you want the node appended to the document. It will be in an [AspCode] object. 272 bool delegate(string) parseSawAspCode; 273 274 /// If the parser sees <?php php code... ?>, it will call this callback. 275 /// It will be passed "?php php code... ?" or "?= asp code .. ?" 276 /// Note: dom.d cannot identify the other php <? code ?> short format. 277 /// Return true if you want the node appended to the document. It will be in a [PhpCode] object. 278 bool delegate(string) parseSawPhpCode; 279 280 /// if it sees a <?xxx> that is not php or asp 281 /// it calls this function with the contents. 282 /// <?SOMETHING foo> calls parseSawQuestionInstruction("?SOMETHING foo") 283 /// Unlike the php/asp ones, this ends on the first > it sees, without requiring ?>. 284 /// Return true if you want the node appended to the document. It will be in a [QuestionInstruction] object. 285 bool delegate(string) parseSawQuestionInstruction; 286 287 /// if it sees a <! that is not CDATA or comment (CDATA is handled automatically and comments call parseSawComment), 288 /// it calls this function with the contents. 289 /// <!SOMETHING foo> calls parseSawBangInstruction("SOMETHING foo") 290 /// Return true if you want the node appended to the document. It will be in a [BangInstruction] object. 291 bool delegate(string) parseSawBangInstruction; 292 293 /// Given the kind of garbage you find on the Internet, try to make sense of it. 294 /// Equivalent to document.parse(data, false, false, null); 295 /// (Case-insensitive, non-strict, determine character encoding from the data.) 296 297 /// NOTE: this makes no attempt at added security, but it will try to recover from anything instead of throwing. 298 /// 299 /// It is a template so it lazily imports characterencodings. 300 void parseGarbage()(string data) { 301 parse(data, false, false, null); 302 } 303 304 /// Parses well-formed UTF-8, case-sensitive, XML or XHTML 305 /// Will throw exceptions on things like unclosed tags. 306 void parseStrict(string data) { 307 parseStream(toUtf8Stream(data), true, true); 308 } 309 310 /// Parses well-formed UTF-8 in loose mode (by default). Tries to correct 311 /// tag soup, but does NOT try to correct bad character encodings. 312 /// 313 /// They will still throw an exception. 314 void parseUtf8(string data, bool caseSensitive = false, bool strict = false) { 315 parseStream(toUtf8Stream(data), caseSensitive, strict); 316 } 317 318 // this is a template so we get lazy import behavior 319 Utf8Stream handleDataEncoding()(in string rawdata, string dataEncoding, bool strict) { 320 import arsd.characterencodings; 321 // gotta determine the data encoding. If you know it, pass it in above to skip all this. 322 if(dataEncoding is null) { 323 dataEncoding = tryToDetermineEncoding(cast(const(ubyte[])) rawdata); 324 // it can't tell... probably a random 8 bit encoding. Let's check the document itself. 325 // Now, XML and HTML can both list encoding in the document, but we can't really parse 326 // it here without changing a lot of code until we know the encoding. So I'm going to 327 // do some hackish string checking. 328 if(dataEncoding is null) { 329 auto dataAsBytes = cast(immutable(ubyte)[]) rawdata; 330 // first, look for an XML prolog 331 auto idx = indexOfBytes(dataAsBytes, cast(immutable ubyte[]) "encoding=\""); 332 if(idx != -1) { 333 idx += "encoding=\"".length; 334 // we're probably past the prolog if it's this far in; we might be looking at 335 // content. Forget about it. 336 if(idx > 100) 337 idx = -1; 338 } 339 // if that fails, we're looking for Content-Type http-equiv or a meta charset (see html5).. 340 if(idx == -1) { 341 idx = indexOfBytes(dataAsBytes, cast(immutable ubyte[]) "charset="); 342 if(idx != -1) { 343 idx += "charset=".length; 344 if(dataAsBytes[idx] == '"') 345 idx++; 346 } 347 } 348 349 // found something in either branch... 350 if(idx != -1) { 351 // read till a quote or about 12 chars, whichever comes first... 352 auto end = idx; 353 while(end < dataAsBytes.length && dataAsBytes[end] != '"' && end - idx < 12) 354 end++; 355 356 dataEncoding = cast(string) dataAsBytes[idx .. end]; 357 } 358 // otherwise, we just don't know. 359 } 360 } 361 362 if(dataEncoding is null) { 363 if(strict) 364 throw new MarkupException("I couldn't figure out the encoding of this document."); 365 else 366 // if we really don't know by here, it means we already tried UTF-8, 367 // looked for utf 16 and 32 byte order marks, and looked for xml or meta 368 // tags... let's assume it's Windows-1252, since that's probably the most 369 // common aside from utf that wouldn't be labeled. 370 371 dataEncoding = "Windows 1252"; 372 } 373 374 // and now, go ahead and convert it. 375 376 string data; 377 378 if(!strict) { 379 // if we're in non-strict mode, we need to check 380 // the document for mislabeling too; sometimes 381 // web documents will say they are utf-8, but aren't 382 // actually properly encoded. If it fails to validate, 383 // we'll assume it's actually Windows encoding - the most 384 // likely candidate for mislabeled garbage. 385 dataEncoding = dataEncoding.toLower(); 386 dataEncoding = dataEncoding.replace(" ", ""); 387 dataEncoding = dataEncoding.replace("-", ""); 388 dataEncoding = dataEncoding.replace("_", ""); 389 if(dataEncoding == "utf8") { 390 try { 391 validate(rawdata); 392 } catch(UTFException e) { 393 dataEncoding = "Windows 1252"; 394 } 395 } 396 } 397 398 if(dataEncoding != "UTF-8") { 399 if(strict) 400 data = convertToUtf8(cast(immutable(ubyte)[]) rawdata, dataEncoding); 401 else { 402 try { 403 data = convertToUtf8(cast(immutable(ubyte)[]) rawdata, dataEncoding); 404 } catch(Exception e) { 405 data = convertToUtf8(cast(immutable(ubyte)[]) rawdata, "Windows 1252"); 406 } 407 } 408 } else 409 data = rawdata; 410 411 return toUtf8Stream(data); 412 } 413 414 private 415 Utf8Stream toUtf8Stream(in string rawdata) { 416 string data = rawdata; 417 static if(is(Utf8Stream == string)) 418 return data; 419 else 420 return new Utf8Stream(data); 421 } 422 423 /++ 424 List of elements that can be assumed to be self-closed 425 in this document. The default for a Document are a hard-coded 426 list of ones appropriate for HTML. For [XmlDocument], it defaults 427 to empty. You can modify this after construction but before parsing. 428 429 History: 430 Added February 8, 2021 (included in dub release 9.2) 431 +/ 432 string[] selfClosedElements = htmlSelfClosedElements; 433 434 /++ 435 List of elements that are considered inline for pretty printing. 436 The default for a Document are hard-coded to something appropriate 437 for HTML. For [XmlDocument], it defaults to empty. You can modify 438 this after construction but before parsing. 439 440 History: 441 Added June 21, 2021 (included in dub release 10.1) 442 +/ 443 string[] inlineElements = htmlInlineElements; 444 445 /** 446 Take XMLish data and try to make the DOM tree out of it. 447 448 The goal isn't to be perfect, but to just be good enough to 449 approximate Javascript's behavior. 450 451 If strict, it throws on something that doesn't make sense. 452 (Examples: mismatched tags. It doesn't validate!) 453 If not strict, it tries to recover anyway, and only throws 454 when something is REALLY unworkable. 455 456 If strict is false, it uses a magic list of tags that needn't 457 be closed. If you are writing a document specifically for this, 458 try to avoid such - use self closed tags at least. Easier to parse. 459 460 The dataEncoding argument can be used to pass a specific 461 charset encoding for automatic conversion. If null (which is NOT 462 the default!), it tries to determine from the data itself, 463 using the xml prolog or meta tags, and assumes UTF-8 if unsure. 464 465 If this assumption is wrong, it can throw on non-ascii 466 characters! 467 468 469 Note that it previously assumed the data was encoded as UTF-8, which 470 is why the dataEncoding argument defaults to that. 471 472 So it shouldn't break backward compatibility. 473 474 But, if you want the best behavior on wild data - figuring it out from the document 475 instead of assuming - you'll probably want to change that argument to null. 476 477 This is a template so it lazily imports arsd.characterencodings, which is required 478 to fix up data encodings. 479 480 If you are sure the encoding is good, try parseUtf8 or parseStrict to avoid the 481 dependency. If it is data from the Internet though, a random website, the encoding 482 is often a lie. This function, if dataEncoding == null, can correct for that, or 483 you can try parseGarbage. In those cases, arsd.characterencodings is required to 484 compile. 485 */ 486 void parse()(in string rawdata, bool caseSensitive = false, bool strict = false, string dataEncoding = "UTF-8") { 487 auto data = handleDataEncoding(rawdata, dataEncoding, strict); 488 parseStream(data, caseSensitive, strict); 489 } 490 491 // note: this work best in strict mode, unless data is just a simple string wrapper 492 void parseStream(Utf8Stream data, bool caseSensitive = false, bool strict = false) { 493 // FIXME: this parser could be faster; it's in the top ten biggest tree times according to the profiler 494 // of my big app. 495 496 assert(data !is null); 497 498 // go through character by character. 499 // if you see a <, consider it a tag. 500 // name goes until the first non tagname character 501 // then see if it self closes or has an attribute 502 503 // if not in a tag, anything not a tag is a big text 504 // node child. It ends as soon as it sees a < 505 506 // Whitespace in text or attributes is preserved, but not between attributes 507 508 // & and friends are converted when I know them, left the same otherwise 509 510 511 // this it should already be done correctly.. so I'm leaving it off to net a ~10% speed boost on my typical test file (really) 512 //validate(data); // it *must* be UTF-8 for this to work correctly 513 514 sizediff_t pos = 0; 515 516 clear(); 517 518 loose = !caseSensitive; 519 520 bool sawImproperNesting = false; 521 bool paragraphHackfixRequired = false; 522 523 int getLineNumber(sizediff_t p) { 524 int line = 1; 525 foreach(c; data[0..p]) 526 if(c == '\n') 527 line++; 528 return line; 529 } 530 531 void parseError(string message) { 532 throw new MarkupException(format("char %d (line %d): %s", pos, getLineNumber(pos), message)); 533 } 534 535 bool eatWhitespace() { 536 bool ateAny = false; 537 while(pos < data.length && data[pos].isSimpleWhite) { 538 pos++; 539 ateAny = true; 540 } 541 return ateAny; 542 } 543 544 string readTagName() { 545 // remember to include : for namespaces 546 // basically just keep going until >, /, or whitespace 547 auto start = pos; 548 while(data[pos] != '>' && data[pos] != '/' && !data[pos].isSimpleWhite) 549 { 550 pos++; 551 if(pos == data.length) { 552 if(strict) 553 throw new Exception("tag name incomplete when file ended"); 554 else 555 break; 556 } 557 } 558 559 if(!caseSensitive) 560 return toLower(data[start..pos]); 561 else 562 return data[start..pos]; 563 } 564 565 string readAttributeName() { 566 // remember to include : for namespaces 567 // basically just keep going until >, /, or whitespace 568 auto start = pos; 569 while(data[pos] != '>' && data[pos] != '/' && data[pos] != '=' && !data[pos].isSimpleWhite) 570 { 571 if(data[pos] == '<') { 572 if(strict) 573 throw new MarkupException("The character < can never appear in an attribute name. Line " ~ to!string(getLineNumber(pos))); 574 else 575 break; // e.g. <a href="something" <img src="poo" /></a>. The > should have been after the href, but some shitty files don't do that right and the browser handles it, so we will too, by pretending the > was indeed there 576 } 577 pos++; 578 if(pos == data.length) { 579 if(strict) 580 throw new Exception("unterminated attribute name"); 581 else 582 break; 583 } 584 } 585 586 if(!caseSensitive) 587 return toLower(data[start..pos]); 588 else 589 return data[start..pos]; 590 } 591 592 string readAttributeValue() { 593 if(pos >= data.length) { 594 if(strict) 595 throw new Exception("no attribute value before end of file"); 596 else 597 return null; 598 } 599 switch(data[pos]) { 600 case '\'': 601 case '"': 602 auto started = pos; 603 char end = data[pos]; 604 pos++; 605 auto start = pos; 606 while(pos < data.length && data[pos] != end) 607 pos++; 608 if(strict && pos == data.length) 609 throw new MarkupException("Unclosed attribute value, started on char " ~ to!string(started)); 610 string v = htmlEntitiesDecode(data[start..pos], strict); 611 pos++; // skip over the end 612 return v; 613 default: 614 if(strict) 615 parseError("Attributes must be quoted"); 616 // read until whitespace or terminator (/> or >) 617 auto start = pos; 618 while( 619 pos < data.length && 620 data[pos] != '>' && 621 // unquoted attributes might be urls, so gotta be careful with them and self-closed elements 622 !(data[pos] == '/' && pos + 1 < data.length && data[pos+1] == '>') && 623 !data[pos].isSimpleWhite) 624 pos++; 625 626 string v = htmlEntitiesDecode(data[start..pos], strict); 627 // don't skip the end - we'll need it later 628 return v; 629 } 630 } 631 632 TextNode readTextNode() { 633 auto start = pos; 634 while(pos < data.length && data[pos] != '<') { 635 pos++; 636 } 637 638 return TextNode.fromUndecodedString(this, data[start..pos]); 639 } 640 641 // this is obsolete! 642 RawSource readCDataNode() { 643 auto start = pos; 644 while(pos < data.length && data[pos] != '<') { 645 pos++; 646 } 647 648 return new RawSource(this, data[start..pos]); 649 } 650 651 652 struct Ele { 653 int type; // element or closing tag or nothing 654 /* 655 type == 0 means regular node, self-closed (element is valid) 656 type == 1 means closing tag (payload is the tag name, element may be valid) 657 type == 2 means you should ignore it completely 658 type == 3 means it is a special element that should be appended, if possible, e.g. a <!DOCTYPE> that was chosen to be kept, php code, or comment. It will be appended at the current element if inside the root, and to a special document area if not 659 type == 4 means the document was totally empty 660 */ 661 Element element; // for type == 0 or type == 3 662 string payload; // for type == 1 663 } 664 // recursively read a tag 665 Ele readElement(string[] parentChain = null) { 666 // FIXME: this is the slowest function in this module, by far, even in strict mode. 667 // Loose mode should perform decently, but strict mode is the important one. 668 if(!strict && parentChain is null) 669 parentChain = []; 670 671 static string[] recentAutoClosedTags; 672 673 if(pos >= data.length) 674 { 675 if(strict) { 676 throw new MarkupException("Gone over the input (is there no root element or did it never close?), chain: " ~ to!string(parentChain)); 677 } else { 678 if(parentChain.length) 679 return Ele(1, null, parentChain[0]); // in loose mode, we just assume the document has ended 680 else 681 return Ele(4); // signal emptiness upstream 682 } 683 } 684 685 if(data[pos] != '<') { 686 return Ele(0, readTextNode(), null); 687 } 688 689 enforce(data[pos] == '<'); 690 pos++; 691 if(pos == data.length) { 692 if(strict) 693 throw new MarkupException("Found trailing < at end of file"); 694 // if not strict, we'll just skip the switch 695 } else 696 switch(data[pos]) { 697 // I don't care about these, so I just want to skip them 698 case '!': // might be a comment, a doctype, or a special instruction 699 pos++; 700 701 // FIXME: we should store these in the tree too 702 // though I like having it stripped out tbh. 703 704 if(pos == data.length) { 705 if(strict) 706 throw new MarkupException("<! opened at end of file"); 707 } else if(data[pos] == '-' && (pos + 1 < data.length) && data[pos+1] == '-') { 708 // comment 709 pos += 2; 710 711 // FIXME: technically, a comment is anything 712 // between -- and -- inside a <!> block. 713 // so in <!-- test -- lol> , the " lol" is NOT a comment 714 // and should probably be handled differently in here, but for now 715 // I'll just keep running until --> since that's the common way 716 717 auto commentStart = pos; 718 while(pos+3 < data.length && data[pos..pos+3] != "-->") 719 pos++; 720 721 auto end = commentStart; 722 723 if(pos + 3 >= data.length) { 724 if(strict) 725 throw new MarkupException("unclosed comment"); 726 end = data.length; 727 pos = data.length; 728 } else { 729 end = pos; 730 assert(data[pos] == '-'); 731 pos++; 732 assert(data[pos] == '-'); 733 pos++; 734 assert(data[pos] == '>'); 735 pos++; 736 } 737 738 if(parseSawComment !is null) 739 if(parseSawComment(data[commentStart .. end])) { 740 return Ele(3, new HtmlComment(this, data[commentStart .. end]), null); 741 } 742 } else if(pos + 7 <= data.length && data[pos..pos + 7] == "[CDATA[") { 743 pos += 7; 744 745 auto cdataStart = pos; 746 747 ptrdiff_t end = -1; 748 typeof(end) cdataEnd; 749 750 if(pos < data.length) { 751 // cdata isn't allowed to nest, so this should be generally ok, as long as it is found 752 end = data[pos .. $].indexOf("]]>"); 753 } 754 755 if(end == -1) { 756 if(strict) 757 throw new MarkupException("Unclosed CDATA section"); 758 end = pos; 759 cdataEnd = pos; 760 } else { 761 cdataEnd = pos + end; 762 pos = cdataEnd + 3; 763 } 764 765 return Ele(0, new TextNode(this, data[cdataStart .. cdataEnd]), null); 766 } else { 767 auto start = pos; 768 while(pos < data.length && data[pos] != '>') 769 pos++; 770 771 auto bangEnds = pos; 772 if(pos == data.length) { 773 if(strict) 774 throw new MarkupException("unclosed processing instruction (<!xxx>)"); 775 } else pos++; // skipping the > 776 777 if(parseSawBangInstruction !is null) 778 if(parseSawBangInstruction(data[start .. bangEnds])) { 779 // FIXME: these should be able to modify the parser state, 780 // doing things like adding entities, somehow. 781 782 return Ele(3, new BangInstruction(this, data[start .. bangEnds]), null); 783 } 784 } 785 786 /* 787 if(pos < data.length && data[pos] == '>') 788 pos++; // skip the > 789 else 790 assert(!strict); 791 */ 792 break; 793 case '%': 794 case '?': 795 /* 796 Here's what we want to support: 797 798 <% asp code %> 799 <%= asp code %> 800 <?php php code ?> 801 <?= php code ?> 802 803 The contents don't really matter, just if it opens with 804 one of the above for, it ends on the two char terminator. 805 806 <?something> 807 this is NOT php code 808 because I've seen this in the wild: <?EM-dummyText> 809 810 This could be php with shorttags which would be cut off 811 prematurely because if(a >) - that > counts as the close 812 of the tag, but since dom.d can't tell the difference 813 between that and the <?EM> real world example, it will 814 not try to look for the ?> ending. 815 816 The difference between this and the asp/php stuff is that it 817 ends on >, not ?>. ONLY <?php or <?= ends on ?>. The rest end 818 on >. 819 */ 820 821 char end = data[pos]; 822 auto started = pos; 823 bool isAsp = end == '%'; 824 int currentIndex = 0; 825 bool isPhp = false; 826 bool isEqualTag = false; 827 int phpCount = 0; 828 829 more: 830 pos++; // skip the start 831 if(pos == data.length) { 832 if(strict) 833 throw new MarkupException("Unclosed <"~end~" by end of file"); 834 } else { 835 currentIndex++; 836 if(currentIndex == 1 && data[pos] == '=') { 837 if(!isAsp) 838 isPhp = true; 839 isEqualTag = true; 840 goto more; 841 } 842 if(currentIndex == 1 && data[pos] == 'p') 843 phpCount++; 844 if(currentIndex == 2 && data[pos] == 'h') 845 phpCount++; 846 if(currentIndex == 3 && data[pos] == 'p' && phpCount == 2) 847 isPhp = true; 848 849 if(data[pos] == '>') { 850 if((isAsp || isPhp) && data[pos - 1] != end) 851 goto more; 852 // otherwise we're done 853 } else 854 goto more; 855 } 856 857 //writefln("%s: %s", isAsp ? "ASP" : isPhp ? "PHP" : "<? ", data[started .. pos]); 858 auto code = data[started .. pos]; 859 860 861 assert((pos < data.length && data[pos] == '>') || (!strict && pos == data.length)); 862 if(pos < data.length) 863 pos++; // get past the > 864 865 if(isAsp && parseSawAspCode !is null) { 866 if(parseSawAspCode(code)) { 867 return Ele(3, new AspCode(this, code), null); 868 } 869 } else if(isPhp && parseSawPhpCode !is null) { 870 if(parseSawPhpCode(code)) { 871 return Ele(3, new PhpCode(this, code), null); 872 } 873 } else if(!isAsp && !isPhp && parseSawQuestionInstruction !is null) { 874 if(parseSawQuestionInstruction(code)) { 875 return Ele(3, new QuestionInstruction(this, code), null); 876 } 877 } 878 break; 879 case '/': // closing an element 880 pos++; // skip the start 881 auto p = pos; 882 while(pos < data.length && data[pos] != '>') 883 pos++; 884 //writefln("</%s>", data[p..pos]); 885 if(pos == data.length && data[pos-1] != '>') { 886 if(strict) 887 throw new MarkupException("File ended before closing tag had a required >"); 888 else 889 data ~= ">"; // just hack it in 890 } 891 pos++; // skip the '>' 892 893 string tname = data[p..pos-1]; 894 if(!strict) 895 tname = tname.strip; 896 if(!caseSensitive) 897 tname = tname.toLower(); 898 899 return Ele(1, null, tname); // closing tag reports itself here 900 case ' ': // assume it isn't a real element... 901 if(strict) { 902 parseError("bad markup - improperly placed <"); 903 assert(0); // parseError always throws 904 } else 905 return Ele(0, TextNode.fromUndecodedString(this, "<"), null); 906 default: 907 908 if(!strict) { 909 // what about something that kinda looks like a tag, but isn't? 910 auto nextTag = data[pos .. $].indexOf("<"); 911 auto closeTag = data[pos .. $].indexOf(">"); 912 if(closeTag != -1 && nextTag != -1) 913 if(nextTag < closeTag) { 914 // since attribute names cannot possibly have a < in them, we'll look for an equal since it might be an attribute value... and even in garbage mode, it'd have to be a quoted one realistically 915 916 auto equal = data[pos .. $].indexOf("=\""); 917 if(equal != -1 && equal < closeTag) { 918 // this MIGHT be ok, soldier on 919 } else { 920 // definitely no good, this must be a (horribly distorted) text node 921 pos++; // skip the < we're on - don't want text node to end prematurely 922 auto node = readTextNode(); 923 node.contents = "<" ~ node.contents; // put this back 924 return Ele(0, node, null); 925 } 926 } 927 } 928 929 string tagName = readTagName(); 930 string[string] attributes; 931 932 Ele addTag(bool selfClosed) { 933 if(selfClosed) 934 pos++; 935 else { 936 if(!strict) 937 if(tagName.isInArray(selfClosedElements)) 938 // these are de-facto self closed 939 selfClosed = true; 940 } 941 942 import std.algorithm.comparison; 943 944 if(strict) { 945 enforce(data[pos] == '>', format("got %s when expecting > (possible missing attribute name)\nContext:\n%s", data[pos], data[max(0, pos - 100) .. min(data.length, pos + 100)])); 946 } else { 947 // if we got here, it's probably because a slash was in an 948 // unquoted attribute - don't trust the selfClosed value 949 if(!selfClosed) 950 selfClosed = tagName.isInArray(selfClosedElements); 951 952 while(pos < data.length && data[pos] != '>') 953 pos++; 954 955 if(pos >= data.length) { 956 // the tag never closed 957 assert(data.length != 0); 958 pos = data.length - 1; // rewinding so it hits the end at the bottom.. 959 } 960 } 961 962 auto whereThisTagStarted = pos; // for better error messages 963 964 pos++; 965 966 auto e = createElement(tagName); 967 e.attributes = attributes; 968 version(dom_node_indexes) { 969 if(e.dataset.nodeIndex.length == 0) 970 e.dataset.nodeIndex = to!string(&(e.attributes)); 971 } 972 e.selfClosed = selfClosed; 973 e.parseAttributes(); 974 975 976 // HACK to handle script and style as a raw data section as it is in HTML browsers 977 if(tagName == "script" || tagName == "style") { 978 if(!selfClosed) { 979 string closer = "</" ~ tagName ~ ">"; 980 ptrdiff_t ending; 981 if(pos >= data.length) 982 ending = -1; 983 else 984 ending = indexOf(data[pos..$], closer); 985 986 ending = indexOf(data[pos..$], closer, 0, (loose ? CaseSensitive.no : CaseSensitive.yes)); 987 /* 988 if(loose && ending == -1 && pos < data.length) 989 ending = indexOf(data[pos..$], closer.toUpper()); 990 */ 991 if(ending == -1) { 992 if(strict) 993 throw new Exception("tag " ~ tagName ~ " never closed"); 994 else { 995 // let's call it totally empty and do the rest of the file as text. doing it as html could still result in some weird stuff like if(a<4) being read as <4 being a tag so it comes out if(a<4></4> and other weirdness) It is either a closed script tag or the rest of the file is forfeit. 996 if(pos < data.length) { 997 e = new TextNode(this, data[pos .. $]); 998 pos = data.length; 999 } 1000 } 1001 } else { 1002 ending += pos; 1003 e.innerRawSource = data[pos..ending]; 1004 pos = ending + closer.length; 1005 } 1006 } 1007 return Ele(0, e, null); 1008 } 1009 1010 bool closed = selfClosed; 1011 1012 void considerHtmlParagraphHack(Element n) { 1013 assert(!strict); 1014 if(e.tagName == "p" && e.tagName == n.tagName) { 1015 // html lets you write <p> para 1 <p> para 1 1016 // but in the dom tree, they should be siblings, not children. 1017 paragraphHackfixRequired = true; 1018 } 1019 } 1020 1021 //writef("<%s>", tagName); 1022 while(!closed) { 1023 Ele n; 1024 if(strict) 1025 n = readElement(); 1026 else 1027 n = readElement(parentChain ~ tagName); 1028 1029 if(n.type == 4) return n; // the document is empty 1030 1031 if(n.type == 3 && n.element !is null) { 1032 // special node, append if possible 1033 if(e !is null) 1034 e.appendChild(n.element); 1035 else 1036 piecesBeforeRoot ~= n.element; 1037 } else if(n.type == 0) { 1038 if(!strict) 1039 considerHtmlParagraphHack(n.element); 1040 e.appendChild(n.element); 1041 } else if(n.type == 1) { 1042 bool found = false; 1043 if(n.payload != tagName) { 1044 if(strict) 1045 parseError(format("mismatched tag: </%s> != <%s> (opened on line %d)", n.payload, tagName, getLineNumber(whereThisTagStarted))); 1046 else { 1047 sawImproperNesting = true; 1048 // this is so we don't drop several levels of awful markup 1049 if(n.element) { 1050 if(!strict) 1051 considerHtmlParagraphHack(n.element); 1052 e.appendChild(n.element); 1053 n.element = null; 1054 } 1055 1056 // is the element open somewhere up the chain? 1057 foreach(i, parent; parentChain) 1058 if(parent == n.payload) { 1059 recentAutoClosedTags ~= tagName; 1060 // just rotating it so we don't inadvertently break stuff with vile crap 1061 if(recentAutoClosedTags.length > 4) 1062 recentAutoClosedTags = recentAutoClosedTags[1 .. $]; 1063 1064 n.element = e; 1065 return n; 1066 } 1067 1068 // if not, this is a text node; we can't fix it up... 1069 1070 // If it's already in the tree somewhere, assume it is closed by algorithm 1071 // and we shouldn't output it - odds are the user just flipped a couple tags 1072 foreach(ele; e.tree) { 1073 if(ele.tagName == n.payload) { 1074 found = true; 1075 break; 1076 } 1077 } 1078 1079 foreach(ele; recentAutoClosedTags) { 1080 if(ele == n.payload) { 1081 found = true; 1082 break; 1083 } 1084 } 1085 1086 if(!found) // if not found in the tree though, it's probably just text 1087 e.appendChild(TextNode.fromUndecodedString(this, "</"~n.payload~">")); 1088 } 1089 } else { 1090 if(n.element) { 1091 if(!strict) 1092 considerHtmlParagraphHack(n.element); 1093 e.appendChild(n.element); 1094 } 1095 } 1096 1097 if(n.payload == tagName) // in strict mode, this is always true 1098 closed = true; 1099 } else { /*throw new Exception("wtf " ~ tagName);*/ } 1100 } 1101 //writef("</%s>\n", tagName); 1102 return Ele(0, e, null); 1103 } 1104 1105 // if a tag was opened but not closed by end of file, we can arrive here 1106 if(!strict && pos >= data.length) 1107 return addTag(false); 1108 //else if(strict) assert(0); // should be caught before 1109 1110 switch(data[pos]) { 1111 default: assert(0); 1112 case '/': // self closing tag 1113 return addTag(true); 1114 case '>': 1115 return addTag(false); 1116 case ' ': 1117 case '\t': 1118 case '\n': 1119 case '\r': 1120 // there might be attributes... 1121 moreAttributes: 1122 eatWhitespace(); 1123 1124 // same deal as above the switch.... 1125 if(!strict && pos >= data.length) 1126 return addTag(false); 1127 1128 if(strict && pos >= data.length) 1129 throw new MarkupException("tag open, didn't find > before end of file"); 1130 1131 switch(data[pos]) { 1132 case '/': // self closing tag 1133 return addTag(true); 1134 case '>': // closed tag; open -- we now read the contents 1135 return addTag(false); 1136 default: // it is an attribute 1137 string attrName = readAttributeName(); 1138 string attrValue = attrName; 1139 1140 bool ateAny = eatWhitespace(); 1141 if(strict && ateAny) 1142 throw new MarkupException("inappropriate whitespace after attribute name"); 1143 1144 if(pos >= data.length) { 1145 if(strict) 1146 assert(0, "this should have thrown in readAttributeName"); 1147 else { 1148 data ~= ">"; 1149 goto blankValue; 1150 } 1151 } 1152 if(data[pos] == '=') { 1153 pos++; 1154 1155 ateAny = eatWhitespace(); 1156 // the spec actually allows this! 1157 //if(strict && ateAny) 1158 //throw new MarkupException("inappropriate whitespace after attribute equals"); 1159 1160 attrValue = readAttributeValue(); 1161 1162 eatWhitespace(); 1163 } 1164 1165 blankValue: 1166 1167 if(strict && attrName in attributes) 1168 throw new MarkupException("Repeated attribute: " ~ attrName); 1169 1170 if(attrName.strip().length) 1171 attributes[attrName] = attrValue; 1172 else if(strict) throw new MarkupException("wtf, zero length attribute name"); 1173 1174 if(!strict && pos < data.length && data[pos] == '<') { 1175 // this is the broken tag that doesn't have a > at the end 1176 data = data[0 .. pos] ~ ">" ~ data[pos.. $]; 1177 // let's insert one as a hack 1178 goto case '>'; 1179 } 1180 1181 goto moreAttributes; 1182 } 1183 } 1184 } 1185 1186 return Ele(2, null, null); // this is a <! or <? thing that got ignored prolly. 1187 //assert(0); 1188 } 1189 1190 eatWhitespace(); 1191 Ele r; 1192 do { 1193 r = readElement(); // there SHOULD only be one element... 1194 1195 if(r.type == 3 && r.element !is null) 1196 piecesBeforeRoot ~= r.element; 1197 1198 if(r.type == 4) 1199 break; // the document is completely empty... 1200 } while (r.type != 0 || r.element.nodeType != 1); // we look past the xml prologue and doctype; root only begins on a regular node 1201 1202 root = r.element; 1203 if(root !is null) 1204 root.parent_ = this; 1205 1206 if(!strict) // in strict mode, we'll just ignore stuff after the xml 1207 while(r.type != 4) { 1208 r = readElement(); 1209 if(r.type != 4 && r.type != 2) { // if not empty and not ignored 1210 if(r.element !is null) 1211 piecesAfterRoot ~= r.element; 1212 } 1213 } 1214 1215 if(root is null) 1216 { 1217 if(strict) 1218 assert(0, "empty document should be impossible in strict mode"); 1219 else 1220 parseUtf8(`<html><head></head><body></body></html>`); // fill in a dummy document in loose mode since that's what browsers do 1221 } 1222 1223 if(paragraphHackfixRequired) { 1224 assert(!strict); // this should never happen in strict mode; it ought to never set the hack flag... 1225 1226 // in loose mode, we can see some "bad" nesting (it's valid html, but poorly formed xml). 1227 // It's hard to handle above though because my code sucks. So, we'll fix it here. 1228 1229 // Where to insert based on the parent (for mixed closed/unclosed <p> tags). See #120 1230 // Kind of inefficient because we can't detect when we recurse back out of a node. 1231 Element[Element] insertLocations; 1232 auto iterator = root.tree; 1233 foreach(ele; iterator) { 1234 if(ele.parentNode is null) 1235 continue; 1236 1237 if(ele.tagName == "p" && ele.parentNode.tagName == ele.tagName) { 1238 auto shouldBePreviousSibling = ele.parentNode; 1239 auto holder = shouldBePreviousSibling.parentNode; // this is the two element's mutual holder... 1240 if (auto p = holder in insertLocations) { 1241 shouldBePreviousSibling = *p; 1242 assert(shouldBePreviousSibling.parentNode is holder); 1243 } 1244 ele = holder.insertAfter(shouldBePreviousSibling, ele.removeFromTree()); 1245 insertLocations[holder] = ele; 1246 iterator.currentKilled(); // the current branch can be skipped; we'll hit it soon anyway since it's now next up. 1247 } 1248 } 1249 } 1250 } 1251 1252 /* end massive parse function */ 1253 1254 /// Gets the <title> element's innerText, if one exists 1255 @property string title() { 1256 bool doesItMatch(Element e) { 1257 return (e.tagName == "title"); 1258 } 1259 1260 auto e = findFirst(&doesItMatch); 1261 if(e) 1262 return e.innerText(); 1263 return ""; 1264 } 1265 1266 /// Sets the title of the page, creating a <title> element if needed. 1267 @property void title(string t) { 1268 bool doesItMatch(Element e) { 1269 return (e.tagName == "title"); 1270 } 1271 1272 auto e = findFirst(&doesItMatch); 1273 1274 if(!e) { 1275 e = createElement("title"); 1276 auto heads = getElementsByTagName("head"); 1277 if(heads.length) 1278 heads[0].appendChild(e); 1279 } 1280 1281 if(e) 1282 e.innerText = t; 1283 } 1284 1285 // FIXME: would it work to alias root this; ???? might be a good idea 1286 /// These functions all forward to the root element. See the documentation in the Element class. 1287 Element getElementById(string id) { 1288 return root.getElementById(id); 1289 } 1290 1291 /// ditto 1292 final SomeElementType requireElementById(SomeElementType = Element)(string id, string file = __FILE__, size_t line = __LINE__) 1293 if( is(SomeElementType : Element)) 1294 out(ret) { assert(ret !is null); } 1295 do { 1296 return root.requireElementById!(SomeElementType)(id, file, line); 1297 } 1298 1299 /// ditto 1300 final SomeElementType requireSelector(SomeElementType = Element)(string selector, string file = __FILE__, size_t line = __LINE__) 1301 if( is(SomeElementType : Element)) 1302 out(ret) { assert(ret !is null); } 1303 do { 1304 auto e = cast(SomeElementType) querySelector(selector); 1305 if(e is null) 1306 throw new ElementNotFoundException(SomeElementType.stringof, selector, this.root, file, line); 1307 return e; 1308 } 1309 1310 /// ditto 1311 final MaybeNullElement!SomeElementType optionSelector(SomeElementType = Element)(string selector, string file = __FILE__, size_t line = __LINE__) 1312 if(is(SomeElementType : Element)) 1313 { 1314 auto e = cast(SomeElementType) querySelector(selector); 1315 return MaybeNullElement!SomeElementType(e); 1316 } 1317 1318 /// ditto 1319 @scriptable 1320 Element querySelector(string selector) { 1321 // see comment below on Document.querySelectorAll 1322 auto s = Selector(selector);//, !loose); 1323 foreach(ref comp; s.components) 1324 if(comp.parts.length && comp.parts[0].separation == 0) 1325 comp.parts[0].separation = -1; 1326 foreach(e; s.getMatchingElementsLazy(this.root)) 1327 return e; 1328 return null; 1329 1330 } 1331 1332 /// ditto 1333 @scriptable 1334 Element[] querySelectorAll(string selector) { 1335 // In standards-compliant code, the document is slightly magical 1336 // in that it is a pseudoelement at top level. It should actually 1337 // match the root as one of its children. 1338 // 1339 // In versions of dom.d before Dec 29 2019, this worked because 1340 // querySelectorAll was willing to return itself. With that bug fix 1341 // (search "arbitrary id asduiwh" in this file for associated unittest) 1342 // this would have failed. Hence adding back the root if it matches the 1343 // selector itself. 1344 // 1345 // I'd love to do this better later. 1346 1347 auto s = Selector(selector);//, !loose); 1348 foreach(ref comp; s.components) 1349 if(comp.parts.length && comp.parts[0].separation == 0) 1350 comp.parts[0].separation = -1; 1351 return s.getMatchingElements(this.root); 1352 } 1353 1354 /// ditto 1355 deprecated("use querySelectorAll instead") 1356 Element[] getElementsBySelector(string selector) { 1357 return root.getElementsBySelector(selector); 1358 } 1359 1360 /// ditto 1361 @scriptable 1362 Element[] getElementsByTagName(string tag) { 1363 return root.getElementsByTagName(tag); 1364 } 1365 1366 /// ditto 1367 @scriptable 1368 Element[] getElementsByClassName(string tag) { 1369 return root.getElementsByClassName(tag); 1370 } 1371 1372 /** FIXME: btw, this could just be a lazy range...... */ 1373 Element getFirstElementByTagName(string tag) { 1374 if(loose) 1375 tag = tag.toLower(); 1376 bool doesItMatch(Element e) { 1377 return e.tagName == tag; 1378 } 1379 return findFirst(&doesItMatch); 1380 } 1381 1382 /// This returns the <body> element, if there is one. (It different than Javascript, where it is called 'body', because body is a keyword in D.) 1383 Element mainBody() { 1384 return getFirstElementByTagName("body"); 1385 } 1386 1387 /// this uses a weird thing... it's [name=] if no colon and 1388 /// [property=] if colon 1389 string getMeta(string name) { 1390 string thing = name.indexOf(":") == -1 ? "name" : "property"; 1391 auto e = querySelector("head meta["~thing~"="~name~"]"); 1392 if(e is null) 1393 return null; 1394 return e.content; 1395 } 1396 1397 /// Sets a meta tag in the document header. It is kinda hacky to work easily for both Facebook open graph and traditional html meta tags/ 1398 void setMeta(string name, string value) { 1399 string thing = name.indexOf(":") == -1 ? "name" : "property"; 1400 auto e = querySelector("head meta["~thing~"="~name~"]"); 1401 if(e is null) { 1402 e = requireSelector("head").addChild("meta"); 1403 e.setAttribute(thing, name); 1404 } 1405 1406 e.content = value; 1407 } 1408 1409 ///. 1410 Form[] forms() { 1411 return cast(Form[]) getElementsByTagName("form"); 1412 } 1413 1414 ///. 1415 Form createForm() 1416 out(ret) { 1417 assert(ret !is null); 1418 } 1419 do { 1420 return cast(Form) createElement("form"); 1421 } 1422 1423 ///. 1424 Element createElement(string name) { 1425 if(loose) 1426 name = name.toLower(); 1427 1428 auto e = Element.make(name, null, null, selfClosedElements); 1429 1430 return e; 1431 1432 // return new Element(this, name, null, selfClosed); 1433 } 1434 1435 ///. 1436 Element createFragment() { 1437 return new DocumentFragment(this); 1438 } 1439 1440 ///. 1441 Element createTextNode(string content) { 1442 return new TextNode(this, content); 1443 } 1444 1445 1446 ///. 1447 Element findFirst(bool delegate(Element) doesItMatch) { 1448 if(root is null) 1449 return null; 1450 Element result; 1451 1452 bool goThroughElement(Element e) { 1453 if(doesItMatch(e)) { 1454 result = e; 1455 return true; 1456 } 1457 1458 foreach(child; e.children) { 1459 if(goThroughElement(child)) 1460 return true; 1461 } 1462 1463 return false; 1464 } 1465 1466 goThroughElement(root); 1467 1468 return result; 1469 } 1470 1471 ///. 1472 void clear() { 1473 root = null; 1474 loose = false; 1475 } 1476 1477 private string _prolog = "<!DOCTYPE html>\n"; 1478 private bool prologWasSet = false; // set to true if the user changed it 1479 1480 /++ 1481 Returns or sets the string before the root element. This is, for example, 1482 `<!DOCTYPE html>\n` or similar. 1483 +/ 1484 @property string prolog() const { 1485 // if the user explicitly changed it, do what they want 1486 // or if we didn't keep/find stuff from the document itself, 1487 // we'll use the builtin one as a default. 1488 if(prologWasSet || piecesBeforeRoot.length == 0) 1489 return _prolog; 1490 1491 string p; 1492 foreach(e; piecesBeforeRoot) 1493 p ~= e.toString() ~ "\n"; 1494 return p; 1495 } 1496 1497 /// ditto 1498 void setProlog(string d) { 1499 _prolog = d; 1500 prologWasSet = true; 1501 } 1502 1503 /++ 1504 Returns the document as string form. Please note that if there is anything in [piecesAfterRoot], 1505 they are discarded. If you want to add them to the file, loop over that and append it yourself 1506 (but remember xml isn't supposed to have anything after the root element). 1507 +/ 1508 override string toString() const { 1509 return prolog ~ root.toString(); 1510 } 1511 1512 /++ 1513 Writes it out with whitespace for easier eyeball debugging 1514 1515 Do NOT use for anything other than eyeball debugging, 1516 because whitespace may be significant content in XML. 1517 +/ 1518 string toPrettyString(bool insertComments = false, int indentationLevel = 0, string indentWith = "\t") const { 1519 import std.string; 1520 string s = prolog.strip; 1521 1522 /* 1523 if(insertComments) s ~= "<!--"; 1524 s ~= "\n"; 1525 if(insertComments) s ~= "-->"; 1526 */ 1527 1528 s ~= root.toPrettyStringImpl(insertComments, indentationLevel, indentWith); 1529 foreach(a; piecesAfterRoot) 1530 s ~= a.toPrettyStringImpl(insertComments, indentationLevel, indentWith); 1531 return s; 1532 } 1533 1534 /// The root element, like `<html>`. Most the methods on Document forward to this object. 1535 Element root; 1536 1537 /// if these were kept, this is stuff that appeared before the root element, such as <?xml version ?> decls and <!DOCTYPE>s 1538 Element[] piecesBeforeRoot; 1539 1540 /// stuff after the root, only stored in non-strict mode and not used in toString, but available in case you want it 1541 Element[] piecesAfterRoot; 1542 1543 ///. 1544 bool loose; 1545 1546 1547 1548 // what follows are for mutation events that you can observe 1549 void delegate(DomMutationEvent)[] eventObservers; 1550 1551 void dispatchMutationEvent(DomMutationEvent e) { 1552 foreach(o; eventObservers) 1553 o(e); 1554 } 1555 } 1556 1557 interface DomParent { 1558 inout(Document) asDocument() inout; 1559 inout(Element) asElement() inout; 1560 } 1561 1562 /++ 1563 This represents almost everything in the DOM and offers a lot of inspection and manipulation functions. Element, or its subclasses, are what makes the dom tree. 1564 +/ 1565 /// Group: core_functionality 1566 class Element : DomParent { 1567 inout(Document) asDocument() inout { return null; } 1568 inout(Element) asElement() inout { return this; } 1569 1570 /// Returns a collection of elements by selector. 1571 /// See: [Document.opIndex] 1572 ElementCollection opIndex(string selector) { 1573 auto e = ElementCollection(this); 1574 return e[selector]; 1575 } 1576 1577 /++ 1578 Returns the child node with the particular index. 1579 1580 Be aware that child nodes include text nodes, including 1581 whitespace-only nodes. 1582 +/ 1583 Element opIndex(size_t index) { 1584 if(index >= children.length) 1585 return null; 1586 return this.children[index]; 1587 } 1588 1589 /// Calls getElementById, but throws instead of returning null if the element is not found. You can also ask for a specific subclass of Element to dynamically cast to, which also throws if it cannot be done. 1590 final SomeElementType requireElementById(SomeElementType = Element)(string id, string file = __FILE__, size_t line = __LINE__) 1591 if( 1592 is(SomeElementType : Element) 1593 ) 1594 out(ret) { 1595 assert(ret !is null); 1596 } 1597 do { 1598 auto e = cast(SomeElementType) getElementById(id); 1599 if(e is null) 1600 throw new ElementNotFoundException(SomeElementType.stringof, "id=" ~ id, this, file, line); 1601 return e; 1602 } 1603 1604 /// ditto but with selectors instead of ids 1605 final SomeElementType requireSelector(SomeElementType = Element)(string selector, string file = __FILE__, size_t line = __LINE__) 1606 if( 1607 is(SomeElementType : Element) 1608 ) 1609 out(ret) { 1610 assert(ret !is null); 1611 } 1612 do { 1613 auto e = cast(SomeElementType) querySelector(selector); 1614 if(e is null) 1615 throw new ElementNotFoundException(SomeElementType.stringof, selector, this, file, line); 1616 return e; 1617 } 1618 1619 1620 /++ 1621 If a matching selector is found, it returns that Element. Otherwise, the returned object returns null for all methods. 1622 +/ 1623 final MaybeNullElement!SomeElementType optionSelector(SomeElementType = Element)(string selector, string file = __FILE__, size_t line = __LINE__) 1624 if(is(SomeElementType : Element)) 1625 { 1626 auto e = cast(SomeElementType) querySelector(selector); 1627 return MaybeNullElement!SomeElementType(e); 1628 } 1629 1630 1631 1632 /// get all the classes on this element 1633 @property string[] classes() const { 1634 // FIXME: remove blank names 1635 auto cs = split(className, " "); 1636 foreach(ref c; cs) 1637 c = c.strip(); 1638 return cs; 1639 } 1640 1641 /++ 1642 The object [classList] returns. 1643 +/ 1644 static struct ClassListHelper { 1645 Element this_; 1646 this(inout(Element) this_) inout { 1647 this.this_ = this_; 1648 } 1649 1650 /// 1651 bool contains(string cn) const { 1652 return this_.hasClass(cn); 1653 } 1654 1655 /// 1656 void add(string cn) { 1657 this_.addClass(cn); 1658 } 1659 1660 /// 1661 void remove(string cn) { 1662 this_.removeClass(cn); 1663 } 1664 1665 /// 1666 void toggle(string cn) { 1667 if(contains(cn)) 1668 remove(cn); 1669 else 1670 add(cn); 1671 } 1672 1673 // this thing supposed to be iterable in javascript but idk how i want to do it in D. meh 1674 /+ 1675 string[] opIndex() const { 1676 return this_.classes; 1677 } 1678 +/ 1679 } 1680 1681 /++ 1682 Returns a helper object to work with classes, just like javascript. 1683 1684 History: 1685 Added August 25, 2022 1686 +/ 1687 @property inout(ClassListHelper) classList() inout { 1688 return inout(ClassListHelper)(this); 1689 } 1690 // FIXME: classList is supposed to whitespace and duplicates when you use it. need to test. 1691 1692 unittest { 1693 Element element = Element.make("div"); 1694 element.classList.add("foo"); 1695 assert(element.classList.contains("foo")); 1696 element.classList.remove("foo"); 1697 assert(!element.classList.contains("foo")); 1698 element.classList.toggle("bar"); 1699 assert(element.classList.contains("bar")); 1700 } 1701 1702 /// ditto 1703 alias classNames = classes; 1704 1705 1706 /// Adds a string to the class attribute. The class attribute is used a lot in CSS. 1707 @scriptable 1708 Element addClass(string c) { 1709 if(hasClass(c)) 1710 return this; // don't add it twice 1711 1712 string cn = getAttribute("class"); 1713 if(cn.length == 0) { 1714 setAttribute("class", c); 1715 return this; 1716 } else { 1717 setAttribute("class", cn ~ " " ~ c); 1718 } 1719 1720 return this; 1721 } 1722 1723 /// Removes a particular class name. 1724 @scriptable 1725 Element removeClass(string c) { 1726 if(!hasClass(c)) 1727 return this; 1728 string n; 1729 foreach(name; classes) { 1730 if(c == name) 1731 continue; // cut it out 1732 if(n.length) 1733 n ~= " "; 1734 n ~= name; 1735 } 1736 1737 className = n.strip(); 1738 1739 return this; 1740 } 1741 1742 /// Returns whether the given class appears in this element. 1743 bool hasClass(string c) const { 1744 string cn = className; 1745 1746 auto idx = cn.indexOf(c); 1747 if(idx == -1) 1748 return false; 1749 1750 foreach(cla; cn.split(" ")) 1751 if(cla.strip == c) 1752 return true; 1753 return false; 1754 1755 /* 1756 int rightSide = idx + c.length; 1757 1758 bool checkRight() { 1759 if(rightSide == cn.length) 1760 return true; // it's the only class 1761 else if(iswhite(cn[rightSide])) 1762 return true; 1763 return false; // this is a substring of something else.. 1764 } 1765 1766 if(idx == 0) { 1767 return checkRight(); 1768 } else { 1769 if(!iswhite(cn[idx - 1])) 1770 return false; // substring 1771 return checkRight(); 1772 } 1773 1774 assert(0); 1775 */ 1776 } 1777 1778 1779 /* ******************************* 1780 DOM Mutation 1781 *********************************/ 1782 /++ 1783 Family of convenience functions to quickly add a tag with some text or 1784 other relevant info (for example, it's a src for an <img> element 1785 instead of inner text). They forward to [Element.make] then calls [appendChild]. 1786 1787 --- 1788 div.addChild("span", "hello there"); 1789 div.addChild("div", Html("<p>children of the div</p>")); 1790 --- 1791 +/ 1792 Element addChild(string tagName, string childInfo = null, string childInfo2 = null) 1793 in { 1794 assert(tagName !is null); 1795 } 1796 out(e) { 1797 //assert(e.parentNode is this); 1798 //assert(e.parentDocument is this.parentDocument); 1799 } 1800 do { 1801 auto e = Element.make(tagName, childInfo, childInfo2); 1802 // FIXME (maybe): if the thing is self closed, we might want to go ahead and 1803 // return the parent. That will break existing code though. 1804 return appendChild(e); 1805 } 1806 1807 /// ditto 1808 Element addChild(Element e) { 1809 return this.appendChild(e); 1810 } 1811 1812 /// ditto 1813 Element addChild(string tagName, Element firstChild, string info2 = null) 1814 in { 1815 assert(firstChild !is null); 1816 } 1817 out(ret) { 1818 assert(ret !is null); 1819 assert(ret.parentNode is this); 1820 assert(firstChild.parentNode is ret); 1821 1822 assert(ret.parentDocument is this.parentDocument); 1823 //assert(firstChild.parentDocument is this.parentDocument); 1824 } 1825 do { 1826 auto e = Element.make(tagName, "", info2); 1827 e.appendChild(firstChild); 1828 this.appendChild(e); 1829 return e; 1830 } 1831 1832 /// ditto 1833 Element addChild(string tagName, in Html innerHtml, string info2 = null) 1834 in { 1835 } 1836 out(ret) { 1837 assert(ret !is null); 1838 assert((cast(DocumentFragment) this !is null) || (ret.parentNode is this), ret.toString);// e.parentNode ? e.parentNode.toString : "null"); 1839 assert(ret.parentDocument is this.parentDocument); 1840 } 1841 do { 1842 auto e = Element.make(tagName, "", info2); 1843 this.appendChild(e); 1844 e.innerHTML = innerHtml.source; 1845 return e; 1846 } 1847 1848 1849 /// Another convenience function. Adds a child directly after the current one, returning 1850 /// the new child. 1851 /// 1852 /// Between this, addChild, and parentNode, you can build a tree as a single expression. 1853 /// See_Also: [addChild] 1854 Element addSibling(string tagName, string childInfo = null, string childInfo2 = null) 1855 in { 1856 assert(tagName !is null); 1857 assert(parentNode !is null); 1858 } 1859 out(e) { 1860 assert(e.parentNode is this.parentNode); 1861 assert(e.parentDocument is this.parentDocument); 1862 } 1863 do { 1864 auto e = Element.make(tagName, childInfo, childInfo2); 1865 return parentNode.insertAfter(this, e); 1866 } 1867 1868 /// ditto 1869 Element addSibling(Element e) { 1870 return parentNode.insertAfter(this, e); 1871 } 1872 1873 /// Convenience function to append text intermixed with other children. 1874 /// For example: div.addChildren("You can visit my website by ", new Link("mysite.com", "clicking here"), "."); 1875 /// or div.addChildren("Hello, ", user.name, "!"); 1876 /// See also: appendHtml. This might be a bit simpler though because you don't have to think about escaping. 1877 void addChildren(T...)(T t) { 1878 foreach(item; t) { 1879 static if(is(item : Element)) 1880 appendChild(item); 1881 else static if (is(isSomeString!(item))) 1882 appendText(to!string(item)); 1883 else static assert(0, "Cannot pass " ~ typeof(item).stringof ~ " to addChildren"); 1884 } 1885 } 1886 1887 /// Appends the list of children to this element. 1888 void appendChildren(Element[] children) { 1889 foreach(ele; children) 1890 appendChild(ele); 1891 } 1892 1893 /// Removes this element form its current parent and appends it to the given `newParent`. 1894 void reparent(Element newParent) 1895 in { 1896 assert(newParent !is null); 1897 assert(parentNode !is null); 1898 } 1899 out { 1900 assert(this.parentNode is newParent); 1901 //assert(isInArray(this, newParent.children)); 1902 } 1903 do { 1904 parentNode.removeChild(this); 1905 newParent.appendChild(this); 1906 } 1907 1908 /** 1909 Strips this tag out of the document, putting its inner html 1910 as children of the parent. 1911 1912 For example, given: `<p>hello <b>there</b></p>`, if you 1913 call `stripOut` on the `b` element, you'll be left with 1914 `<p>hello there<p>`. 1915 1916 The idea here is to make it easy to get rid of garbage 1917 markup you aren't interested in. 1918 */ 1919 void stripOut() 1920 in { 1921 assert(parentNode !is null); 1922 } 1923 out { 1924 assert(parentNode is null); 1925 assert(children.length == 0); 1926 } 1927 do { 1928 foreach(c; children) 1929 c.parentNode = null; // remove the parent 1930 if(children.length) 1931 parentNode.replaceChild(this, this.children); 1932 else 1933 parentNode.removeChild(this); 1934 this.children.length = 0; // we reparented them all above 1935 } 1936 1937 /// shorthand for `this.parentNode.removeChild(this)` with `parentNode` `null` check 1938 /// if the element already isn't in a tree, it does nothing. 1939 Element removeFromTree() 1940 in { 1941 1942 } 1943 out(var) { 1944 assert(this.parentNode is null); 1945 assert(var is this); 1946 } 1947 do { 1948 if(this.parentNode is null) 1949 return this; 1950 1951 this.parentNode.removeChild(this); 1952 1953 return this; 1954 } 1955 1956 /++ 1957 Wraps this element inside the given element. 1958 It's like `this.replaceWith(what); what.appendchild(this);` 1959 1960 Given: `<b>cool</b>`, if you call `b.wrapIn(new Link("site.com", "my site is "));` 1961 you'll end up with: `<a href="site.com">my site is <b>cool</b></a>`. 1962 +/ 1963 Element wrapIn(Element what) 1964 in { 1965 assert(what !is null); 1966 } 1967 out(ret) { 1968 assert(this.parentNode is what); 1969 assert(ret is what); 1970 } 1971 do { 1972 this.replaceWith(what); 1973 what.appendChild(this); 1974 1975 return what; 1976 } 1977 1978 /// Replaces this element with something else in the tree. 1979 Element replaceWith(Element e) 1980 in { 1981 assert(this.parentNode !is null); 1982 } 1983 do { 1984 e.removeFromTree(); 1985 this.parentNode.replaceChild(this, e); 1986 return e; 1987 } 1988 1989 /** 1990 Fetches the first consecutive text nodes concatenated together. 1991 1992 1993 `firstInnerText` of `<example>some text<span>more text</span></example>` is `some text`. It stops at the first child tag encountered. 1994 1995 See_also: [directText], [innerText] 1996 */ 1997 string firstInnerText() const { 1998 string s; 1999 foreach(child; children) { 2000 if(child.nodeType != NodeType.Text) 2001 break; 2002 2003 s ~= child.nodeValue(); 2004 } 2005 return s; 2006 } 2007 2008 2009 /** 2010 Returns the text directly under this element. 2011 2012 2013 Unlike [innerText], it does not recurse, and unlike [firstInnerText], it continues 2014 past child tags. So, `<example>some <b>bold</b> text</example>` 2015 will return `some text` because it only gets the text, skipping non-text children. 2016 2017 See_also: [firstInnerText], [innerText] 2018 */ 2019 @property string directText() { 2020 string ret; 2021 foreach(e; children) { 2022 if(e.nodeType == NodeType.Text) 2023 ret ~= e.nodeValue(); 2024 } 2025 2026 return ret; 2027 } 2028 2029 /** 2030 Sets the direct text, without modifying other child nodes. 2031 2032 2033 Unlike [innerText], this does *not* remove existing elements in the element. 2034 2035 It only replaces the first text node it sees. 2036 2037 If there are no text nodes, it calls [appendText]. 2038 2039 So, given `<div><img />text here</div>`, it will keep the `<img />`, and replace the `text here`. 2040 */ 2041 @property void directText(string text) { 2042 foreach(e; children) { 2043 if(e.nodeType == NodeType.Text) { 2044 auto it = cast(TextNode) e; 2045 it.contents = text; 2046 return; 2047 } 2048 } 2049 2050 appendText(text); 2051 } 2052 2053 // do nothing, this is primarily a virtual hook 2054 // for links and forms 2055 void setValue(string field, string value) { } 2056 2057 2058 // this is a thing so i can remove observer support if it gets slow 2059 // I have not implemented all these yet 2060 private void sendObserverEvent(DomMutationOperations operation, string s1 = null, string s2 = null, Element r = null, Element r2 = null) { 2061 if(parentDocument is null) return; 2062 DomMutationEvent me; 2063 me.operation = operation; 2064 me.target = this; 2065 me.relatedString = s1; 2066 me.relatedString2 = s2; 2067 me.related = r; 2068 me.related2 = r2; 2069 parentDocument.dispatchMutationEvent(me); 2070 } 2071 2072 // putting all the members up front 2073 2074 // this ought to be private. don't use it directly. 2075 Element[] children; 2076 2077 /// The name of the tag. Remember, changing this doesn't change the dynamic type of the object. 2078 string tagName; 2079 2080 /// This is where the attributes are actually stored. You should use getAttribute, setAttribute, and hasAttribute instead. 2081 string[string] attributes; 2082 2083 /// In XML, it is valid to write <tag /> for all elements with no children, but that breaks HTML, so I don't do it here. 2084 /// Instead, this flag tells if it should be. It is based on the source document's notation and a html element list. 2085 private bool selfClosed; 2086 2087 private DomParent parent_; 2088 2089 /// Get the parent Document object that contains this element. 2090 /// It may be null, so remember to check for that. 2091 @property inout(Document) parentDocument() inout { 2092 if(this.parent_ is null) 2093 return null; 2094 auto p = cast() this.parent_.asElement; 2095 auto prev = cast() this; 2096 while(p) { 2097 prev = p; 2098 if(p.parent_ is null) 2099 return null; 2100 p = cast() p.parent_.asElement; 2101 } 2102 return cast(inout) prev.parent_.asDocument; 2103 } 2104 2105 deprecated @property void parentDocument(Document doc) { 2106 parent_ = doc; 2107 } 2108 2109 /// Returns the parent node in the tree this element is attached to. 2110 inout(Element) parentNode() inout { 2111 if(parent_ is null) 2112 return null; 2113 2114 auto p = parent_.asElement; 2115 2116 if(cast(DocumentFragment) p) { 2117 if(p.parent_ is null) 2118 return null; 2119 else 2120 return p.parent_.asElement; 2121 } 2122 2123 return p; 2124 } 2125 2126 //protected 2127 Element parentNode(Element e) { 2128 parent_ = e; 2129 return e; 2130 } 2131 2132 // these are here for event handlers. Don't forget that this library never fires events. 2133 // (I'm thinking about putting this in a version statement so you don't have the baggage. The instance size of this class is 56 bytes right now.) 2134 2135 version(dom_with_events) { 2136 EventHandler[][string] bubblingEventHandlers; 2137 EventHandler[][string] capturingEventHandlers; 2138 EventHandler[string] defaultEventHandlers; 2139 2140 void addEventListener(string event, EventHandler handler, bool useCapture = false) { 2141 if(event.length > 2 && event[0..2] == "on") 2142 event = event[2 .. $]; 2143 2144 if(useCapture) 2145 capturingEventHandlers[event] ~= handler; 2146 else 2147 bubblingEventHandlers[event] ~= handler; 2148 } 2149 } 2150 2151 2152 // and now methods 2153 2154 /++ 2155 Convenience function to try to do the right thing for HTML. This is the main way I create elements. 2156 2157 History: 2158 On February 8, 2021, the `selfClosedElements` parameter was added. Previously, it used a private 2159 immutable global list for HTML. It still defaults to the same list, but you can change it now via 2160 the parameter. 2161 See_Also: 2162 [addChild], [addSibling] 2163 +/ 2164 static Element make(string tagName, string childInfo = null, string childInfo2 = null, const string[] selfClosedElements = htmlSelfClosedElements) { 2165 bool selfClosed = tagName.isInArray(selfClosedElements); 2166 2167 Element e; 2168 // want to create the right kind of object for the given tag... 2169 switch(tagName) { 2170 case "#text": 2171 e = new TextNode(null, childInfo); 2172 return e; 2173 // break; 2174 case "table": 2175 e = new Table(null); 2176 break; 2177 case "a": 2178 e = new Link(null); 2179 break; 2180 case "form": 2181 e = new Form(null); 2182 break; 2183 case "tr": 2184 e = new TableRow(null); 2185 break; 2186 case "td", "th": 2187 e = new TableCell(null, tagName); 2188 break; 2189 default: 2190 e = new Element(null, tagName, null, selfClosed); // parent document should be set elsewhere 2191 } 2192 2193 // make sure all the stuff is constructed properly FIXME: should probably be in all the right constructors too 2194 e.tagName = tagName; 2195 e.selfClosed = selfClosed; 2196 2197 if(childInfo !is null) 2198 switch(tagName) { 2199 /* html5 convenience tags */ 2200 case "audio": 2201 if(childInfo.length) 2202 e.addChild("source", childInfo); 2203 if(childInfo2 !is null) 2204 e.appendText(childInfo2); 2205 break; 2206 case "source": 2207 e.src = childInfo; 2208 if(childInfo2 !is null) 2209 e.type = childInfo2; 2210 break; 2211 /* regular html 4 stuff */ 2212 case "img": 2213 e.src = childInfo; 2214 if(childInfo2 !is null) 2215 e.alt = childInfo2; 2216 break; 2217 case "link": 2218 e.href = childInfo; 2219 if(childInfo2 !is null) 2220 e.rel = childInfo2; 2221 break; 2222 case "option": 2223 e.innerText = childInfo; 2224 if(childInfo2 !is null) 2225 e.value = childInfo2; 2226 break; 2227 case "input": 2228 e.type = "hidden"; 2229 e.name = childInfo; 2230 if(childInfo2 !is null) 2231 e.value = childInfo2; 2232 break; 2233 case "button": 2234 e.innerText = childInfo; 2235 if(childInfo2 !is null) 2236 e.type = childInfo2; 2237 break; 2238 case "a": 2239 e.innerText = childInfo; 2240 if(childInfo2 !is null) 2241 e.href = childInfo2; 2242 break; 2243 case "script": 2244 case "style": 2245 e.innerRawSource = childInfo; 2246 break; 2247 case "meta": 2248 e.name = childInfo; 2249 if(childInfo2 !is null) 2250 e.content = childInfo2; 2251 break; 2252 /* generically, assume we were passed text and perhaps class */ 2253 default: 2254 e.innerText = childInfo; 2255 if(childInfo2.length) 2256 e.className = childInfo2; 2257 } 2258 2259 return e; 2260 } 2261 2262 /// ditto 2263 static Element make(string tagName, in Html innerHtml, string childInfo2 = null) { 2264 // FIXME: childInfo2 is ignored when info1 is null 2265 auto m = Element.make(tagName, "not null"[0..0], childInfo2); 2266 m.innerHTML = innerHtml.source; 2267 return m; 2268 } 2269 2270 /// ditto 2271 static Element make(string tagName, Element child, string childInfo2 = null) { 2272 auto m = Element.make(tagName, cast(string) null, childInfo2); 2273 m.appendChild(child); 2274 return m; 2275 } 2276 2277 2278 /// Generally, you don't want to call this yourself - use Element.make or document.createElement instead. 2279 this(Document _parentDocument, string _tagName, string[string] _attributes = null, bool _selfClosed = false) { 2280 tagName = _tagName; 2281 if(_attributes !is null) 2282 attributes = _attributes; 2283 selfClosed = _selfClosed; 2284 2285 version(dom_node_indexes) 2286 this.dataset.nodeIndex = to!string(&(this.attributes)); 2287 2288 assert(_tagName.indexOf(" ") == -1);//, "<" ~ _tagName ~ "> is invalid"); 2289 } 2290 2291 /++ 2292 Convenience constructor when you don't care about the parentDocument. Note this might break things on the document. 2293 Note also that without a parent document, elements are always in strict, case-sensitive mode. 2294 2295 History: 2296 On February 8, 2021, the `selfClosedElements` parameter was added. It defaults to the same behavior as 2297 before: using the hard-coded list of HTML elements, but it can now be overridden. If you use 2298 [Document.createElement], it will use the list set for the current document. Otherwise, you can pass 2299 something here if you like. 2300 +/ 2301 this(string _tagName, string[string] _attributes = null, const string[] selfClosedElements = htmlSelfClosedElements) { 2302 tagName = _tagName; 2303 if(_attributes !is null) 2304 attributes = _attributes; 2305 selfClosed = tagName.isInArray(selfClosedElements); 2306 2307 // this is meant to reserve some memory. It makes a small, but consistent improvement. 2308 //children.length = 8; 2309 //children.length = 0; 2310 2311 version(dom_node_indexes) 2312 this.dataset.nodeIndex = to!string(&(this.attributes)); 2313 } 2314 2315 private this(Document _parentDocument) { 2316 version(dom_node_indexes) 2317 this.dataset.nodeIndex = to!string(&(this.attributes)); 2318 } 2319 2320 2321 /* ******************************* 2322 Navigating the DOM 2323 *********************************/ 2324 2325 /// Returns the first child of this element. If it has no children, returns null. 2326 /// Remember, text nodes are children too. 2327 @property Element firstChild() { 2328 return children.length ? children[0] : null; 2329 } 2330 2331 /// Returns the last child of the element, or null if it has no children. Remember, text nodes are children too. 2332 @property Element lastChild() { 2333 return children.length ? children[$ - 1] : null; 2334 } 2335 2336 // FIXME UNTESTED 2337 /// the next or previous element you would encounter if you were reading it in the source. May be a text node or other special non-tag object if you enabled them. 2338 Element nextInSource() { 2339 auto n = firstChild; 2340 if(n is null) 2341 n = nextSibling(); 2342 if(n is null) { 2343 auto p = this.parentNode; 2344 while(p !is null && n is null) { 2345 n = p.nextSibling; 2346 } 2347 } 2348 2349 return n; 2350 } 2351 2352 /// ditto 2353 Element previousInSource() { 2354 auto p = previousSibling; 2355 if(p is null) { 2356 auto par = parentNode; 2357 if(par) 2358 p = par.lastChild; 2359 if(p is null) 2360 p = par; 2361 } 2362 return p; 2363 } 2364 2365 /++ 2366 Returns the next or previous sibling that is not a text node. Please note: the behavior with comments is subject to change. Currently, it will return a comment or other nodes if it is in the tree (if you enabled it with [Document.enableAddingSpecialTagsToDom] or [Document.parseSawComment]) and not if you didn't, but the implementation will probably change at some point to skip them regardless. 2367 2368 Equivalent to [previousSibling]/[nextSibling]("*"). 2369 2370 Please note it may return `null`. 2371 +/ 2372 @property Element previousElementSibling() { 2373 return previousSibling("*"); 2374 } 2375 2376 /// ditto 2377 @property Element nextElementSibling() { 2378 return nextSibling("*"); 2379 } 2380 2381 /++ 2382 Returns the next or previous sibling matching the `tagName` filter. The default filter of `null` will return the first sibling it sees, even if it is a comment or text node, or anything else. A filter of `"*"` will match any tag with a name. Otherwise, the string must match the [tagName] of the sibling you want to find. 2383 +/ 2384 @property Element previousSibling(string tagName = null) { 2385 if(this.parentNode is null) 2386 return null; 2387 Element ps = null; 2388 foreach(e; this.parentNode.childNodes) { 2389 if(e is this) 2390 break; 2391 if(tagName == "*" && e.nodeType != NodeType.Text) { 2392 ps = e; 2393 } else if(tagName is null || e.tagName == tagName) 2394 ps = e; 2395 } 2396 2397 return ps; 2398 } 2399 2400 /// ditto 2401 @property Element nextSibling(string tagName = null) { 2402 if(this.parentNode is null) 2403 return null; 2404 Element ns = null; 2405 bool mightBe = false; 2406 foreach(e; this.parentNode.childNodes) { 2407 if(e is this) { 2408 mightBe = true; 2409 continue; 2410 } 2411 if(mightBe) { 2412 if(tagName == "*" && e.nodeType != NodeType.Text) { 2413 ns = e; 2414 break; 2415 } 2416 if(tagName is null || e.tagName == tagName) { 2417 ns = e; 2418 break; 2419 } 2420 } 2421 } 2422 2423 return ns; 2424 } 2425 2426 2427 /++ 2428 Gets the nearest node, going up the chain, with the given tagName 2429 May return null or throw. The type `T` will specify a subclass like 2430 [Form], [Table], or [Link], which it will cast for you when found. 2431 +/ 2432 T getParent(T = Element)(string tagName = null) if(is(T : Element)) { 2433 if(tagName is null) { 2434 static if(is(T == Form)) 2435 tagName = "form"; 2436 else static if(is(T == Table)) 2437 tagName = "table"; 2438 else static if(is(T == Link)) 2439 tagName == "a"; 2440 } 2441 2442 auto par = this.parentNode; 2443 while(par !is null) { 2444 if(tagName is null || par.tagName == tagName) 2445 break; 2446 par = par.parentNode; 2447 } 2448 2449 static if(!is(T == Element)) { 2450 auto t = cast(T) par; 2451 if(t is null) 2452 throw new ElementNotFoundException("", tagName ~ " parent not found", this); 2453 } else 2454 auto t = par; 2455 2456 return t; 2457 } 2458 2459 /++ 2460 Searches this element and the tree of elements under it for one matching the given `id` attribute. 2461 +/ 2462 Element getElementById(string id) { 2463 // FIXME: I use this function a lot, and it's kinda slow 2464 // not terribly slow, but not great. 2465 foreach(e; tree) 2466 if(e.id == id) 2467 return e; 2468 return null; 2469 } 2470 2471 /++ 2472 Returns a child element that matches the given `selector`. 2473 2474 Note: you can give multiple selectors, separated by commas. 2475 It will return the first match it finds. 2476 2477 Tip: to use namespaces, escape the colon in the name: 2478 2479 --- 2480 element.querySelector(`ns\:tag`); // the backticks are raw strings then the backslash is interpreted by querySelector 2481 --- 2482 +/ 2483 @scriptable 2484 Element querySelector(string selector) { 2485 Selector s = Selector(selector); 2486 foreach(ele; tree) 2487 if(s.matchesElement(ele)) 2488 return ele; 2489 return null; 2490 } 2491 2492 /// If the element matches the given selector. Previously known as `matchesSelector`. 2493 @scriptable 2494 bool matches(string selector) { 2495 /+ 2496 bool caseSensitiveTags = true; 2497 if(parentDocument && parentDocument.loose) 2498 caseSensitiveTags = false; 2499 +/ 2500 2501 Selector s = Selector(selector); 2502 return s.matchesElement(this); 2503 } 2504 2505 /// Returns itself or the closest parent that matches the given selector, or null if none found 2506 /// See_also: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest 2507 @scriptable 2508 Element closest(string selector) { 2509 Element e = this; 2510 while(e !is null) { 2511 if(e.matches(selector)) 2512 return e; 2513 e = e.parentNode; 2514 } 2515 return null; 2516 } 2517 2518 /** 2519 Returns elements that match the given CSS selector 2520 2521 * -- all, default if nothing else is there 2522 2523 tag#id.class.class.class:pseudo[attrib=what][attrib=what] OP selector 2524 2525 It is all additive 2526 2527 OP 2528 2529 space = descendant 2530 > = direct descendant 2531 + = sibling (E+F Matches any F element immediately preceded by a sibling element E) 2532 2533 [foo] Foo is present as an attribute 2534 [foo="warning"] Matches any E element whose "foo" attribute value is exactly equal to "warning". 2535 E[foo~="warning"] Matches any E element whose "foo" attribute value is a list of space-separated values, one of which is exactly equal to "warning" 2536 E[lang|="en"] Matches any E element whose "lang" attribute has a hyphen-separated list of values beginning (from the left) with "en". 2537 2538 [item$=sdas] ends with 2539 [item^-sdsad] begins with 2540 2541 Quotes are optional here. 2542 2543 Pseudos: 2544 :first-child 2545 :last-child 2546 :link (same as a[href] for our purposes here) 2547 2548 2549 There can be commas separating the selector. A comma separated list result is OR'd onto the main. 2550 2551 2552 2553 This ONLY cares about elements. text, etc, are ignored 2554 2555 2556 There should be two functions: given element, does it match the selector? and given a selector, give me all the elements 2557 2558 The name `getElementsBySelector` was the original name, written back before the name `querySelector` was standardized (this library is older than you might think!), but they do the same thing.. 2559 */ 2560 @scriptable 2561 Element[] querySelectorAll(string selector) { 2562 // FIXME: this function could probably use some performance attention 2563 // ... but only mildly so according to the profiler in the big scheme of things; probably negligible in a big app. 2564 2565 2566 bool caseSensitiveTags = true; 2567 if(parentDocument && parentDocument.loose) 2568 caseSensitiveTags = false; 2569 2570 Element[] ret; 2571 foreach(sel; parseSelectorString(selector, caseSensitiveTags)) 2572 ret ~= sel.getElements(this); 2573 return ret; 2574 } 2575 2576 /// ditto 2577 alias getElementsBySelector = querySelectorAll; 2578 2579 /++ 2580 Returns child elements that have the given class name or tag name. 2581 2582 Please note the standard specifies this should return a live node list. This means, in Javascript for example, if you loop over the value returned by getElementsByTagName and getElementsByClassName and remove the elements, the length of the list will decrease. When I implemented this, I figured that was more trouble than it was worth and returned a plain array instead. By the time I had the infrastructure to make it simple, I didn't want to do the breaking change. 2583 2584 So these is incompatible with Javascript in the face of live dom mutation and will likely remain so. 2585 +/ 2586 Element[] getElementsByClassName(string cn) { 2587 // is this correct? 2588 return getElementsBySelector("." ~ cn); 2589 } 2590 2591 /// ditto 2592 Element[] getElementsByTagName(string tag) { 2593 if(parentDocument && parentDocument.loose) 2594 tag = tag.toLower(); 2595 Element[] ret; 2596 foreach(e; tree) 2597 if(e.tagName == tag) 2598 ret ~= e; 2599 return ret; 2600 } 2601 2602 2603 /* ******************************* 2604 Attributes 2605 *********************************/ 2606 2607 /** 2608 Gets the given attribute value, or null if the 2609 attribute is not set. 2610 2611 Note that the returned string is decoded, so it no longer contains any xml entities. 2612 */ 2613 @scriptable 2614 string getAttribute(string name) const { 2615 if(parentDocument && parentDocument.loose) 2616 name = name.toLower(); 2617 auto e = name in attributes; 2618 if(e) 2619 return *e; 2620 else 2621 return null; 2622 } 2623 2624 /** 2625 Sets an attribute. Returns this for easy chaining 2626 */ 2627 @scriptable 2628 Element setAttribute(string name, string value) { 2629 if(parentDocument && parentDocument.loose) 2630 name = name.toLower(); 2631 2632 // I never use this shit legitimately and neither should you 2633 auto it = name.toLower(); 2634 if(it == "href" || it == "src") { 2635 auto v = value.strip().toLower(); 2636 if(v.startsWith("vbscript:")) 2637 value = value[9..$]; 2638 if(v.startsWith("javascript:")) 2639 value = value[11..$]; 2640 } 2641 2642 attributes[name] = value; 2643 2644 sendObserverEvent(DomMutationOperations.setAttribute, name, value); 2645 2646 return this; 2647 } 2648 2649 /** 2650 Returns if the attribute exists. 2651 */ 2652 @scriptable 2653 bool hasAttribute(string name) { 2654 if(parentDocument && parentDocument.loose) 2655 name = name.toLower(); 2656 2657 if(name in attributes) 2658 return true; 2659 else 2660 return false; 2661 } 2662 2663 /** 2664 Removes the given attribute from the element. 2665 */ 2666 @scriptable 2667 Element removeAttribute(string name) 2668 out(ret) { 2669 assert(ret is this); 2670 } 2671 do { 2672 if(parentDocument && parentDocument.loose) 2673 name = name.toLower(); 2674 if(name in attributes) 2675 attributes.remove(name); 2676 2677 sendObserverEvent(DomMutationOperations.removeAttribute, name); 2678 return this; 2679 } 2680 2681 /** 2682 Gets or sets the class attribute's contents. Returns 2683 an empty string if it has no class. 2684 */ 2685 @property string className() const { 2686 auto c = getAttribute("class"); 2687 if(c is null) 2688 return ""; 2689 return c; 2690 } 2691 2692 /// ditto 2693 @property Element className(string c) { 2694 setAttribute("class", c); 2695 return this; 2696 } 2697 2698 /** 2699 Provides easy access to common HTML attributes, object style. 2700 2701 --- 2702 auto element = Element.make("a"); 2703 a.href = "cool.html"; // this is the same as a.setAttribute("href", "cool.html"); 2704 string where = a.href; // same as a.getAttribute("href"); 2705 --- 2706 2707 */ 2708 @property string opDispatch(string name)(string v = null) if(isConvenientAttribute(name)) { 2709 if(v !is null) 2710 setAttribute(name, v); 2711 return getAttribute(name); 2712 } 2713 2714 /** 2715 Old access to attributes. Use [attrs] instead. 2716 2717 DEPRECATED: generally open opDispatch caused a lot of unforeseen trouble with compile time duck typing and UFCS extensions. 2718 so I want to remove it. A small whitelist of attributes is still allowed, but others are not. 2719 2720 Instead, use element.attrs.attribute, element.attrs["attribute"], 2721 or element.getAttribute("attribute")/element.setAttribute("attribute"). 2722 */ 2723 @property string opDispatch(string name)(string v = null) if(!isConvenientAttribute(name)) { 2724 static assert(0, "Don't use " ~ name ~ " direct on Element, instead use element.attrs.attributeName"); 2725 } 2726 2727 /* 2728 // this would be nice for convenience, but it broke the getter above. 2729 @property void opDispatch(string name)(bool boolean) if(name != "popFront") { 2730 if(boolean) 2731 setAttribute(name, name); 2732 else 2733 removeAttribute(name); 2734 } 2735 */ 2736 2737 /** 2738 Returns the element's children. 2739 */ 2740 @property inout(Element[]) childNodes() inout { 2741 return children; 2742 } 2743 2744 /++ 2745 HTML5's dataset property. It is an alternate view into attributes with the data- prefix. 2746 Given `<a data-my-property="cool" />`, we get `assert(a.dataset.myProperty == "cool");` 2747 +/ 2748 @property DataSet dataset() { 2749 return DataSet(this); 2750 } 2751 2752 /++ 2753 Gives dot/opIndex access to attributes 2754 --- 2755 ele.attrs.largeSrc = "foo"; // same as ele.setAttribute("largeSrc", "foo") 2756 --- 2757 +/ 2758 @property AttributeSet attrs() { 2759 return AttributeSet(this); 2760 } 2761 2762 /++ 2763 Provides both string and object style (like in Javascript) access to the style attribute. 2764 2765 --- 2766 element.style.color = "red"; // translates into setting `color: red;` in the `style` attribute 2767 --- 2768 +/ 2769 @property ElementStyle style() { 2770 return ElementStyle(this); 2771 } 2772 2773 /++ 2774 This sets the style attribute with a string. 2775 +/ 2776 @property ElementStyle style(string s) { 2777 this.setAttribute("style", s); 2778 return this.style; 2779 } 2780 2781 private void parseAttributes(string[] whichOnes = null) { 2782 /+ 2783 if(whichOnes is null) 2784 whichOnes = attributes.keys; 2785 foreach(attr; whichOnes) { 2786 switch(attr) { 2787 case "id": 2788 2789 break; 2790 case "class": 2791 2792 break; 2793 case "style": 2794 2795 break; 2796 default: 2797 // we don't care about it 2798 } 2799 } 2800 +/ 2801 } 2802 2803 2804 // if you change something here, it won't apply... FIXME const? but changing it would be nice if it applies to the style attribute too though you should use style there. 2805 2806 // the next few methods are for implementing interactive kind of things 2807 private CssStyle _computedStyle; 2808 2809 /// Don't use this. It can try to parse out the style element but it isn't complete and if I get back to it, it won't be for a while. 2810 @property CssStyle computedStyle() { 2811 if(_computedStyle is null) { 2812 auto style = this.getAttribute("style"); 2813 /* we'll treat shitty old html attributes as css here */ 2814 if(this.hasAttribute("width")) 2815 style ~= "; width: " ~ this.attrs.width; 2816 if(this.hasAttribute("height")) 2817 style ~= "; height: " ~ this.attrs.height; 2818 if(this.hasAttribute("bgcolor")) 2819 style ~= "; background-color: " ~ this.attrs.bgcolor; 2820 if(this.tagName == "body" && this.hasAttribute("text")) 2821 style ~= "; color: " ~ this.attrs.text; 2822 if(this.hasAttribute("color")) 2823 style ~= "; color: " ~ this.attrs.color; 2824 /* done */ 2825 2826 2827 _computedStyle = new CssStyle(null, style); // gives at least something to work with 2828 } 2829 return _computedStyle; 2830 } 2831 2832 /// These properties are useless in most cases, but if you write a layout engine on top of this lib, they may be good 2833 version(browser) { 2834 void* expansionHook; ///ditto 2835 int offsetWidth; ///ditto 2836 int offsetHeight; ///ditto 2837 int offsetLeft; ///ditto 2838 int offsetTop; ///ditto 2839 Element offsetParent; ///ditto 2840 bool hasLayout; ///ditto 2841 int zIndex; ///ditto 2842 2843 ///ditto 2844 int absoluteLeft() { 2845 int a = offsetLeft; 2846 auto p = offsetParent; 2847 while(p) { 2848 a += p.offsetLeft; 2849 p = p.offsetParent; 2850 } 2851 2852 return a; 2853 } 2854 2855 ///ditto 2856 int absoluteTop() { 2857 int a = offsetTop; 2858 auto p = offsetParent; 2859 while(p) { 2860 a += p.offsetTop; 2861 p = p.offsetParent; 2862 } 2863 2864 return a; 2865 } 2866 } 2867 2868 // Back to the regular dom functions 2869 2870 public: 2871 2872 2873 /* ******************************* 2874 DOM Mutation 2875 *********************************/ 2876 2877 /// Removes all inner content from the tag; all child text and elements are gone. 2878 void removeAllChildren() 2879 out { 2880 assert(this.children.length == 0); 2881 } 2882 do { 2883 foreach(child; children) 2884 child.parentNode = null; 2885 children = null; 2886 } 2887 2888 /++ 2889 Adds a sibling element before or after this one in the dom. 2890 2891 History: added June 13, 2020 2892 +/ 2893 Element appendSibling(Element e) { 2894 parentNode.insertAfter(this, e); 2895 return e; 2896 } 2897 2898 /// ditto 2899 Element prependSibling(Element e) { 2900 parentNode.insertBefore(this, e); 2901 return e; 2902 } 2903 2904 2905 /++ 2906 Appends the given element to this one. If it already has a parent, it is removed from that tree and moved to this one. 2907 2908 See_also: https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild 2909 2910 History: 2911 Prior to 1 Jan 2020 (git tag v4.4.1 and below), it required that the given element must not have a parent already. This was in violation of standard, so it changed the behavior to remove it from the existing parent and instead move it here. 2912 +/ 2913 Element appendChild(Element e) 2914 in { 2915 assert(e !is null); 2916 assert(e !is this); 2917 } 2918 out (ret) { 2919 assert((cast(DocumentFragment) this !is null) || (e.parentNode is this), e.toString);// e.parentNode ? e.parentNode.toString : "null"); 2920 assert(e.parentDocument is this.parentDocument); 2921 assert(e is ret); 2922 } 2923 do { 2924 if(e.parentNode !is null) 2925 e.parentNode.removeChild(e); 2926 2927 selfClosed = false; 2928 if(auto frag = cast(DocumentFragment) e) 2929 children ~= frag.children; 2930 else 2931 children ~= e; 2932 2933 e.parentNode = this; 2934 2935 /+ 2936 foreach(item; e.tree) 2937 item.parentDocument = this.parentDocument; 2938 +/ 2939 2940 sendObserverEvent(DomMutationOperations.appendChild, null, null, e); 2941 2942 return e; 2943 } 2944 2945 /// Inserts the second element to this node, right before the first param 2946 Element insertBefore(in Element where, Element what) 2947 in { 2948 assert(where !is null); 2949 assert(where.parentNode is this); 2950 assert(what !is null); 2951 assert(what.parentNode is null); 2952 } 2953 out (ret) { 2954 assert(where.parentNode is this); 2955 assert(what.parentNode is this); 2956 2957 assert(what.parentDocument is this.parentDocument); 2958 assert(ret is what); 2959 } 2960 do { 2961 foreach(i, e; children) { 2962 if(e is where) { 2963 if(auto frag = cast(DocumentFragment) what) { 2964 children = children[0..i] ~ frag.children ~ children[i..$]; 2965 foreach(child; frag.children) 2966 child.parentNode = this; 2967 } else { 2968 children = children[0..i] ~ what ~ children[i..$]; 2969 } 2970 what.parentNode = this; 2971 return what; 2972 } 2973 } 2974 2975 return what; 2976 2977 assert(0); 2978 } 2979 2980 /++ 2981 Inserts the given element `what` as a sibling of the `this` element, after the element `where` in the parent node. 2982 +/ 2983 Element insertAfter(in Element where, Element what) 2984 in { 2985 assert(where !is null); 2986 assert(where.parentNode is this); 2987 assert(what !is null); 2988 assert(what.parentNode is null); 2989 } 2990 out (ret) { 2991 assert(where.parentNode is this); 2992 assert(what.parentNode is this); 2993 assert(what.parentDocument is this.parentDocument); 2994 assert(ret is what); 2995 } 2996 do { 2997 foreach(i, e; children) { 2998 if(e is where) { 2999 if(auto frag = cast(DocumentFragment) what) { 3000 children = children[0 .. i + 1] ~ what.children ~ children[i + 1 .. $]; 3001 foreach(child; frag.children) 3002 child.parentNode = this; 3003 } else 3004 children = children[0 .. i + 1] ~ what ~ children[i + 1 .. $]; 3005 what.parentNode = this; 3006 return what; 3007 } 3008 } 3009 3010 return what; 3011 3012 assert(0); 3013 } 3014 3015 /// swaps one child for a new thing. Returns the old child which is now parentless. 3016 Element swapNode(Element child, Element replacement) 3017 in { 3018 assert(child !is null); 3019 assert(replacement !is null); 3020 assert(child.parentNode is this); 3021 } 3022 out(ret) { 3023 assert(ret is child); 3024 assert(ret.parentNode is null); 3025 assert(replacement.parentNode is this); 3026 assert(replacement.parentDocument is this.parentDocument); 3027 } 3028 do { 3029 foreach(ref c; this.children) 3030 if(c is child) { 3031 c.parentNode = null; 3032 c = replacement; 3033 c.parentNode = this; 3034 return child; 3035 } 3036 assert(0); 3037 } 3038 3039 3040 /++ 3041 Appends the given to the node. 3042 3043 3044 Calling `e.appendText(" hi")` on `<example>text <b>bold</b></example>` 3045 yields `<example>text <b>bold</b> hi</example>`. 3046 3047 See_Also: 3048 [firstInnerText], [directText], [innerText], [appendChild] 3049 +/ 3050 @scriptable 3051 Element appendText(string text) { 3052 Element e = new TextNode(parentDocument, text); 3053 appendChild(e); 3054 return this; 3055 } 3056 3057 /++ 3058 Returns child elements which are of a tag type (excludes text, comments, etc.). 3059 3060 3061 childElements of `<example>text <b>bold</b></example>` is just the `<b>` tag. 3062 3063 Params: 3064 tagName = filter results to only the child elements with the given tag name. 3065 +/ 3066 @property Element[] childElements(string tagName = null) { 3067 Element[] ret; 3068 foreach(c; children) 3069 if(c.nodeType == 1 && (tagName is null || c.tagName == tagName)) 3070 ret ~= c; 3071 return ret; 3072 } 3073 3074 /++ 3075 Appends the given html to the element, returning the elements appended 3076 3077 3078 This is similar to `element.innerHTML += "html string";` in Javascript. 3079 +/ 3080 @scriptable 3081 Element[] appendHtml(string html) { 3082 Document d = new Document("<root>" ~ html ~ "</root>"); 3083 return stealChildren(d.root); 3084 } 3085 3086 3087 /++ 3088 Inserts a child under this element after the element `where`. 3089 +/ 3090 void insertChildAfter(Element child, Element where) 3091 in { 3092 assert(child !is null); 3093 assert(where !is null); 3094 assert(where.parentNode is this); 3095 assert(!selfClosed); 3096 //assert(isInArray(where, children)); 3097 } 3098 out { 3099 assert(child.parentNode is this); 3100 assert(where.parentNode is this); 3101 //assert(isInArray(where, children)); 3102 //assert(isInArray(child, children)); 3103 } 3104 do { 3105 foreach(ref i, c; children) { 3106 if(c is where) { 3107 i++; 3108 if(auto frag = cast(DocumentFragment) child) { 3109 children = children[0..i] ~ child.children ~ children[i..$]; 3110 //foreach(child; frag.children) 3111 //child.parentNode = this; 3112 } else 3113 children = children[0..i] ~ child ~ children[i..$]; 3114 child.parentNode = this; 3115 break; 3116 } 3117 } 3118 } 3119 3120 /++ 3121 Reparents all the child elements of `e` to `this`, leaving `e` childless. 3122 3123 Params: 3124 e = the element whose children you want to steal 3125 position = an existing child element in `this` before which you want the stolen children to be inserted. If `null`, it will append the stolen children at the end of our current children. 3126 +/ 3127 Element[] stealChildren(Element e, Element position = null) 3128 in { 3129 assert(!selfClosed); 3130 assert(e !is null); 3131 //if(position !is null) 3132 //assert(isInArray(position, children)); 3133 } 3134 out (ret) { 3135 assert(e.children.length == 0); 3136 // all the parentNode is this checks fail because DocumentFragments do not appear in the parent tree, they are invisible... 3137 version(none) 3138 debug foreach(child; ret) { 3139 assert(child.parentNode is this); 3140 assert(child.parentDocument is this.parentDocument); 3141 } 3142 } 3143 do { 3144 foreach(c; e.children) { 3145 c.parentNode = this; 3146 } 3147 if(position is null) 3148 children ~= e.children; 3149 else { 3150 foreach(i, child; children) { 3151 if(child is position) { 3152 children = children[0..i] ~ 3153 e.children ~ 3154 children[i..$]; 3155 break; 3156 } 3157 } 3158 } 3159 3160 auto ret = e.children[]; 3161 e.children.length = 0; 3162 3163 return ret; 3164 } 3165 3166 /// Puts the current element first in our children list. The given element must not have a parent already. 3167 Element prependChild(Element e) 3168 in { 3169 assert(e.parentNode is null); 3170 assert(!selfClosed); 3171 } 3172 out { 3173 assert(e.parentNode is this); 3174 assert(e.parentDocument is this.parentDocument); 3175 assert(children[0] is e); 3176 } 3177 do { 3178 if(auto frag = cast(DocumentFragment) e) { 3179 children = e.children ~ children; 3180 foreach(child; frag.children) 3181 child.parentNode = this; 3182 } else 3183 children = e ~ children; 3184 e.parentNode = this; 3185 return e; 3186 } 3187 3188 3189 /** 3190 Returns a string containing all child elements, formatted such that it could be pasted into 3191 an XML file. 3192 */ 3193 @property string innerHTML(Appender!string where = appender!string()) const { 3194 if(children is null) 3195 return ""; 3196 3197 auto start = where.data.length; 3198 3199 foreach(child; children) { 3200 assert(child !is null); 3201 3202 child.writeToAppender(where); 3203 } 3204 3205 return where.data[start .. $]; 3206 } 3207 3208 /** 3209 Takes some html and replaces the element's children with the tree made from the string. 3210 */ 3211 @property Element innerHTML(string html, bool strict = false) { 3212 if(html.length) 3213 selfClosed = false; 3214 3215 if(html.length == 0) { 3216 // I often say innerHTML = ""; as a shortcut to clear it out, 3217 // so let's optimize that slightly. 3218 removeAllChildren(); 3219 return this; 3220 } 3221 3222 auto doc = new Document(); 3223 doc.parseUtf8("<innerhtml>" ~ html ~ "</innerhtml>", strict, strict); // FIXME: this should preserve the strictness of the parent document 3224 3225 children = doc.root.children; 3226 foreach(c; children) { 3227 c.parentNode = this; 3228 } 3229 3230 doc.root.children = null; 3231 3232 return this; 3233 } 3234 3235 /// ditto 3236 @property Element innerHTML(Html html) { 3237 return this.innerHTML = html.source; 3238 } 3239 3240 /** 3241 Replaces this node with the given html string, which is parsed 3242 3243 Note: this invalidates the this reference, since it is removed 3244 from the tree. 3245 3246 Returns the new children that replace this. 3247 */ 3248 @property Element[] outerHTML(string html) { 3249 auto doc = new Document(); 3250 doc.parseUtf8("<innerhtml>" ~ html ~ "</innerhtml>"); // FIXME: needs to preserve the strictness 3251 3252 children = doc.root.children; 3253 foreach(c; children) { 3254 c.parentNode = this; 3255 } 3256 3257 stripOut(); 3258 3259 return doc.root.children; 3260 } 3261 3262 /++ 3263 Returns all the html for this element, including the tag itself. 3264 3265 This is equivalent to calling toString(). 3266 +/ 3267 @property string outerHTML() { 3268 return this.toString(); 3269 } 3270 3271 /// This sets the inner content of the element *without* trying to parse it. 3272 /// You can inject any code in there; this serves as an escape hatch from the dom. 3273 /// 3274 /// The only times you might actually need it are for < style > and < script > tags in html. 3275 /// Other than that, innerHTML and/or innerText should do the job. 3276 @property void innerRawSource(string rawSource) { 3277 children.length = 0; 3278 auto rs = new RawSource(parentDocument, rawSource); 3279 children ~= rs; 3280 rs.parentNode = this; 3281 } 3282 3283 /++ 3284 Replaces the element `find`, which must be a child of `this`, with the element `replace`, which must have no parent. 3285 +/ 3286 Element replaceChild(Element find, Element replace) 3287 in { 3288 assert(find !is null); 3289 assert(find.parentNode is this); 3290 assert(replace !is null); 3291 assert(replace.parentNode is null); 3292 } 3293 out(ret) { 3294 assert(ret is replace); 3295 assert(replace.parentNode is this); 3296 assert(replace.parentDocument is this.parentDocument); 3297 assert(find.parentNode is null); 3298 } 3299 do { 3300 // FIXME 3301 //if(auto frag = cast(DocumentFragment) replace) 3302 //return this.replaceChild(frag, replace.children); 3303 for(int i = 0; i < children.length; i++) { 3304 if(children[i] is find) { 3305 replace.parentNode = this; 3306 children[i].parentNode = null; 3307 children[i] = replace; 3308 return replace; 3309 } 3310 } 3311 3312 throw new Exception("no such child ");// ~ find.toString ~ " among " ~ typeid(this).toString);//.toString ~ " magic \n\n\n" ~ find.parentNode.toString); 3313 } 3314 3315 /** 3316 Replaces the given element with a whole group. 3317 */ 3318 void replaceChild(Element find, Element[] replace) 3319 in { 3320 assert(find !is null); 3321 assert(replace !is null); 3322 assert(find.parentNode is this); 3323 debug foreach(r; replace) 3324 assert(r.parentNode is null); 3325 } 3326 out { 3327 assert(find.parentNode is null); 3328 assert(children.length >= replace.length); 3329 debug foreach(child; children) 3330 assert(child !is find); 3331 debug foreach(r; replace) 3332 assert(r.parentNode is this); 3333 } 3334 do { 3335 if(replace.length == 0) { 3336 removeChild(find); 3337 return; 3338 } 3339 assert(replace.length); 3340 for(int i = 0; i < children.length; i++) { 3341 if(children[i] is find) { 3342 children[i].parentNode = null; // this element should now be dead 3343 children[i] = replace[0]; 3344 foreach(e; replace) { 3345 e.parentNode = this; 3346 } 3347 3348 children = .insertAfter(children, i, replace[1..$]); 3349 3350 return; 3351 } 3352 } 3353 3354 throw new Exception("no such child"); 3355 } 3356 3357 3358 /** 3359 Removes the given child from this list. 3360 3361 Returns the removed element. 3362 */ 3363 Element removeChild(Element c) 3364 in { 3365 assert(c !is null); 3366 assert(c.parentNode is this); 3367 } 3368 out { 3369 debug foreach(child; children) 3370 assert(child !is c); 3371 assert(c.parentNode is null); 3372 } 3373 do { 3374 foreach(i, e; children) { 3375 if(e is c) { 3376 children = children[0..i] ~ children [i+1..$]; 3377 c.parentNode = null; 3378 return c; 3379 } 3380 } 3381 3382 throw new Exception("no such child"); 3383 } 3384 3385 /// This removes all the children from this element, returning the old list. 3386 Element[] removeChildren() 3387 out (ret) { 3388 assert(children.length == 0); 3389 debug foreach(r; ret) 3390 assert(r.parentNode is null); 3391 } 3392 do { 3393 Element[] oldChildren = children.dup; 3394 foreach(c; oldChildren) 3395 c.parentNode = null; 3396 3397 children.length = 0; 3398 3399 return oldChildren; 3400 } 3401 3402 /** 3403 Fetch the inside text, with all tags stripped out. 3404 3405 <p>cool <b>api</b> & code dude<p> 3406 innerText of that is "cool api & code dude". 3407 3408 This does not match what real innerText does! 3409 http://perfectionkills.com/the-poor-misunderstood-innerText/ 3410 3411 It is more like [textContent]. 3412 3413 See_Also: 3414 [visibleText], which is closer to what the real `innerText` 3415 does. 3416 */ 3417 @scriptable 3418 @property string innerText() const { 3419 string s; 3420 foreach(child; children) { 3421 if(child.nodeType != NodeType.Text) 3422 s ~= child.innerText; 3423 else 3424 s ~= child.nodeValue(); 3425 } 3426 return s; 3427 } 3428 3429 /// ditto 3430 alias textContent = innerText; 3431 3432 /++ 3433 Gets the element's visible text, similar to how it would look assuming 3434 the document was HTML being displayed by a browser. This means it will 3435 attempt whitespace normalization (unless it is a `<pre>` tag), add `\n` 3436 characters for `<br>` tags, and I reserve the right to make it process 3437 additional css and tags in the future. 3438 3439 If you need specific output, use the more stable [textContent] property 3440 or iterate yourself with [tree] or a recursive function with [children]. 3441 3442 History: 3443 Added March 25, 2022 (dub v10.8) 3444 +/ 3445 string visibleText() const { 3446 return this.visibleTextHelper(this.tagName == "pre"); 3447 } 3448 3449 private string visibleTextHelper(bool pre) const { 3450 string result; 3451 foreach(thing; this.children) { 3452 if(thing.nodeType == NodeType.Text) 3453 result ~= pre ? thing.nodeValue : normalizeWhitespace(thing.nodeValue); 3454 else if(thing.tagName == "br") 3455 result ~= "\n"; 3456 else 3457 result ~= thing.visibleTextHelper(pre || thing.tagName == "pre"); 3458 } 3459 return result; 3460 } 3461 3462 /** 3463 Sets the inside text, replacing all children. You don't 3464 have to worry about entity encoding. 3465 */ 3466 @scriptable 3467 @property void innerText(string text) { 3468 selfClosed = false; 3469 Element e = new TextNode(parentDocument, text); 3470 children = [e]; 3471 e.parentNode = this; 3472 } 3473 3474 /** 3475 Strips this node out of the document, replacing it with the given text 3476 */ 3477 @property void outerText(string text) { 3478 parentNode.replaceChild(this, new TextNode(parentDocument, text)); 3479 } 3480 3481 /** 3482 Same result as innerText; the tag with all inner tags stripped out 3483 */ 3484 @property string outerText() const { 3485 return innerText; 3486 } 3487 3488 3489 /* ******************************* 3490 Miscellaneous 3491 *********************************/ 3492 3493 /// This is a full clone of the element. Alias for cloneNode(true) now. Don't extend it. 3494 @property Element cloned() 3495 /+ 3496 out(ret) { 3497 // FIXME: not sure why these fail... 3498 assert(ret.children.length == this.children.length, format("%d %d", ret.children.length, this.children.length)); 3499 assert(ret.tagName == this.tagName); 3500 } 3501 do { 3502 +/ 3503 { 3504 return this.cloneNode(true); 3505 } 3506 3507 /// Clones the node. If deepClone is true, clone all inner tags too. If false, only do this tag (and its attributes), but it will have no contents. 3508 Element cloneNode(bool deepClone) { 3509 auto e = Element.make(this.tagName); 3510 e.attributes = this.attributes.aadup; 3511 e.selfClosed = this.selfClosed; 3512 3513 if(deepClone) { 3514 foreach(child; children) { 3515 e.appendChild(child.cloneNode(true)); 3516 } 3517 } 3518 3519 3520 return e; 3521 } 3522 3523 /// W3C DOM interface. Only really meaningful on [TextNode] instances, but the interface is present on the base class. 3524 string nodeValue() const { 3525 return ""; 3526 } 3527 3528 // should return int 3529 ///. 3530 @property int nodeType() const { 3531 return 1; 3532 } 3533 3534 3535 invariant () { 3536 debug assert(tagName.indexOf(" ") == -1); 3537 3538 // commented cuz it gets into recursive pain and eff dat. 3539 /+ 3540 if(children !is null) 3541 foreach(child; children) { 3542 // assert(parentNode !is null); 3543 assert(child !is null); 3544 assert(child.parent_.asElement is this, format("%s is not a parent of %s (it thought it was %s)", tagName, child.tagName, child.parent_.asElement is null ? "null" : child.parent_.asElement.tagName)); 3545 assert(child !is this); 3546 //assert(child !is parentNode); 3547 } 3548 +/ 3549 3550 /+ 3551 // this isn't helping 3552 if(parent_ && parent_.asElement) { 3553 bool found = false; 3554 foreach(child; parent_.asElement.children) 3555 if(child is this) 3556 found = true; 3557 assert(found, format("%s lists %s as parent, but it is not in children", typeid(this), typeid(this.parent_.asElement))); 3558 } 3559 +/ 3560 3561 /+ // only depend on parentNode's accuracy if you shuffle things around and use the top elements - where the contracts guarantee it on out 3562 if(parentNode !is null) { 3563 // if you have a parent, you should share the same parentDocument; this is appendChild()'s job 3564 auto lol = cast(TextNode) this; 3565 assert(parentDocument is parentNode.parentDocument, lol is null ? this.tagName : lol.contents); 3566 } 3567 +/ 3568 //assert(parentDocument !is null); // no more; if it is present, we use it, but it is not required 3569 // reason is so you can create these without needing a reference to the document 3570 } 3571 3572 /** 3573 Turns the whole element, including tag, attributes, and children, into a string which could be pasted into 3574 an XML file. 3575 */ 3576 override string toString() const { 3577 return writeToAppender(); 3578 } 3579 3580 /++ 3581 Returns if the node would be printed to string as `<tag />` or `<tag></tag>`. In other words, if it has no non-empty text nodes and no element nodes. Please note that whitespace text nodes are NOT considered empty; `Html("<tag> </tag>").isEmpty == false`. 3582 3583 3584 The value is undefined if there are comment or processing instruction nodes. The current implementation returns false if it sees those, assuming the nodes haven't been stripped out during parsing. But I'm not married to the current implementation and reserve the right to change it without notice. 3585 3586 History: 3587 Added December 3, 2021 (dub v10.5) 3588 3589 +/ 3590 public bool isEmpty() const { 3591 foreach(child; this.children) { 3592 // any non-text node is of course not empty since that's a tag 3593 if(child.nodeType != NodeType.Text) 3594 return false; 3595 // or a text node is empty if it is is a null or empty string, so this length check fixes that 3596 if(child.nodeValue.length) 3597 return false; 3598 } 3599 3600 return true; 3601 } 3602 3603 protected string toPrettyStringIndent(bool insertComments, int indentationLevel, string indentWith) const { 3604 if(indentWith is null) 3605 return null; 3606 3607 // at the top we don't have anything to really do 3608 //if(parent_ is null) 3609 //return null; 3610 3611 // I've used isEmpty before but this other check seems better.... 3612 //|| this.isEmpty()) 3613 3614 string s; 3615 3616 if(insertComments) s ~= "<!--"; 3617 s ~= "\n"; 3618 foreach(indent; 0 .. indentationLevel) 3619 s ~= indentWith; 3620 if(insertComments) s ~= "-->"; 3621 3622 return s; 3623 } 3624 3625 /++ 3626 Writes out with formatting. Be warned: formatting changes the contents. Use ONLY 3627 for eyeball debugging. 3628 3629 $(PITFALL 3630 This function is not stable. Its interface and output may change without 3631 notice. The only promise I make is that it will continue to make a best- 3632 effort attempt at being useful for debugging by human eyes. 3633 3634 I have used it in the past for diffing html documents, but even then, it 3635 might change between versions. If it is useful, great, but beware; this 3636 use is at your own risk. 3637 ) 3638 3639 History: 3640 On November 19, 2021, I changed this to `final`. If you were overriding it, 3641 change our override to `toPrettyStringImpl` instead. It now just calls 3642 `toPrettyStringImpl.strip` to be an entry point for a stand-alone call. 3643 3644 If you are calling it as part of another implementation, you might want to 3645 change that call to `toPrettyStringImpl` as well. 3646 3647 I am NOT considering this a breaking change since this function is documented 3648 to only be used for eyeball debugging anyway, which means the exact format is 3649 not specified and the override behavior can generally not be relied upon. 3650 3651 (And I find it extremely unlikely anyone was subclassing anyway, but if you were, 3652 email me, and we'll see what we can do. I'd like to know at least.) 3653 3654 I reserve the right to make future changes in the future without considering 3655 them breaking as well. 3656 +/ 3657 final string toPrettyString(bool insertComments = false, int indentationLevel = 0, string indentWith = "\t") const { 3658 return toPrettyStringImpl(insertComments, indentationLevel, indentWith).strip; 3659 } 3660 3661 string toPrettyStringImpl(bool insertComments = false, int indentationLevel = 0, string indentWith = "\t") const { 3662 3663 // first step is to concatenate any consecutive text nodes to simplify 3664 // the white space analysis. this changes the tree! but i'm allowed since 3665 // the comment always says it changes the comments 3666 // 3667 // actually i'm not allowed cuz it is const so i will cheat and lie 3668 /+ 3669 TextNode lastTextChild = null; 3670 for(int a = 0; a < this.children.length; a++) { 3671 auto child = this.children[a]; 3672 if(auto tn = cast(TextNode) child) { 3673 if(lastTextChild) { 3674 lastTextChild.contents ~= tn.contents; 3675 for(int b = a; b < this.children.length - 1; b++) 3676 this.children[b] = this.children[b + 1]; 3677 this.children = this.children[0 .. $-1]; 3678 } else { 3679 lastTextChild = tn; 3680 } 3681 } else { 3682 lastTextChild = null; 3683 } 3684 } 3685 +/ 3686 3687 auto inlineElements = (parentDocument is null ? null : parentDocument.inlineElements); 3688 3689 const(Element)[] children; 3690 3691 TextNode lastTextChild = null; 3692 for(int a = 0; a < this.children.length; a++) { 3693 auto child = this.children[a]; 3694 if(auto tn = cast(const(TextNode)) child) { 3695 if(lastTextChild !is null) { 3696 lastTextChild.contents ~= tn.contents; 3697 } else { 3698 lastTextChild = new TextNode(""); 3699 lastTextChild.parentNode = cast(Element) this; 3700 lastTextChild.contents ~= tn.contents; 3701 children ~= lastTextChild; 3702 } 3703 } else { 3704 lastTextChild = null; 3705 children ~= child; 3706 } 3707 } 3708 3709 string s = toPrettyStringIndent(insertComments, indentationLevel, indentWith); 3710 3711 s ~= "<"; 3712 s ~= tagName; 3713 3714 // i sort these for consistent output. might be more legible 3715 // but especially it keeps it the same for diff purposes. 3716 import std.algorithm : sort; 3717 auto keys = sort(attributes.keys); 3718 foreach(n; keys) { 3719 auto v = attributes[n]; 3720 s ~= " "; 3721 s ~= n; 3722 s ~= "=\""; 3723 s ~= htmlEntitiesEncode(v); 3724 s ~= "\""; 3725 } 3726 3727 if(selfClosed){ 3728 s ~= " />"; 3729 return s; 3730 } 3731 3732 s ~= ">"; 3733 3734 // for simple `<collection><item>text</item><item>text</item></collection>`, let's 3735 // just keep them on the same line 3736 3737 if(isEmpty) { 3738 // no work needed, this is empty so don't indent just for a blank line 3739 } else if(children.length == 1 && children[0].isEmpty) { 3740 // just one empty one, can put it inline too 3741 s ~= children[0].toString(); 3742 } else if(tagName.isInArray(inlineElements) || allAreInlineHtml(children, inlineElements)) { 3743 foreach(child; children) { 3744 s ~= child.toString();//toPrettyString(false, 0, null); 3745 } 3746 } else { 3747 foreach(child; children) { 3748 assert(child !is null); 3749 3750 s ~= child.toPrettyStringImpl(insertComments, indentationLevel + 1, indentWith); 3751 } 3752 3753 s ~= toPrettyStringIndent(insertComments, indentationLevel, indentWith); 3754 } 3755 3756 s ~= "</"; 3757 s ~= tagName; 3758 s ~= ">"; 3759 3760 return s; 3761 } 3762 3763 /+ 3764 /// Writes out the opening tag only, if applicable. 3765 string writeTagOnly(Appender!string where = appender!string()) const { 3766 +/ 3767 3768 /// This is the actual implementation used by toString. You can pass it a preallocated buffer to save some time. 3769 /// Note: the ordering of attributes in the string is undefined. 3770 /// Returns the string it creates. 3771 string writeToAppender(Appender!string where = appender!string()) const { 3772 assert(tagName !is null); 3773 3774 where.reserve((this.children.length + 1) * 512); 3775 3776 auto start = where.data.length; 3777 3778 where.put("<"); 3779 where.put(tagName); 3780 3781 import std.algorithm : sort; 3782 auto keys = sort(attributes.keys); 3783 foreach(n; keys) { 3784 auto v = attributes[n]; // I am sorting these for convenience with another project. order of AAs is undefined, so I'm allowed to do it.... and it is still undefined, I might change it back later. 3785 //assert(v !is null); 3786 where.put(" "); 3787 where.put(n); 3788 where.put("=\""); 3789 htmlEntitiesEncode(v, where); 3790 where.put("\""); 3791 } 3792 3793 if(selfClosed){ 3794 where.put(" />"); 3795 return where.data[start .. $]; 3796 } 3797 3798 where.put('>'); 3799 3800 innerHTML(where); 3801 3802 where.put("</"); 3803 where.put(tagName); 3804 where.put('>'); 3805 3806 return where.data[start .. $]; 3807 } 3808 3809 /** 3810 Returns a lazy range of all its children, recursively. 3811 */ 3812 @property ElementStream tree() { 3813 return new ElementStream(this); 3814 } 3815 3816 // I moved these from Form because they are generally useful. 3817 // Ideally, I'd put them in arsd.html and use UFCS, but that doesn't work with the opDispatch here. 3818 // FIXME: add overloads for other label types... 3819 /++ 3820 Adds a form field to this element, normally a `<input>` but `type` can also be `"textarea"`. 3821 3822 This is fairly html specific and the label uses my style. I recommend you view the source before you use it to better understand what it does. 3823 +/ 3824 /// Tags: HTML, HTML5 3825 Element addField(string label, string name, string type = "text", FormFieldOptions fieldOptions = FormFieldOptions.none) { 3826 auto fs = this; 3827 auto i = fs.addChild("label"); 3828 3829 if(!(type == "checkbox" || type == "radio")) 3830 i.addChild("span", label); 3831 3832 Element input; 3833 if(type == "textarea") 3834 input = i.addChild("textarea"). 3835 setAttribute("name", name). 3836 setAttribute("rows", "6"); 3837 else 3838 input = i.addChild("input"). 3839 setAttribute("name", name). 3840 setAttribute("type", type); 3841 3842 if(type == "checkbox" || type == "radio") 3843 i.addChild("span", label); 3844 3845 // these are html 5 attributes; you'll have to implement fallbacks elsewhere. In Javascript or maybe I'll add a magic thing to html.d later. 3846 fieldOptions.applyToElement(input); 3847 return i; 3848 } 3849 3850 /// ditto 3851 Element addField(Element label, string name, string type = "text", FormFieldOptions fieldOptions = FormFieldOptions.none) { 3852 auto fs = this; 3853 auto i = fs.addChild("label"); 3854 i.addChild(label); 3855 Element input; 3856 if(type == "textarea") 3857 input = i.addChild("textarea"). 3858 setAttribute("name", name). 3859 setAttribute("rows", "6"); 3860 else 3861 input = i.addChild("input"). 3862 setAttribute("name", name). 3863 setAttribute("type", type); 3864 3865 // these are html 5 attributes; you'll have to implement fallbacks elsewhere. In Javascript or maybe I'll add a magic thing to html.d later. 3866 fieldOptions.applyToElement(input); 3867 return i; 3868 } 3869 3870 /// ditto 3871 Element addField(string label, string name, FormFieldOptions fieldOptions) { 3872 return addField(label, name, "text", fieldOptions); 3873 } 3874 3875 /// ditto 3876 Element addField(string label, string name, string[string] options, FormFieldOptions fieldOptions = FormFieldOptions.none) { 3877 auto fs = this; 3878 auto i = fs.addChild("label"); 3879 i.addChild("span", label); 3880 auto sel = i.addChild("select").setAttribute("name", name); 3881 3882 foreach(k, opt; options) 3883 sel.addChild("option", opt, k); 3884 3885 // FIXME: implement requirements somehow 3886 3887 return i; 3888 } 3889 3890 /// ditto 3891 Element addSubmitButton(string label = null) { 3892 auto t = this; 3893 auto holder = t.addChild("div"); 3894 holder.addClass("submit-holder"); 3895 auto i = holder.addChild("input"); 3896 i.type = "submit"; 3897 if(label.length) 3898 i.value = label; 3899 return holder; 3900 } 3901 3902 } 3903 // computedStyle could argubaly be removed to bring size down 3904 //pragma(msg, __traits(classInstanceSize, Element)); 3905 //pragma(msg, Element.tupleof); 3906 3907 // FIXME: since Document loosens the input requirements, it should probably be the sub class... 3908 /// Specializes Document for handling generic XML. (always uses strict mode, uses xml mime type and file header) 3909 /// Group: core_functionality 3910 class XmlDocument : Document { 3911 this(string data) { 3912 selfClosedElements = null; 3913 inlineElements = null; 3914 contentType = "text/xml; charset=utf-8"; 3915 _prolog = `<?xml version="1.0" encoding="UTF-8"?>` ~ "\n"; 3916 3917 parseStrict(data); 3918 } 3919 } 3920 3921 3922 3923 3924 import std.string; 3925 3926 /* domconvenience follows { */ 3927 3928 /// finds comments that match the given txt. Case insensitive, strips whitespace. 3929 /// Group: core_functionality 3930 Element[] findComments(Document document, string txt) { 3931 return findComments(document.root, txt); 3932 } 3933 3934 /// ditto 3935 Element[] findComments(Element element, string txt) { 3936 txt = txt.strip().toLower(); 3937 Element[] ret; 3938 3939 foreach(comment; element.getElementsByTagName("#comment")) { 3940 string t = comment.nodeValue().strip().toLower(); 3941 if(t == txt) 3942 ret ~= comment; 3943 } 3944 3945 return ret; 3946 } 3947 3948 /// An option type that propagates null. See: [Element.optionSelector] 3949 /// Group: implementations 3950 struct MaybeNullElement(SomeElementType) { 3951 this(SomeElementType ele) { 3952 this.element = ele; 3953 } 3954 SomeElementType element; 3955 3956 /// Forwards to the element, wit a null check inserted that propagates null. 3957 auto opDispatch(string method, T...)(T args) { 3958 alias type = typeof(__traits(getMember, element, method)(args)); 3959 static if(is(type : Element)) { 3960 if(element is null) 3961 return MaybeNullElement!type(null); 3962 return __traits(getMember, element, method)(args); 3963 } else static if(is(type == string)) { 3964 if(element is null) 3965 return cast(string) null; 3966 return __traits(getMember, element, method)(args); 3967 } else static if(is(type == void)) { 3968 if(element is null) 3969 return; 3970 __traits(getMember, element, method)(args); 3971 } else { 3972 static assert(0); 3973 } 3974 } 3975 3976 /// Allows implicit casting to the wrapped element. 3977 alias element this; 3978 } 3979 3980 /++ 3981 A collection of elements which forwards methods to the children. 3982 +/ 3983 /// Group: implementations 3984 struct ElementCollection { 3985 /// 3986 this(Element e) { 3987 elements = [e]; 3988 } 3989 3990 /// 3991 this(Element e, string selector) { 3992 elements = e.querySelectorAll(selector); 3993 } 3994 3995 /// 3996 this(Element[] e) { 3997 elements = e; 3998 } 3999 4000 Element[] elements; 4001 //alias elements this; // let it implicitly convert to the underlying array 4002 4003 /// 4004 ElementCollection opIndex(string selector) { 4005 ElementCollection ec; 4006 foreach(e; elements) 4007 ec.elements ~= e.getElementsBySelector(selector); 4008 return ec; 4009 } 4010 4011 /// 4012 Element opIndex(int i) { 4013 return elements[i]; 4014 } 4015 4016 /// if you slice it, give the underlying array for easy forwarding of the 4017 /// collection to range expecting algorithms or looping over. 4018 Element[] opSlice() { 4019 return elements; 4020 } 4021 4022 /// And input range primitives so we can foreach over this 4023 void popFront() { 4024 elements = elements[1..$]; 4025 } 4026 4027 /// ditto 4028 Element front() { 4029 return elements[0]; 4030 } 4031 4032 /// ditto 4033 bool empty() { 4034 return !elements.length; 4035 } 4036 4037 /++ 4038 Collects strings from the collection, concatenating them together 4039 Kinda like running reduce and ~= on it. 4040 4041 --- 4042 document["p"].collect!"innerText"; 4043 --- 4044 +/ 4045 string collect(string method)(string separator = "") { 4046 string text; 4047 foreach(e; elements) { 4048 text ~= mixin("e." ~ method); 4049 text ~= separator; 4050 } 4051 return text; 4052 } 4053 4054 /// Forward method calls to each individual [Element|element] of the collection 4055 /// returns this so it can be chained. 4056 ElementCollection opDispatch(string name, T...)(T t) { 4057 foreach(e; elements) { 4058 mixin("e." ~ name)(t); 4059 } 4060 return this; 4061 } 4062 4063 /++ 4064 Calls [Element.wrapIn] on each member of the collection, but clones the argument `what` for each one. 4065 +/ 4066 ElementCollection wrapIn(Element what) { 4067 foreach(e; elements) { 4068 e.wrapIn(what.cloneNode(false)); 4069 } 4070 4071 return this; 4072 } 4073 4074 /// Concatenates two ElementCollection together. 4075 ElementCollection opBinary(string op : "~")(ElementCollection rhs) { 4076 return ElementCollection(this.elements ~ rhs.elements); 4077 } 4078 } 4079 4080 4081 /// this puts in operators and opDispatch to handle string indexes and properties, forwarding to get and set functions. 4082 /// Group: implementations 4083 mixin template JavascriptStyleDispatch() { 4084 /// 4085 string opDispatch(string name)(string v = null) if(name != "popFront") { // popFront will make this look like a range. Do not want. 4086 if(v !is null) 4087 return set(name, v); 4088 return get(name); 4089 } 4090 4091 /// 4092 string opIndex(string key) const { 4093 return get(key); 4094 } 4095 4096 /// 4097 string opIndexAssign(string value, string field) { 4098 return set(field, value); 4099 } 4100 4101 // FIXME: doesn't seem to work 4102 string* opBinary(string op)(string key) if(op == "in") { 4103 return key in fields; 4104 } 4105 } 4106 4107 /// A proxy object to do the Element class' dataset property. See Element.dataset for more info. 4108 /// 4109 /// Do not create this object directly. 4110 /// Group: implementations 4111 struct DataSet { 4112 /// 4113 this(Element e) { 4114 this._element = e; 4115 } 4116 4117 private Element _element; 4118 /// 4119 string set(string name, string value) { 4120 _element.setAttribute("data-" ~ unCamelCase(name), value); 4121 return value; 4122 } 4123 4124 /// 4125 string get(string name) const { 4126 return _element.getAttribute("data-" ~ unCamelCase(name)); 4127 } 4128 4129 /// 4130 mixin JavascriptStyleDispatch!(); 4131 } 4132 4133 /// Proxy object for attributes which will replace the main opDispatch eventually 4134 /// Group: implementations 4135 struct AttributeSet { 4136 /// 4137 this(Element e) { 4138 this._element = e; 4139 } 4140 4141 private Element _element; 4142 /// 4143 string set(string name, string value) { 4144 _element.setAttribute(name, value); 4145 return value; 4146 } 4147 4148 /// 4149 string get(string name) const { 4150 return _element.getAttribute(name); 4151 } 4152 4153 /// 4154 mixin JavascriptStyleDispatch!(); 4155 } 4156 4157 4158 4159 /// for style, i want to be able to set it with a string like a plain attribute, 4160 /// but also be able to do properties Javascript style. 4161 4162 /// Group: implementations 4163 struct ElementStyle { 4164 this(Element parent) { 4165 _element = parent; 4166 } 4167 4168 Element _element; 4169 4170 @property ref inout(string) _attribute() inout { 4171 auto s = "style" in _element.attributes; 4172 if(s is null) { 4173 auto e = cast() _element; // const_cast 4174 e.attributes["style"] = ""; // we need something to reference 4175 s = cast(inout) ("style" in e.attributes); 4176 } 4177 4178 assert(s !is null); 4179 return *s; 4180 } 4181 4182 alias _attribute this; // this is meant to allow element.style = element.style ~ " string "; to still work. 4183 4184 string set(string name, string value) { 4185 if(name.length == 0) 4186 return value; 4187 if(name == "cssFloat") 4188 name = "float"; 4189 else 4190 name = unCamelCase(name); 4191 auto r = rules(); 4192 r[name] = value; 4193 4194 _attribute = ""; 4195 foreach(k, v; r) { 4196 if(v is null || v.length == 0) /* css can't do empty rules anyway so we'll use that to remove */ 4197 continue; 4198 if(_attribute.length) 4199 _attribute ~= " "; 4200 _attribute ~= k ~ ": " ~ v ~ ";"; 4201 } 4202 4203 _element.setAttribute("style", _attribute); // this is to trigger the observer call 4204 4205 return value; 4206 } 4207 string get(string name) const { 4208 if(name == "cssFloat") 4209 name = "float"; 4210 else 4211 name = unCamelCase(name); 4212 auto r = rules(); 4213 if(name in r) 4214 return r[name]; 4215 return null; 4216 } 4217 4218 string[string] rules() const { 4219 string[string] ret; 4220 foreach(rule; _attribute.split(";")) { 4221 rule = rule.strip(); 4222 if(rule.length == 0) 4223 continue; 4224 auto idx = rule.indexOf(":"); 4225 if(idx == -1) 4226 ret[rule] = ""; 4227 else { 4228 auto name = rule[0 .. idx].strip(); 4229 auto value = rule[idx + 1 .. $].strip(); 4230 4231 ret[name] = value; 4232 } 4233 } 4234 4235 return ret; 4236 } 4237 4238 mixin JavascriptStyleDispatch!(); 4239 } 4240 4241 /// Converts a camel cased propertyName to a css style dashed property-name 4242 string unCamelCase(string a) { 4243 string ret; 4244 foreach(c; a) 4245 if((c >= 'A' && c <= 'Z')) 4246 ret ~= "-" ~ toLower("" ~ c)[0]; 4247 else 4248 ret ~= c; 4249 return ret; 4250 } 4251 4252 /// Translates a css style property-name to a camel cased propertyName 4253 string camelCase(string a) { 4254 string ret; 4255 bool justSawDash = false; 4256 foreach(c; a) 4257 if(c == '-') { 4258 justSawDash = true; 4259 } else { 4260 if(justSawDash) { 4261 justSawDash = false; 4262 ret ~= toUpper("" ~ c); 4263 } else 4264 ret ~= c; 4265 } 4266 return ret; 4267 } 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 // domconvenience ends } 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 // @safe: 4290 4291 // NOTE: do *NOT* override toString on Element subclasses. It won't work. 4292 // Instead, override writeToAppender(); 4293 4294 // FIXME: should I keep processing instructions like <?blah ?> and <!-- blah --> (comments too lol)? I *want* them stripped out of most my output, but I want to be able to parse and create them too. 4295 4296 // Stripping them is useful for reading php as html.... but adding them 4297 // is good for building php. 4298 4299 // I need to maintain compatibility with the way it is now too. 4300 4301 import std.string; 4302 import std.exception; 4303 import std.uri; 4304 import std.array; 4305 import std.range; 4306 4307 //import std.stdio; 4308 4309 // tag soup works for most the crap I know now! If you have two bad closing tags back to back, it might erase one, but meh 4310 // that's rarer than the flipped closing tags that hack fixes so I'm ok with it. (Odds are it should be erased anyway; it's 4311 // most likely a typo so I say kill kill kill. 4312 4313 4314 /++ 4315 This might belong in another module, but it represents a file with a mime type and some data. 4316 Document implements this interface with type = text/html (see Document.contentType for more info) 4317 and data = document.toString, so you can return Documents anywhere web.d expects FileResources. 4318 +/ 4319 /// Group: bonus_functionality 4320 interface FileResource { 4321 /// the content-type of the file. e.g. "text/html; charset=utf-8" or "image/png" 4322 @property string contentType() const; 4323 /// the data 4324 immutable(ubyte)[] getData() const; 4325 /++ 4326 filename, return null if none 4327 4328 History: 4329 Added December 25, 2020 4330 +/ 4331 @property string filename() const; 4332 } 4333 4334 4335 4336 4337 ///. 4338 /// Group: bonus_functionality 4339 enum NodeType { Text = 3 } 4340 4341 4342 /// You can use this to do an easy null check or a dynamic cast+null check on any element. 4343 /// Group: core_functionality 4344 T require(T = Element, string file = __FILE__, int line = __LINE__)(Element e) if(is(T : Element)) 4345 in {} 4346 out(ret) { assert(ret !is null); } 4347 do { 4348 auto ret = cast(T) e; 4349 if(ret is null) 4350 throw new ElementNotFoundException(T.stringof, "passed value", e, file, line); 4351 return ret; 4352 } 4353 4354 4355 ///. 4356 /// Group: core_functionality 4357 class DocumentFragment : Element { 4358 ///. 4359 this(Document _parentDocument) { 4360 tagName = "#fragment"; 4361 super(_parentDocument); 4362 } 4363 4364 /++ 4365 Creates a document fragment from the given HTML. Note that the HTML is assumed to close all tags contained inside it. 4366 4367 Since: March 29, 2018 (or git tagged v2.1.0) 4368 +/ 4369 this(Html html) { 4370 this(null); 4371 4372 this.innerHTML = html.source; 4373 } 4374 4375 ///. 4376 override string writeToAppender(Appender!string where = appender!string()) const { 4377 return this.innerHTML(where); 4378 } 4379 4380 override string toPrettyStringImpl(bool insertComments, int indentationLevel, string indentWith) const { 4381 string s; 4382 foreach(child; children) 4383 s ~= child.toPrettyStringImpl(insertComments, indentationLevel, indentWith); 4384 return s; 4385 } 4386 4387 /// DocumentFragments don't really exist in a dom, so they ignore themselves in parent nodes 4388 /* 4389 override inout(Element) parentNode() inout { 4390 return children.length ? children[0].parentNode : null; 4391 } 4392 */ 4393 /+ 4394 override Element parentNode(Element p) { 4395 this.parentNode = p; 4396 foreach(child; children) 4397 child.parentNode = p; 4398 return p; 4399 } 4400 +/ 4401 } 4402 4403 /// Given text, encode all html entities on it - &, <, >, and ". This function also 4404 /// encodes all 8 bit characters as entities, thus ensuring the resultant text will work 4405 /// even if your charset isn't set right. You can suppress with by setting encodeNonAscii = false 4406 /// 4407 /// The output parameter can be given to append to an existing buffer. You don't have to 4408 /// pass one; regardless, the return value will be usable for you, with just the data encoded. 4409 /// Group: core_functionality 4410 string htmlEntitiesEncode(string data, Appender!string output = appender!string(), bool encodeNonAscii = true) { 4411 // if there's no entities, we can save a lot of time by not bothering with the 4412 // decoding loop. This check cuts the net toString time by better than half in my test. 4413 // let me know if it made your tests worse though, since if you use an entity in just about 4414 // every location, the check will add time... but I suspect the average experience is like mine 4415 // since the check gives up as soon as it can anyway. 4416 4417 bool shortcut = true; 4418 foreach(char c; data) { 4419 // non ascii chars are always higher than 127 in utf8; we'd better go to the full decoder if we see it. 4420 if(c == '<' || c == '>' || c == '"' || c == '&' || (encodeNonAscii && cast(uint) c > 127)) { 4421 shortcut = false; // there's actual work to be done 4422 break; 4423 } 4424 } 4425 4426 if(shortcut) { 4427 output.put(data); 4428 return data; 4429 } 4430 4431 auto start = output.data.length; 4432 4433 output.reserve(data.length + 64); // grab some extra space for the encoded entities 4434 4435 foreach(dchar d; data) { 4436 if(d == '&') 4437 output.put("&"); 4438 else if (d == '<') 4439 output.put("<"); 4440 else if (d == '>') 4441 output.put(">"); 4442 else if (d == '\"') 4443 output.put("""); 4444 // else if (d == '\'') 4445 // output.put("'"); // if you are in an attribute, it might be important to encode for the same reason as double quotes 4446 // FIXME: should I encode apostrophes too? as '... I could also do space but if your html is so bad that it doesn't 4447 // quote attributes at all, maybe you deserve the xss. Encoding spaces will make everything really ugly so meh 4448 // idk about apostrophes though. Might be worth it, might not. 4449 else if (!encodeNonAscii || (d < 128 && d > 0)) 4450 output.put(d); 4451 else 4452 output.put("&#" ~ std.conv.to!string(cast(int) d) ~ ";"); 4453 } 4454 4455 //assert(output !is null); // this fails on empty attributes..... 4456 return output.data[start .. $]; 4457 4458 // data = data.replace("\u00a0", " "); 4459 } 4460 4461 /// An alias for htmlEntitiesEncode; it works for xml too 4462 /// Group: core_functionality 4463 string xmlEntitiesEncode(string data) { 4464 return htmlEntitiesEncode(data); 4465 } 4466 4467 /// This helper function is used for decoding html entities. It has a hard-coded list of entities and characters. 4468 /// Group: core_functionality 4469 dchar parseEntity(in dchar[] entity) { 4470 4471 char[128] buffer; 4472 int bpos; 4473 foreach(char c; entity[1 .. $-1]) 4474 buffer[bpos++] = c; 4475 char[] entityAsString = buffer[0 .. bpos]; 4476 4477 int min = 0; 4478 int max = cast(int) availableEntities.length; 4479 4480 keep_looking: 4481 if(min + 1 < max) { 4482 int spot = (max - min) / 2 + min; 4483 if(availableEntities[spot] == entityAsString) { 4484 return availableEntitiesValues[spot]; 4485 } else if(entityAsString < availableEntities[spot]) { 4486 max = spot; 4487 goto keep_looking; 4488 } else { 4489 min = spot; 4490 goto keep_looking; 4491 } 4492 } 4493 4494 switch(entity[1..$-1]) { 4495 case "quot": 4496 return '"'; 4497 case "apos": 4498 return '\''; 4499 case "lt": 4500 return '<'; 4501 case "gt": 4502 return '>'; 4503 case "amp": 4504 return '&'; 4505 // the next are html rather than xml 4506 4507 // and handling numeric entities 4508 default: 4509 if(entity[1] == '#') { 4510 if(entity[2] == 'x' /*|| (!strict && entity[2] == 'X')*/) { 4511 auto hex = entity[3..$-1]; 4512 4513 auto p = intFromHex(to!string(hex).toLower()); 4514 return cast(dchar) p; 4515 } else { 4516 auto decimal = entity[2..$-1]; 4517 4518 // dealing with broken html entities 4519 while(decimal.length && (decimal[0] < '0' || decimal[0] > '9')) 4520 decimal = decimal[1 .. $]; 4521 4522 while(decimal.length && (decimal[$-1] < '0' || decimal[$-1] > '9')) 4523 decimal = decimal[0 .. $ - 1]; 4524 4525 if(decimal.length == 0) 4526 return ' '; // this is really broken html 4527 // done with dealing with broken stuff 4528 4529 auto p = std.conv.to!int(decimal); 4530 return cast(dchar) p; 4531 } 4532 } else 4533 return '\ufffd'; // replacement character diamond thing 4534 } 4535 4536 assert(0); 4537 } 4538 4539 unittest { 4540 // not in the binary search 4541 assert(parseEntity("""d) == '"'); 4542 4543 // numeric value 4544 assert(parseEntity("Դ") == '\u0534'); 4545 4546 // not found at all 4547 assert(parseEntity("&asdasdasd;"d) == '\ufffd'); 4548 4549 // random values in the bin search 4550 assert(parseEntity("	"d) == '\t'); 4551 assert(parseEntity("»"d) == '\»'); 4552 4553 // near the middle and edges of the bin search 4554 assert(parseEntity("𝒶"d) == '\U0001d4b6'); 4555 assert(parseEntity("*"d) == '\u002a'); 4556 assert(parseEntity("Æ"d) == '\u00c6'); 4557 assert(parseEntity("‌"d) == '\u200c'); 4558 } 4559 4560 import std.utf; 4561 import std.stdio; 4562 4563 /// This takes a string of raw HTML and decodes the entities into a nice D utf-8 string. 4564 /// By default, it uses loose mode - it will try to return a useful string from garbage input too. 4565 /// Set the second parameter to true if you'd prefer it to strictly throw exceptions on garbage input. 4566 /// Group: core_functionality 4567 string htmlEntitiesDecode(string data, bool strict = false) { 4568 // this check makes a *big* difference; about a 50% improvement of parse speed on my test. 4569 if(data.indexOf("&") == -1) // all html entities begin with & 4570 return data; // if there are no entities in here, we can return the original slice and save some time 4571 4572 char[] a; // this seems to do a *better* job than appender! 4573 4574 char[4] buffer; 4575 4576 bool tryingEntity = false; 4577 bool tryingNumericEntity = false; 4578 bool tryingHexEntity = false; 4579 dchar[16] entityBeingTried; 4580 int entityBeingTriedLength = 0; 4581 int entityAttemptIndex = 0; 4582 4583 foreach(dchar ch; data) { 4584 if(tryingEntity) { 4585 entityAttemptIndex++; 4586 entityBeingTried[entityBeingTriedLength++] = ch; 4587 4588 if(entityBeingTriedLength == 2 && ch == '#') { 4589 tryingNumericEntity = true; 4590 continue; 4591 } else if(tryingNumericEntity && entityBeingTriedLength == 3 && ch == 'x') { 4592 tryingHexEntity = true; 4593 continue; 4594 } 4595 4596 // I saw some crappy html in the wild that looked like &0ї this tries to handle that. 4597 if(ch == '&') { 4598 if(strict) 4599 throw new Exception("unterminated entity; & inside another at " ~ to!string(entityBeingTried[0 .. entityBeingTriedLength])); 4600 4601 // if not strict, let's try to parse both. 4602 4603 if(entityBeingTried[0 .. entityBeingTriedLength] == "&&") { 4604 a ~= "&"; // double amp means keep the first one, still try to parse the next one 4605 } else { 4606 auto ch2 = parseEntity(entityBeingTried[0 .. entityBeingTriedLength]); 4607 if(ch2 == '\ufffd') { // either someone put this in intentionally (lol) or we failed to get it 4608 // but either way, just abort and keep the plain text 4609 foreach(char c; entityBeingTried[0 .. entityBeingTriedLength - 1]) // cut off the & we're on now 4610 a ~= c; 4611 } else { 4612 a ~= buffer[0.. std.utf.encode(buffer, ch2)]; 4613 } 4614 } 4615 4616 // tryingEntity is still true 4617 goto new_entity; 4618 } else 4619 if(ch == ';') { 4620 tryingEntity = false; 4621 a ~= buffer[0.. std.utf.encode(buffer, parseEntity(entityBeingTried[0 .. entityBeingTriedLength]))]; 4622 } else if(ch == ' ') { 4623 // e.g. you & i 4624 if(strict) 4625 throw new Exception("unterminated entity at " ~ to!string(entityBeingTried[0 .. entityBeingTriedLength])); 4626 else { 4627 tryingEntity = false; 4628 a ~= to!(char[])(entityBeingTried[0 .. entityBeingTriedLength - 1]); 4629 a ~= buffer[0 .. std.utf.encode(buffer, ch)]; 4630 } 4631 } else { 4632 if(tryingNumericEntity) { 4633 if(ch < '0' || ch > '9') { 4634 if(tryingHexEntity) { 4635 if(ch < 'A') 4636 goto trouble; 4637 if(ch > 'Z' && ch < 'a') 4638 goto trouble; 4639 if(ch > 'z') 4640 goto trouble; 4641 } else { 4642 trouble: 4643 if(strict) 4644 throw new Exception("unterminated entity at " ~ to!string(entityBeingTried[0 .. entityBeingTriedLength])); 4645 tryingEntity = false; 4646 a ~= buffer[0.. std.utf.encode(buffer, parseEntity(entityBeingTried[0 .. entityBeingTriedLength]))]; 4647 a ~= ch; 4648 continue; 4649 } 4650 } 4651 } 4652 4653 4654 if(entityAttemptIndex >= 9) { 4655 done: 4656 if(strict) 4657 throw new Exception("unterminated entity at " ~ to!string(entityBeingTried[0 .. entityBeingTriedLength])); 4658 else { 4659 tryingEntity = false; 4660 a ~= to!(char[])(entityBeingTried[0 .. entityBeingTriedLength]); 4661 } 4662 } 4663 } 4664 } else { 4665 if(ch == '&') { 4666 new_entity: 4667 tryingEntity = true; 4668 tryingNumericEntity = false; 4669 tryingHexEntity = false; 4670 entityBeingTriedLength = 0; 4671 entityBeingTried[entityBeingTriedLength++] = ch; 4672 entityAttemptIndex = 0; 4673 } else { 4674 a ~= buffer[0 .. std.utf.encode(buffer, ch)]; 4675 } 4676 } 4677 } 4678 4679 if(tryingEntity) { 4680 if(strict) 4681 throw new Exception("unterminated entity at " ~ to!string(entityBeingTried[0 .. entityBeingTriedLength])); 4682 4683 // otherwise, let's try to recover, at least so we don't drop any data 4684 a ~= to!string(entityBeingTried[0 .. entityBeingTriedLength]); 4685 // FIXME: what if we have "cool &"? should we try to parse it? 4686 } 4687 4688 return cast(string) a; // assumeUnique is actually kinda slow, lol 4689 } 4690 4691 unittest { 4692 // error recovery 4693 assert(htmlEntitiesDecode("<&foo") == "<&foo"); // unterminated turned back to thing 4694 assert(htmlEntitiesDecode("<&foo") == "<&foo"); // semi-terminated... parse and carry on (is this really sane?) 4695 assert(htmlEntitiesDecode("loc=en_us&tracknum=111") == "loc=en_us&tracknum=111"); // a bit of both, seen in a real life email 4696 assert(htmlEntitiesDecode("& test") == "& test"); // unterminated, just abort 4697 4698 // in strict mode all of these should fail 4699 try { assert(htmlEntitiesDecode("<&foo", true) == "<&foo"); assert(0); } catch(Exception e) { } 4700 try { assert(htmlEntitiesDecode("<&foo", true) == "<&foo"); assert(0); } catch(Exception e) { } 4701 try { assert(htmlEntitiesDecode("loc=en_us&tracknum=111", true) == "<&foo"); assert(0); } catch(Exception e) { } 4702 try { assert(htmlEntitiesDecode("& test", true) == "& test"); assert(0); } catch(Exception e) { } 4703 4704 // correct cases that should pass the same in strict or loose mode 4705 foreach(strict; [false, true]) { 4706 assert(htmlEntitiesDecode("&hello» win", strict) == "&hello\» win"); 4707 } 4708 } 4709 4710 /// Group: implementations 4711 abstract class SpecialElement : Element { 4712 this(Document _parentDocument) { 4713 super(_parentDocument); 4714 } 4715 4716 ///. 4717 override Element appendChild(Element e) { 4718 assert(0, "Cannot append to a special node"); 4719 } 4720 4721 ///. 4722 @property override int nodeType() const { 4723 return 100; 4724 } 4725 } 4726 4727 ///. 4728 /// Group: implementations 4729 class RawSource : SpecialElement { 4730 ///. 4731 this(Document _parentDocument, string s) { 4732 super(_parentDocument); 4733 source = s; 4734 tagName = "#raw"; 4735 } 4736 4737 ///. 4738 override string nodeValue() const { 4739 return this.toString(); 4740 } 4741 4742 ///. 4743 override string writeToAppender(Appender!string where = appender!string()) const { 4744 where.put(source); 4745 return source; 4746 } 4747 4748 override string toPrettyStringImpl(bool, int, string) const { 4749 return source; 4750 } 4751 4752 4753 override RawSource cloneNode(bool deep) { 4754 return new RawSource(parentDocument, source); 4755 } 4756 4757 ///. 4758 string source; 4759 } 4760 4761 /// Group: implementations 4762 abstract class ServerSideCode : SpecialElement { 4763 this(Document _parentDocument, string type) { 4764 super(_parentDocument); 4765 tagName = "#" ~ type; 4766 } 4767 4768 ///. 4769 override string nodeValue() const { 4770 return this.source; 4771 } 4772 4773 ///. 4774 override string writeToAppender(Appender!string where = appender!string()) const { 4775 auto start = where.data.length; 4776 where.put("<"); 4777 where.put(source); 4778 where.put(">"); 4779 return where.data[start .. $]; 4780 } 4781 4782 override string toPrettyStringImpl(bool, int, string) const { 4783 return "<" ~ source ~ ">"; 4784 } 4785 4786 ///. 4787 string source; 4788 } 4789 4790 ///. 4791 /// Group: implementations 4792 class PhpCode : ServerSideCode { 4793 ///. 4794 this(Document _parentDocument, string s) { 4795 super(_parentDocument, "php"); 4796 source = s; 4797 } 4798 4799 override PhpCode cloneNode(bool deep) { 4800 return new PhpCode(parentDocument, source); 4801 } 4802 } 4803 4804 ///. 4805 /// Group: implementations 4806 class AspCode : ServerSideCode { 4807 ///. 4808 this(Document _parentDocument, string s) { 4809 super(_parentDocument, "asp"); 4810 source = s; 4811 } 4812 4813 override AspCode cloneNode(bool deep) { 4814 return new AspCode(parentDocument, source); 4815 } 4816 } 4817 4818 ///. 4819 /// Group: implementations 4820 class BangInstruction : SpecialElement { 4821 ///. 4822 this(Document _parentDocument, string s) { 4823 super(_parentDocument); 4824 source = s; 4825 tagName = "#bpi"; 4826 } 4827 4828 ///. 4829 override string nodeValue() const { 4830 return this.source; 4831 } 4832 4833 override BangInstruction cloneNode(bool deep) { 4834 return new BangInstruction(parentDocument, source); 4835 } 4836 4837 ///. 4838 override string writeToAppender(Appender!string where = appender!string()) const { 4839 auto start = where.data.length; 4840 where.put("<!"); 4841 where.put(source); 4842 where.put(">"); 4843 return where.data[start .. $]; 4844 } 4845 4846 override string toPrettyStringImpl(bool, int, string) const { 4847 string s; 4848 s ~= "<!"; 4849 s ~= source; 4850 s ~= ">"; 4851 return s; 4852 } 4853 4854 ///. 4855 string source; 4856 } 4857 4858 ///. 4859 /// Group: implementations 4860 class QuestionInstruction : SpecialElement { 4861 ///. 4862 this(Document _parentDocument, string s) { 4863 super(_parentDocument); 4864 source = s; 4865 tagName = "#qpi"; 4866 } 4867 4868 override QuestionInstruction cloneNode(bool deep) { 4869 return new QuestionInstruction(parentDocument, source); 4870 } 4871 4872 ///. 4873 override string nodeValue() const { 4874 return this.source; 4875 } 4876 4877 ///. 4878 override string writeToAppender(Appender!string where = appender!string()) const { 4879 auto start = where.data.length; 4880 where.put("<"); 4881 where.put(source); 4882 where.put(">"); 4883 return where.data[start .. $]; 4884 } 4885 4886 override string toPrettyStringImpl(bool, int, string) const { 4887 string s; 4888 s ~= "<"; 4889 s ~= source; 4890 s ~= ">"; 4891 return s; 4892 } 4893 4894 4895 ///. 4896 string source; 4897 } 4898 4899 ///. 4900 /// Group: implementations 4901 class HtmlComment : SpecialElement { 4902 ///. 4903 this(Document _parentDocument, string s) { 4904 super(_parentDocument); 4905 source = s; 4906 tagName = "#comment"; 4907 } 4908 4909 override HtmlComment cloneNode(bool deep) { 4910 return new HtmlComment(parentDocument, source); 4911 } 4912 4913 ///. 4914 override string nodeValue() const { 4915 return this.source; 4916 } 4917 4918 ///. 4919 override string writeToAppender(Appender!string where = appender!string()) const { 4920 auto start = where.data.length; 4921 where.put("<!--"); 4922 where.put(source); 4923 where.put("-->"); 4924 return where.data[start .. $]; 4925 } 4926 4927 override string toPrettyStringImpl(bool, int, string) const { 4928 string s; 4929 s ~= "<!--"; 4930 s ~= source; 4931 s ~= "-->"; 4932 return s; 4933 } 4934 4935 4936 ///. 4937 string source; 4938 } 4939 4940 4941 4942 4943 ///. 4944 /// Group: implementations 4945 class TextNode : Element { 4946 public: 4947 ///. 4948 this(Document _parentDocument, string e) { 4949 super(_parentDocument); 4950 contents = e; 4951 tagName = "#text"; 4952 } 4953 4954 /// 4955 this(string e) { 4956 this(null, e); 4957 } 4958 4959 string opDispatch(string name)(string v = null) if(0) { return null; } // text nodes don't have attributes 4960 4961 ///. 4962 static TextNode fromUndecodedString(Document _parentDocument, string html) { 4963 auto e = new TextNode(_parentDocument, ""); 4964 e.contents = htmlEntitiesDecode(html, _parentDocument is null ? false : !_parentDocument.loose); 4965 return e; 4966 } 4967 4968 ///. 4969 override @property TextNode cloneNode(bool deep) { 4970 auto n = new TextNode(parentDocument, contents); 4971 return n; 4972 } 4973 4974 ///. 4975 override string nodeValue() const { 4976 return this.contents; //toString(); 4977 } 4978 4979 ///. 4980 @property override int nodeType() const { 4981 return NodeType.Text; 4982 } 4983 4984 ///. 4985 override string writeToAppender(Appender!string where = appender!string()) const { 4986 string s; 4987 if(contents.length) 4988 s = htmlEntitiesEncode(contents, where); 4989 else 4990 s = ""; 4991 4992 assert(s !is null); 4993 return s; 4994 } 4995 4996 override string toPrettyStringImpl(bool insertComments = false, int indentationLevel = 0, string indentWith = "\t") const { 4997 string s; 4998 4999 string contents = this.contents; 5000 // we will first collapse the whitespace per html 5001 // sort of. note this can break stuff yo!!!! 5002 if(this.parentNode is null || this.parentNode.tagName != "pre") { 5003 string n = ""; 5004 bool lastWasWhitespace = indentationLevel > 0; 5005 foreach(char c; contents) { 5006 if(c.isSimpleWhite) { 5007 if(!lastWasWhitespace) 5008 n ~= ' '; 5009 lastWasWhitespace = true; 5010 } else { 5011 n ~= c; 5012 lastWasWhitespace = false; 5013 } 5014 } 5015 5016 contents = n; 5017 } 5018 5019 if(this.parentNode !is null && this.parentNode.tagName != "p") { 5020 contents = contents.strip; 5021 } 5022 5023 auto e = htmlEntitiesEncode(contents); 5024 import std.algorithm.iteration : splitter; 5025 bool first = true; 5026 foreach(line; splitter(e, "\n")) { 5027 if(first) { 5028 s ~= toPrettyStringIndent(insertComments, indentationLevel, indentWith); 5029 first = false; 5030 } else { 5031 s ~= "\n"; 5032 if(insertComments) 5033 s ~= "<!--"; 5034 foreach(i; 0 .. indentationLevel) 5035 s ~= "\t"; 5036 if(insertComments) 5037 s ~= "-->"; 5038 } 5039 s ~= line.stripRight; 5040 } 5041 return s; 5042 } 5043 5044 ///. 5045 override Element appendChild(Element e) { 5046 assert(0, "Cannot append to a text node"); 5047 } 5048 5049 ///. 5050 string contents; 5051 // alias contents content; // I just mistype this a lot, 5052 } 5053 5054 /** 5055 There are subclasses of Element offering improved helper 5056 functions for the element in HTML. 5057 */ 5058 5059 /++ 5060 Represents a HTML link. This provides some convenience methods for manipulating query strings, but otherwise is sthe same Element interface. 5061 5062 Please note this object may not be used for all `<a>` tags. 5063 +/ 5064 /// Group: implementations 5065 class Link : Element { 5066 5067 /++ 5068 Constructs `<a href="that href">that text</a>`. 5069 +/ 5070 this(string href, string text) { 5071 super("a"); 5072 setAttribute("href", href); 5073 innerText = text; 5074 } 5075 5076 /// ditto 5077 this(Document _parentDocument) { 5078 super(_parentDocument); 5079 this.tagName = "a"; 5080 } 5081 5082 /+ 5083 /// Returns everything in the href EXCEPT the query string 5084 @property string targetSansQuery() { 5085 5086 } 5087 5088 ///. 5089 @property string domainName() { 5090 5091 } 5092 5093 ///. 5094 @property string path 5095 +/ 5096 /// This gets a variable from the URL's query string. 5097 string getValue(string name) { 5098 auto vars = variablesHash(); 5099 if(name in vars) 5100 return vars[name]; 5101 return null; 5102 } 5103 5104 private string[string] variablesHash() { 5105 string href = getAttribute("href"); 5106 if(href is null) 5107 return null; 5108 5109 auto ques = href.indexOf("?"); 5110 string str = ""; 5111 if(ques != -1) { 5112 str = href[ques+1..$]; 5113 5114 auto fragment = str.indexOf("#"); 5115 if(fragment != -1) 5116 str = str[0..fragment]; 5117 } 5118 5119 string[] variables = str.split("&"); 5120 5121 string[string] hash; 5122 5123 foreach(var; variables) { 5124 auto index = var.indexOf("="); 5125 if(index == -1) 5126 hash[var] = ""; 5127 else { 5128 hash[decodeComponent(var[0..index])] = decodeComponent(var[index + 1 .. $]); 5129 } 5130 } 5131 5132 return hash; 5133 } 5134 5135 /// Replaces all the stuff after a ? in the link at once with the given assoc array values. 5136 /*private*/ void updateQueryString(string[string] vars) { 5137 string href = getAttribute("href"); 5138 5139 auto question = href.indexOf("?"); 5140 if(question != -1) 5141 href = href[0..question]; 5142 5143 string frag = ""; 5144 auto fragment = href.indexOf("#"); 5145 if(fragment != -1) { 5146 frag = href[fragment..$]; 5147 href = href[0..fragment]; 5148 } 5149 5150 string query = "?"; 5151 bool first = true; 5152 foreach(name, value; vars) { 5153 if(!first) 5154 query ~= "&"; 5155 else 5156 first = false; 5157 5158 query ~= encodeComponent(name); 5159 if(value.length) 5160 query ~= "=" ~ encodeComponent(value); 5161 } 5162 5163 if(query != "?") 5164 href ~= query; 5165 5166 href ~= frag; 5167 5168 setAttribute("href", href); 5169 } 5170 5171 /// Sets or adds the variable with the given name to the given value 5172 /// It automatically URI encodes the values and takes care of the ? and &. 5173 override void setValue(string name, string variable) { 5174 auto vars = variablesHash(); 5175 vars[name] = variable; 5176 5177 updateQueryString(vars); 5178 } 5179 5180 /// Removes the given variable from the query string 5181 void removeValue(string name) { 5182 auto vars = variablesHash(); 5183 vars.remove(name); 5184 5185 updateQueryString(vars); 5186 } 5187 5188 /* 5189 ///. 5190 override string toString() { 5191 5192 } 5193 5194 ///. 5195 override string getAttribute(string name) { 5196 if(name == "href") { 5197 5198 } else 5199 return super.getAttribute(name); 5200 } 5201 */ 5202 } 5203 5204 /++ 5205 Represents a HTML form. This slightly specializes Element to add a few more convenience methods for adding and extracting form data. 5206 5207 Please note this object may not be used for all `<form>` tags. 5208 +/ 5209 /// Group: implementations 5210 class Form : Element { 5211 5212 ///. 5213 this(Document _parentDocument) { 5214 super(_parentDocument); 5215 tagName = "form"; 5216 } 5217 5218 /// Overrides of the base class implementations that more confirm to *my* conventions when writing form html. 5219 override Element addField(string label, string name, string type = "text", FormFieldOptions fieldOptions = FormFieldOptions.none) { 5220 auto t = this.querySelector("fieldset div"); 5221 if(t is null) 5222 return super.addField(label, name, type, fieldOptions); 5223 else 5224 return t.addField(label, name, type, fieldOptions); 5225 } 5226 5227 /// ditto 5228 override Element addField(string label, string name, FormFieldOptions fieldOptions) { 5229 auto type = "text"; 5230 auto t = this.querySelector("fieldset div"); 5231 if(t is null) 5232 return super.addField(label, name, type, fieldOptions); 5233 else 5234 return t.addField(label, name, type, fieldOptions); 5235 } 5236 5237 /// ditto 5238 override Element addField(string label, string name, string[string] options, FormFieldOptions fieldOptions = FormFieldOptions.none) { 5239 auto t = this.querySelector("fieldset div"); 5240 if(t is null) 5241 return super.addField(label, name, options, fieldOptions); 5242 else 5243 return t.addField(label, name, options, fieldOptions); 5244 } 5245 5246 /// ditto 5247 override void setValue(string field, string value) { 5248 setValue(field, value, true); 5249 } 5250 5251 // FIXME: doesn't handle arrays; multiple fields can have the same name 5252 5253 /// Set's the form field's value. For input boxes, this sets the value attribute. For 5254 /// textareas, it sets the innerText. For radio boxes and select boxes, it removes 5255 /// the checked/selected attribute from all, and adds it to the one matching the value. 5256 /// For checkboxes, if the value is non-null and not empty, it checks the box. 5257 5258 /// If you set a value that doesn't exist, it throws an exception if makeNew is false. 5259 /// Otherwise, it makes a new input with type=hidden to keep the value. 5260 void setValue(string field, string value, bool makeNew) { 5261 auto eles = getField(field); 5262 if(eles.length == 0) { 5263 if(makeNew) { 5264 addInput(field, value); 5265 return; 5266 } else 5267 throw new Exception("form field does not exist"); 5268 } 5269 5270 if(eles.length == 1) { 5271 auto e = eles[0]; 5272 switch(e.tagName) { 5273 default: assert(0); 5274 case "textarea": 5275 e.innerText = value; 5276 break; 5277 case "input": 5278 string type = e.getAttribute("type"); 5279 if(type is null) { 5280 e.value = value; 5281 return; 5282 } 5283 switch(type) { 5284 case "checkbox": 5285 case "radio": 5286 if(value.length && value != "false") 5287 e.setAttribute("checked", "checked"); 5288 else 5289 e.removeAttribute("checked"); 5290 break; 5291 default: 5292 e.value = value; 5293 return; 5294 } 5295 break; 5296 case "select": 5297 bool found = false; 5298 foreach(child; e.tree) { 5299 if(child.tagName != "option") 5300 continue; 5301 string val = child.getAttribute("value"); 5302 if(val is null) 5303 val = child.innerText; 5304 if(val == value) { 5305 child.setAttribute("selected", "selected"); 5306 found = true; 5307 } else 5308 child.removeAttribute("selected"); 5309 } 5310 5311 if(!found) { 5312 e.addChild("option", value) 5313 .setAttribute("selected", "selected"); 5314 } 5315 break; 5316 } 5317 } else { 5318 // assume radio boxes 5319 foreach(e; eles) { 5320 string val = e.getAttribute("value"); 5321 //if(val is null) 5322 // throw new Exception("don't know what to do with radio boxes with null value"); 5323 if(val == value) 5324 e.setAttribute("checked", "checked"); 5325 else 5326 e.removeAttribute("checked"); 5327 } 5328 } 5329 } 5330 5331 /// This takes an array of strings and adds hidden <input> elements for each one of them. Unlike setValue, 5332 /// it makes no attempt to find and modify existing elements in the form to the new values. 5333 void addValueArray(string key, string[] arrayOfValues) { 5334 foreach(arr; arrayOfValues) 5335 addChild("input", key, arr); 5336 } 5337 5338 /// Gets the value of the field; what would be given if it submitted right now. (so 5339 /// it handles select boxes and radio buttons too). For checkboxes, if a value isn't 5340 /// given, but it is checked, it returns "checked", since null and "" are indistinguishable 5341 string getValue(string field) { 5342 auto eles = getField(field); 5343 if(eles.length == 0) 5344 return ""; 5345 if(eles.length == 1) { 5346 auto e = eles[0]; 5347 switch(e.tagName) { 5348 default: assert(0); 5349 case "input": 5350 if(e.type == "checkbox") { 5351 if(e.checked) 5352 return e.value.length ? e.value : "checked"; 5353 return ""; 5354 } else 5355 return e.value; 5356 case "textarea": 5357 return e.innerText; 5358 case "select": 5359 foreach(child; e.tree) { 5360 if(child.tagName != "option") 5361 continue; 5362 if(child.selected) 5363 return child.value; 5364 } 5365 break; 5366 } 5367 } else { 5368 // assuming radio 5369 foreach(e; eles) { 5370 if(e.checked) 5371 return e.value; 5372 } 5373 } 5374 5375 return ""; 5376 } 5377 5378 // FIXME: doesn't handle multiple elements with the same name (except radio buttons) 5379 /++ 5380 Returns the form's contents in application/x-www-form-urlencoded format. 5381 5382 Bugs: 5383 Doesn't handle repeated elements of the same name nor files. 5384 +/ 5385 string getPostableData() { 5386 bool[string] namesDone; 5387 5388 string ret; 5389 bool outputted = false; 5390 5391 foreach(e; getElementsBySelector("[name]")) { 5392 if(e.name in namesDone) 5393 continue; 5394 5395 if(outputted) 5396 ret ~= "&"; 5397 else 5398 outputted = true; 5399 5400 ret ~= std.uri.encodeComponent(e.name) ~ "=" ~ std.uri.encodeComponent(getValue(e.name)); 5401 5402 namesDone[e.name] = true; 5403 } 5404 5405 return ret; 5406 } 5407 5408 /// Gets the actual elements with the given name 5409 Element[] getField(string name) { 5410 Element[] ret; 5411 foreach(e; tree) { 5412 if(e.name == name) 5413 ret ~= e; 5414 } 5415 return ret; 5416 } 5417 5418 /// Grabs the <label> with the given for tag, if there is one. 5419 Element getLabel(string forId) { 5420 foreach(e; tree) 5421 if(e.tagName == "label" && e.getAttribute("for") == forId) 5422 return e; 5423 return null; 5424 } 5425 5426 /// Adds a new INPUT field to the end of the form with the given attributes. 5427 Element addInput(string name, string value, string type = "hidden") { 5428 auto e = new Element(parentDocument, "input", null, true); 5429 e.name = name; 5430 e.value = value; 5431 e.type = type; 5432 5433 appendChild(e); 5434 5435 return e; 5436 } 5437 5438 /// Removes the given field from the form. It finds the element and knocks it right out. 5439 void removeField(string name) { 5440 foreach(e; getField(name)) 5441 e.parentNode.removeChild(e); 5442 } 5443 5444 /+ 5445 /// Returns all form members. 5446 @property Element[] elements() { 5447 5448 } 5449 5450 ///. 5451 string opDispatch(string name)(string v = null) 5452 // filter things that should actually be attributes on the form 5453 if( name != "method" && name != "action" && name != "enctype" 5454 && name != "style" && name != "name" && name != "id" && name != "class") 5455 { 5456 5457 } 5458 +/ 5459 /+ 5460 void submit() { 5461 // take its elements and submit them through http 5462 } 5463 +/ 5464 } 5465 5466 import std.conv; 5467 5468 /++ 5469 Represents a HTML table. Has some convenience methods for working with tabular data. 5470 +/ 5471 /// Group: implementations 5472 class Table : Element { 5473 5474 /// You can make this yourself but you'd generally get one of these object out of a html parse or [Element.make] call. 5475 this(Document _parentDocument) { 5476 super(_parentDocument); 5477 tagName = "table"; 5478 } 5479 5480 /++ 5481 Creates an element with the given type and content. The argument can be an Element, Html, or other data which is converted to text with `to!string` 5482 5483 The element is $(I not) appended to the table. 5484 +/ 5485 Element th(T)(T t) { 5486 Element e; 5487 if(parentDocument !is null) 5488 e = parentDocument.createElement("th"); 5489 else 5490 e = Element.make("th"); 5491 static if(is(T == Html)) 5492 e.innerHTML = t; 5493 else static if(is(T : Element)) 5494 e.appendChild(t); 5495 else 5496 e.innerText = to!string(t); 5497 return e; 5498 } 5499 5500 /// ditto 5501 Element td(T)(T t) { 5502 Element e; 5503 if(parentDocument !is null) 5504 e = parentDocument.createElement("td"); 5505 else 5506 e = Element.make("td"); 5507 static if(is(T == Html)) 5508 e.innerHTML = t; 5509 else static if(is(T : Element)) 5510 e.appendChild(t); 5511 else 5512 e.innerText = to!string(t); 5513 return e; 5514 } 5515 5516 /++ 5517 Passes each argument to the [th] method for `appendHeaderRow` or [td] method for the others, appends them all to the `<tbody>` element for `appendRow`, `<thead>` element for `appendHeaderRow`, or a `<tfoot>` element for `appendFooterRow`, and ensures it is appended it to the table. 5518 +/ 5519 Element appendHeaderRow(T...)(T t) { 5520 return appendRowInternal("th", "thead", t); 5521 } 5522 5523 /// ditto 5524 Element appendFooterRow(T...)(T t) { 5525 return appendRowInternal("td", "tfoot", t); 5526 } 5527 5528 /// ditto 5529 Element appendRow(T...)(T t) { 5530 return appendRowInternal("td", "tbody", t); 5531 } 5532 5533 /++ 5534 Takes each argument as a class name and calls [Element.addClass] for each element in the column associated with that index. 5535 5536 Please note this does not use the html `<col>` element. 5537 +/ 5538 void addColumnClasses(string[] classes...) { 5539 auto grid = getGrid(); 5540 foreach(row; grid) 5541 foreach(i, cl; classes) { 5542 if(cl.length) 5543 if(i < row.length) 5544 row[i].addClass(cl); 5545 } 5546 } 5547 5548 private Element appendRowInternal(T...)(string innerType, string findType, T t) { 5549 Element row = Element.make("tr"); 5550 5551 foreach(e; t) { 5552 static if(is(typeof(e) : Element)) { 5553 if(e.tagName == "td" || e.tagName == "th") 5554 row.appendChild(e); 5555 else { 5556 Element a = Element.make(innerType); 5557 5558 a.appendChild(e); 5559 5560 row.appendChild(a); 5561 } 5562 } else static if(is(typeof(e) == Html)) { 5563 Element a = Element.make(innerType); 5564 a.innerHTML = e.source; 5565 row.appendChild(a); 5566 } else static if(is(typeof(e) == Element[])) { 5567 Element a = Element.make(innerType); 5568 foreach(ele; e) 5569 a.appendChild(ele); 5570 row.appendChild(a); 5571 } else static if(is(typeof(e) == string[])) { 5572 foreach(ele; e) { 5573 Element a = Element.make(innerType); 5574 a.innerText = to!string(ele); 5575 row.appendChild(a); 5576 } 5577 } else { 5578 Element a = Element.make(innerType); 5579 a.innerText = to!string(e); 5580 row.appendChild(a); 5581 } 5582 } 5583 5584 foreach(e; children) { 5585 if(e.tagName == findType) { 5586 e.appendChild(row); 5587 return row; 5588 } 5589 } 5590 5591 // the type was not found if we are here... let's add it so it is well-formed 5592 auto lol = this.addChild(findType); 5593 lol.appendChild(row); 5594 5595 return row; 5596 } 5597 5598 /// Returns the `<caption>` element of the table, creating one if it isn't there. 5599 Element captionElement() { 5600 Element cap; 5601 foreach(c; children) { 5602 if(c.tagName == "caption") { 5603 cap = c; 5604 break; 5605 } 5606 } 5607 5608 if(cap is null) { 5609 cap = Element.make("caption"); 5610 appendChild(cap); 5611 } 5612 5613 return cap; 5614 } 5615 5616 /// Returns or sets the text inside the `<caption>` element, creating that element if it isnt' there. 5617 @property string caption() { 5618 return captionElement().innerText; 5619 } 5620 5621 /// ditto 5622 @property void caption(string text) { 5623 captionElement().innerText = text; 5624 } 5625 5626 /// Gets the logical layout of the table as a rectangular grid of 5627 /// cells. It considers rowspan and colspan. A cell with a large 5628 /// span is represented in the grid by being referenced several times. 5629 /// The tablePortition parameter can get just a <thead>, <tbody>, or 5630 /// <tfoot> portion if you pass one. 5631 /// 5632 /// Note: the rectangular grid might include null cells. 5633 /// 5634 /// This is kinda expensive so you should call once when you want the grid, 5635 /// then do lookups on the returned array. 5636 TableCell[][] getGrid(Element tablePortition = null) 5637 in { 5638 if(tablePortition is null) 5639 assert(tablePortition is null); 5640 else { 5641 assert(tablePortition !is null); 5642 assert(tablePortition.parentNode is this); 5643 assert( 5644 tablePortition.tagName == "tbody" 5645 || 5646 tablePortition.tagName == "tfoot" 5647 || 5648 tablePortition.tagName == "thead" 5649 ); 5650 } 5651 } 5652 do { 5653 if(tablePortition is null) 5654 tablePortition = this; 5655 5656 TableCell[][] ret; 5657 5658 // FIXME: will also return rows of sub tables! 5659 auto rows = tablePortition.getElementsByTagName("tr"); 5660 ret.length = rows.length; 5661 5662 int maxLength = 0; 5663 5664 int insertCell(int row, int position, TableCell cell) { 5665 if(row >= ret.length) 5666 return position; // not supposed to happen - a rowspan is prolly too big. 5667 5668 if(position == -1) { 5669 position++; 5670 foreach(item; ret[row]) { 5671 if(item is null) 5672 break; 5673 position++; 5674 } 5675 } 5676 5677 if(position < ret[row].length) 5678 ret[row][position] = cell; 5679 else 5680 foreach(i; ret[row].length .. position + 1) { 5681 if(i == position) 5682 ret[row] ~= cell; 5683 else 5684 ret[row] ~= null; 5685 } 5686 return position; 5687 } 5688 5689 foreach(i, rowElement; rows) { 5690 auto row = cast(TableRow) rowElement; 5691 assert(row !is null); 5692 assert(i < ret.length); 5693 5694 int position = 0; 5695 foreach(cellElement; rowElement.childNodes) { 5696 auto cell = cast(TableCell) cellElement; 5697 if(cell is null) 5698 continue; 5699 5700 // FIXME: colspan == 0 or rowspan == 0 5701 // is supposed to mean fill in the rest of 5702 // the table, not skip it 5703 foreach(int j; 0 .. cell.colspan) { 5704 foreach(int k; 0 .. cell.rowspan) 5705 // if the first row, always append. 5706 insertCell(k + cast(int) i, k == 0 ? -1 : position, cell); 5707 position++; 5708 } 5709 } 5710 5711 if(ret[i].length > maxLength) 5712 maxLength = cast(int) ret[i].length; 5713 } 5714 5715 // want to ensure it's rectangular 5716 foreach(ref r; ret) { 5717 foreach(i; r.length .. maxLength) 5718 r ~= null; 5719 } 5720 5721 return ret; 5722 } 5723 } 5724 5725 /// Represents a table row element - a <tr> 5726 /// Group: implementations 5727 class TableRow : Element { 5728 ///. 5729 this(Document _parentDocument) { 5730 super(_parentDocument); 5731 tagName = "tr"; 5732 } 5733 5734 // FIXME: the standard says there should be a lot more in here, 5735 // but meh, I never use it and it's a pain to implement. 5736 } 5737 5738 /// Represents anything that can be a table cell - <td> or <th> html. 5739 /// Group: implementations 5740 class TableCell : Element { 5741 ///. 5742 this(Document _parentDocument, string _tagName) { 5743 super(_parentDocument, _tagName); 5744 } 5745 5746 /// Gets and sets the row/colspan attributes as integers 5747 @property int rowspan() const { 5748 int ret = 1; 5749 auto it = getAttribute("rowspan"); 5750 if(it.length) 5751 ret = to!int(it); 5752 return ret; 5753 } 5754 5755 /// ditto 5756 @property int colspan() const { 5757 int ret = 1; 5758 auto it = getAttribute("colspan"); 5759 if(it.length) 5760 ret = to!int(it); 5761 return ret; 5762 } 5763 5764 /// ditto 5765 @property int rowspan(int i) { 5766 setAttribute("rowspan", to!string(i)); 5767 return i; 5768 } 5769 5770 /// ditto 5771 @property int colspan(int i) { 5772 setAttribute("colspan", to!string(i)); 5773 return i; 5774 } 5775 5776 } 5777 5778 5779 /// This is thrown on parse errors. 5780 /// Group: implementations 5781 class MarkupException : Exception { 5782 5783 ///. 5784 this(string message, string file = __FILE__, size_t line = __LINE__) { 5785 super(message, file, line); 5786 } 5787 } 5788 5789 /// This is used when you are using one of the require variants of navigation, and no matching element can be found in the tree. 5790 /// Group: implementations 5791 class ElementNotFoundException : Exception { 5792 5793 /// type == kind of element you were looking for and search == a selector describing the search. 5794 this(string type, string search, Element searchContext, string file = __FILE__, size_t line = __LINE__) { 5795 this.searchContext = searchContext; 5796 super("Element of type '"~type~"' matching {"~search~"} not found.", file, line); 5797 } 5798 5799 Element searchContext; 5800 } 5801 5802 /// The html struct is used to differentiate between regular text nodes and html in certain functions 5803 /// 5804 /// Easiest way to construct it is like this: `auto html = Html("<p>hello</p>");` 5805 /// Group: core_functionality 5806 struct Html { 5807 /// This string holds the actual html. Use it to retrieve the contents. 5808 string source; 5809 } 5810 5811 // for the observers 5812 enum DomMutationOperations { 5813 setAttribute, 5814 removeAttribute, 5815 appendChild, // tagname, attributes[], innerHTML 5816 insertBefore, 5817 truncateChildren, 5818 removeChild, 5819 appendHtml, 5820 replaceHtml, 5821 appendText, 5822 replaceText, 5823 replaceTextOnly 5824 } 5825 5826 // and for observers too 5827 struct DomMutationEvent { 5828 DomMutationOperations operation; 5829 Element target; 5830 Element related; // what this means differs with the operation 5831 Element related2; 5832 string relatedString; 5833 string relatedString2; 5834 } 5835 5836 5837 private immutable static string[] htmlSelfClosedElements = [ 5838 // html 4 5839 "area","base","br","col","hr","img","input","link","meta","param", 5840 5841 // html 5 5842 "embed","source","track","wbr" 5843 ]; 5844 5845 private immutable static string[] htmlInlineElements = [ 5846 "span", "strong", "em", "b", "i", "a" 5847 ]; 5848 5849 5850 static import std.conv; 5851 5852 /// helper function for decoding html entities 5853 int intFromHex(string hex) { 5854 int place = 1; 5855 int value = 0; 5856 for(sizediff_t a = hex.length - 1; a >= 0; a--) { 5857 int v; 5858 char q = hex[a]; 5859 if( q >= '0' && q <= '9') 5860 v = q - '0'; 5861 else if (q >= 'a' && q <= 'f') 5862 v = q - 'a' + 10; 5863 else if (q >= 'A' && q <= 'F') 5864 v = q - 'A' + 10; 5865 else throw new Exception("Illegal hex character: " ~ q); 5866 5867 value += v * place; 5868 5869 place *= 16; 5870 } 5871 5872 return value; 5873 } 5874 5875 5876 // CSS selector handling 5877 5878 // EXTENSIONS 5879 // dd - dt means get the dt directly before that dd (opposite of +) NOT IMPLEMENTED 5880 // dd -- dt means rewind siblings until you hit a dt, go as far as you need to NOT IMPLEMENTED 5881 // dt < dl means get the parent of that dt iff it is a dl (usable for "get a dt that are direct children of dl") 5882 // dt << dl means go as far up as needed to find a dl (you have an element and want its containers) NOT IMPLEMENTED 5883 // :first means to stop at the first hit, don't do more (so p + p == p ~ p:first 5884 5885 5886 5887 // CSS4 draft currently says you can change the subject (the element actually returned) by putting a ! at the end of it. 5888 // That might be useful to implement, though I do have parent selectors too. 5889 5890 ///. 5891 static immutable string[] selectorTokens = [ 5892 // It is important that the 2 character possibilities go first here for accurate lexing 5893 "~=", "*=", "|=", "^=", "$=", "!=", 5894 "::", ">>", 5895 "<<", // my any-parent extension (reciprocal of whitespace) 5896 // " - ", // previous-sibling extension (whitespace required to disambiguate tag-names) 5897 ".", ">", "+", "*", ":", "[", "]", "=", "\"", "#", ",", " ", "~", "<", "(", ")" 5898 ]; // other is white space or a name. 5899 5900 ///. 5901 sizediff_t idToken(string str, sizediff_t position) { 5902 sizediff_t tid = -1; 5903 char c = str[position]; 5904 foreach(a, token; selectorTokens) 5905 5906 if(c == token[0]) { 5907 if(token.length > 1) { 5908 if(position + 1 >= str.length || str[position+1] != token[1]) 5909 continue; // not this token 5910 } 5911 tid = a; 5912 break; 5913 } 5914 return tid; 5915 } 5916 5917 /// Parts of the CSS selector implementation 5918 // look, ma, no phobos! 5919 // new lexer by ketmar 5920 string[] lexSelector (string selstr) { 5921 5922 static sizediff_t idToken (string str, size_t stpos) { 5923 char c = str[stpos]; 5924 foreach (sizediff_t tidx, immutable token; selectorTokens) { 5925 if (c == token[0]) { 5926 if (token.length > 1) { 5927 assert(token.length == 2, token); // we don't have 3-char tokens yet 5928 if (str.length-stpos < 2 || str[stpos+1] != token[1]) continue; 5929 } 5930 return tidx; 5931 } 5932 } 5933 return -1; 5934 } 5935 5936 // skip spaces and comments 5937 static string removeLeadingBlanks (string str) { 5938 size_t curpos = 0; 5939 while (curpos < str.length) { 5940 immutable char ch = str[curpos]; 5941 // this can overflow on 4GB strings on 32-bit; 'cmon, don't be silly, nobody cares! 5942 if (ch == '/' && str.length-curpos > 1 && str[curpos+1] == '*') { 5943 // comment 5944 curpos += 2; 5945 while (curpos < str.length) { 5946 if (str[curpos] == '*' && str.length-curpos > 1 && str[curpos+1] == '/') { 5947 curpos += 2; 5948 break; 5949 } 5950 ++curpos; 5951 } 5952 } else if (ch < 32) { // The < instead of <= is INTENTIONAL. See note from adr below. 5953 ++curpos; 5954 5955 // FROM ADR: This does NOT catch ' '! Spaces have semantic meaning in CSS! While 5956 // "foo bar" is clear, and can only have one meaning, consider ".foo .bar". 5957 // That is not the same as ".foo.bar". If the space is stripped, important 5958 // information is lost, despite the tokens being separatable anyway. 5959 // 5960 // The parser really needs to be aware of the presence of a space. 5961 } else { 5962 break; 5963 } 5964 } 5965 return str[curpos..$]; 5966 } 5967 5968 static bool isBlankAt() (string str, size_t pos) { 5969 // we should consider unicode spaces too, but... unicode sux anyway. 5970 return 5971 (pos < str.length && // in string 5972 (str[pos] <= 32 || // space 5973 (str.length-pos > 1 && str[pos] == '/' && str[pos+1] == '*'))); // comment 5974 } 5975 5976 string[] tokens; 5977 // lexx it! 5978 while ((selstr = removeLeadingBlanks(selstr)).length > 0) { 5979 if(selstr[0] == '\"' || selstr[0] == '\'') { 5980 auto end = selstr[0]; 5981 auto pos = 1; 5982 bool escaping; 5983 while(pos < selstr.length && !escaping && selstr[pos] != end) { 5984 if(escaping) 5985 escaping = false; 5986 else if(selstr[pos] == '\\') 5987 escaping = true; 5988 pos++; 5989 } 5990 5991 // FIXME: do better unescaping 5992 tokens ~= selstr[1 .. pos].replace(`\"`, `"`).replace(`\'`, `'`).replace(`\\`, `\`); 5993 if(pos+1 >= selstr.length) 5994 assert(0, selstr); 5995 selstr = selstr[pos + 1.. $]; 5996 continue; 5997 } 5998 5999 6000 // no tokens starts with escape 6001 immutable tid = idToken(selstr, 0); 6002 if (tid >= 0) { 6003 // special token 6004 tokens ~= selectorTokens[tid]; // it's funnier this way 6005 selstr = selstr[selectorTokens[tid].length..$]; 6006 continue; 6007 } 6008 // from start to space or special token 6009 size_t escapePos = size_t.max; 6010 size_t curpos = 0; // i can has chizburger^w escape at the start 6011 while (curpos < selstr.length) { 6012 if (selstr[curpos] == '\\') { 6013 // this is escape, just skip it and next char 6014 if (escapePos == size_t.max) escapePos = curpos; 6015 curpos = (selstr.length-curpos >= 2 ? curpos+2 : selstr.length); 6016 } else { 6017 if (isBlankAt(selstr, curpos) || idToken(selstr, curpos) >= 0) break; 6018 ++curpos; 6019 } 6020 } 6021 // identifier 6022 if (escapePos != size_t.max) { 6023 // i hate it when it happens 6024 string id = selstr[0..escapePos]; 6025 while (escapePos < curpos) { 6026 if (curpos-escapePos < 2) break; 6027 id ~= selstr[escapePos+1]; // escaped char 6028 escapePos += 2; 6029 immutable stp = escapePos; 6030 while (escapePos < curpos && selstr[escapePos] != '\\') ++escapePos; 6031 if (escapePos > stp) id ~= selstr[stp..escapePos]; 6032 } 6033 if (id.length > 0) tokens ~= id; 6034 } else { 6035 tokens ~= selstr[0..curpos]; 6036 } 6037 selstr = selstr[curpos..$]; 6038 } 6039 return tokens; 6040 } 6041 version(unittest_domd_lexer) unittest { 6042 assert(lexSelector(r" test\=me /*d*/") == [r"test=me"]); 6043 assert(lexSelector(r"div/**/. id") == ["div", ".", "id"]); 6044 assert(lexSelector(r" < <") == ["<", "<"]); 6045 assert(lexSelector(r" <<") == ["<<"]); 6046 assert(lexSelector(r" <</") == ["<<", "/"]); 6047 assert(lexSelector(r" <</*") == ["<<"]); 6048 assert(lexSelector(r" <\</*") == ["<", "<"]); 6049 assert(lexSelector(r"heh\") == ["heh"]); 6050 assert(lexSelector(r"alice \") == ["alice"]); 6051 assert(lexSelector(r"alice,is#best") == ["alice", ",", "is", "#", "best"]); 6052 } 6053 6054 /// ditto 6055 struct SelectorPart { 6056 string tagNameFilter; ///. 6057 string[] attributesPresent; /// [attr] 6058 string[2][] attributesEqual; /// [attr=value] 6059 string[2][] attributesStartsWith; /// [attr^=value] 6060 string[2][] attributesEndsWith; /// [attr$=value] 6061 // split it on space, then match to these 6062 string[2][] attributesIncludesSeparatedBySpaces; /// [attr~=value] 6063 // split it on dash, then match to these 6064 string[2][] attributesIncludesSeparatedByDashes; /// [attr|=value] 6065 string[2][] attributesInclude; /// [attr*=value] 6066 string[2][] attributesNotEqual; /// [attr!=value] -- extension by me 6067 6068 string[] hasSelectors; /// :has(this) 6069 string[] notSelectors; /// :not(this) 6070 6071 string[] isSelectors; /// :is(this) 6072 string[] whereSelectors; /// :where(this) 6073 6074 ParsedNth[] nthOfType; /// . 6075 ParsedNth[] nthLastOfType; /// . 6076 ParsedNth[] nthChild; /// . 6077 6078 bool firstChild; ///. 6079 bool lastChild; ///. 6080 6081 bool firstOfType; /// . 6082 bool lastOfType; /// . 6083 6084 bool emptyElement; ///. 6085 bool whitespaceOnly; /// 6086 bool oddChild; ///. 6087 bool evenChild; ///. 6088 6089 bool scopeElement; /// the css :scope thing; matches just the `this` element. NOT IMPLEMENTED 6090 6091 bool rootElement; ///. 6092 6093 int separation = -1; /// -1 == only itself; the null selector, 0 == tree, 1 == childNodes, 2 == childAfter, 3 == youngerSibling, 4 == parentOf 6094 6095 bool isCleanSlateExceptSeparation() { 6096 auto cp = this; 6097 cp.separation = -1; 6098 return cp is SelectorPart.init; 6099 } 6100 6101 ///. 6102 string toString() { 6103 string ret; 6104 switch(separation) { 6105 default: assert(0); 6106 case -1: break; 6107 case 0: ret ~= " "; break; 6108 case 1: ret ~= " > "; break; 6109 case 2: ret ~= " + "; break; 6110 case 3: ret ~= " ~ "; break; 6111 case 4: ret ~= " < "; break; 6112 } 6113 ret ~= tagNameFilter; 6114 foreach(a; attributesPresent) ret ~= "[" ~ a ~ "]"; 6115 foreach(a; attributesEqual) ret ~= "[" ~ a[0] ~ "=\"" ~ a[1] ~ "\"]"; 6116 foreach(a; attributesEndsWith) ret ~= "[" ~ a[0] ~ "$=\"" ~ a[1] ~ "\"]"; 6117 foreach(a; attributesStartsWith) ret ~= "[" ~ a[0] ~ "^=\"" ~ a[1] ~ "\"]"; 6118 foreach(a; attributesNotEqual) ret ~= "[" ~ a[0] ~ "!=\"" ~ a[1] ~ "\"]"; 6119 foreach(a; attributesInclude) ret ~= "[" ~ a[0] ~ "*=\"" ~ a[1] ~ "\"]"; 6120 foreach(a; attributesIncludesSeparatedByDashes) ret ~= "[" ~ a[0] ~ "|=\"" ~ a[1] ~ "\"]"; 6121 foreach(a; attributesIncludesSeparatedBySpaces) ret ~= "[" ~ a[0] ~ "~=\"" ~ a[1] ~ "\"]"; 6122 6123 foreach(a; notSelectors) ret ~= ":not(" ~ a ~ ")"; 6124 foreach(a; hasSelectors) ret ~= ":has(" ~ a ~ ")"; 6125 6126 foreach(a; isSelectors) ret ~= ":is(" ~ a ~ ")"; 6127 foreach(a; whereSelectors) ret ~= ":where(" ~ a ~ ")"; 6128 6129 foreach(a; nthChild) ret ~= ":nth-child(" ~ a.toString ~ ")"; 6130 foreach(a; nthOfType) ret ~= ":nth-of-type(" ~ a.toString ~ ")"; 6131 foreach(a; nthLastOfType) ret ~= ":nth-last-of-type(" ~ a.toString ~ ")"; 6132 6133 if(firstChild) ret ~= ":first-child"; 6134 if(lastChild) ret ~= ":last-child"; 6135 if(firstOfType) ret ~= ":first-of-type"; 6136 if(lastOfType) ret ~= ":last-of-type"; 6137 if(emptyElement) ret ~= ":empty"; 6138 if(whitespaceOnly) ret ~= ":whitespace-only"; 6139 if(oddChild) ret ~= ":odd-child"; 6140 if(evenChild) ret ~= ":even-child"; 6141 if(rootElement) ret ~= ":root"; 6142 if(scopeElement) ret ~= ":scope"; 6143 6144 return ret; 6145 } 6146 6147 // USEFUL 6148 /// Returns true if the given element matches this part 6149 bool matchElement(Element e) { 6150 // FIXME: this can be called a lot of times, and really add up in times according to the profiler. 6151 // Each individual call is reasonably fast already, but it adds up. 6152 if(e is null) return false; 6153 if(e.nodeType != 1) return false; 6154 6155 if(tagNameFilter != "" && tagNameFilter != "*") 6156 if(e.tagName != tagNameFilter) 6157 return false; 6158 if(firstChild) { 6159 if(e.parentNode is null) 6160 return false; 6161 if(e.parentNode.childElements[0] !is e) 6162 return false; 6163 } 6164 if(lastChild) { 6165 if(e.parentNode is null) 6166 return false; 6167 auto ce = e.parentNode.childElements; 6168 if(ce[$-1] !is e) 6169 return false; 6170 } 6171 if(firstOfType) { 6172 if(e.parentNode is null) 6173 return false; 6174 auto ce = e.parentNode.childElements; 6175 foreach(c; ce) { 6176 if(c.tagName == e.tagName) { 6177 if(c is e) 6178 return true; 6179 else 6180 return false; 6181 } 6182 } 6183 } 6184 if(lastOfType) { 6185 if(e.parentNode is null) 6186 return false; 6187 auto ce = e.parentNode.childElements; 6188 foreach_reverse(c; ce) { 6189 if(c.tagName == e.tagName) { 6190 if(c is e) 6191 return true; 6192 else 6193 return false; 6194 } 6195 } 6196 } 6197 /+ 6198 if(scopeElement) { 6199 if(e !is this_) 6200 return false; 6201 } 6202 +/ 6203 if(emptyElement) { 6204 if(e.isEmpty()) 6205 return false; 6206 } 6207 if(whitespaceOnly) { 6208 if(e.innerText.strip.length) 6209 return false; 6210 } 6211 if(rootElement) { 6212 if(e.parentNode !is null) 6213 return false; 6214 } 6215 if(oddChild || evenChild) { 6216 if(e.parentNode is null) 6217 return false; 6218 foreach(i, child; e.parentNode.childElements) { 6219 if(child is e) { 6220 if(oddChild && !(i&1)) 6221 return false; 6222 if(evenChild && (i&1)) 6223 return false; 6224 break; 6225 } 6226 } 6227 } 6228 6229 bool matchWithSeparator(string attr, string value, string separator) { 6230 foreach(s; attr.split(separator)) 6231 if(s == value) 6232 return true; 6233 return false; 6234 } 6235 6236 foreach(a; attributesPresent) 6237 if(a !in e.attributes) 6238 return false; 6239 foreach(a; attributesEqual) 6240 if(a[0] !in e.attributes || e.attributes[a[0]] != a[1]) 6241 return false; 6242 foreach(a; attributesNotEqual) 6243 // FIXME: maybe it should say null counts... this just bit me. 6244 // I did [attr][attr!=value] to work around. 6245 // 6246 // if it's null, it's not equal, right? 6247 //if(a[0] !in e.attributes || e.attributes[a[0]] == a[1]) 6248 if(e.getAttribute(a[0]) == a[1]) 6249 return false; 6250 foreach(a; attributesInclude) 6251 if(a[0] !in e.attributes || (e.attributes[a[0]].indexOf(a[1]) == -1)) 6252 return false; 6253 foreach(a; attributesStartsWith) 6254 if(a[0] !in e.attributes || !e.attributes[a[0]].startsWith(a[1])) 6255 return false; 6256 foreach(a; attributesEndsWith) 6257 if(a[0] !in e.attributes || !e.attributes[a[0]].endsWith(a[1])) 6258 return false; 6259 foreach(a; attributesIncludesSeparatedBySpaces) 6260 if(a[0] !in e.attributes || !matchWithSeparator(e.attributes[a[0]], a[1], " ")) 6261 return false; 6262 foreach(a; attributesIncludesSeparatedByDashes) 6263 if(a[0] !in e.attributes || !matchWithSeparator(e.attributes[a[0]], a[1], "-")) 6264 return false; 6265 foreach(a; hasSelectors) { 6266 if(e.querySelector(a) is null) 6267 return false; 6268 } 6269 foreach(a; notSelectors) { 6270 auto sel = Selector(a); 6271 if(sel.matchesElement(e)) 6272 return false; 6273 } 6274 foreach(a; isSelectors) { 6275 auto sel = Selector(a); 6276 if(!sel.matchesElement(e)) 6277 return false; 6278 } 6279 foreach(a; whereSelectors) { 6280 auto sel = Selector(a); 6281 if(!sel.matchesElement(e)) 6282 return false; 6283 } 6284 6285 foreach(a; nthChild) { 6286 if(e.parentNode is null) 6287 return false; 6288 6289 auto among = e.parentNode.childElements; 6290 6291 if(!a.solvesFor(among, e)) 6292 return false; 6293 } 6294 foreach(a; nthOfType) { 6295 if(e.parentNode is null) 6296 return false; 6297 6298 auto among = e.parentNode.childElements(e.tagName); 6299 6300 if(!a.solvesFor(among, e)) 6301 return false; 6302 } 6303 foreach(a; nthLastOfType) { 6304 if(e.parentNode is null) 6305 return false; 6306 6307 auto among = retro(e.parentNode.childElements(e.tagName)); 6308 6309 if(!a.solvesFor(among, e)) 6310 return false; 6311 } 6312 6313 return true; 6314 } 6315 } 6316 6317 struct ParsedNth { 6318 int multiplier; 6319 int adder; 6320 6321 string of; 6322 6323 this(string text) { 6324 auto original = text; 6325 consumeWhitespace(text); 6326 if(text.startsWith("odd")) { 6327 multiplier = 2; 6328 adder = 1; 6329 6330 text = text[3 .. $]; 6331 } else if(text.startsWith("even")) { 6332 multiplier = 2; 6333 adder = 1; 6334 6335 text = text[4 .. $]; 6336 } else { 6337 int n = (text.length && text[0] == 'n') ? 1 : parseNumber(text); 6338 consumeWhitespace(text); 6339 if(text.length && text[0] == 'n') { 6340 multiplier = n; 6341 text = text[1 .. $]; 6342 consumeWhitespace(text); 6343 if(text.length) { 6344 if(text[0] == '+') { 6345 text = text[1 .. $]; 6346 adder = parseNumber(text); 6347 } else if(text[0] == '-') { 6348 text = text[1 .. $]; 6349 adder = -parseNumber(text); 6350 } else if(text[0] == 'o') { 6351 // continue, this is handled below 6352 } else 6353 throw new Exception("invalid css string at " ~ text ~ " in " ~ original); 6354 } 6355 } else { 6356 adder = n; 6357 } 6358 } 6359 6360 consumeWhitespace(text); 6361 if(text.startsWith("of")) { 6362 text = text[2 .. $]; 6363 consumeWhitespace(text); 6364 of = text[0 .. $]; 6365 } 6366 } 6367 6368 string toString() { 6369 return format("%dn%s%d%s%s", multiplier, adder >= 0 ? "+" : "", adder, of.length ? " of " : "", of); 6370 } 6371 6372 bool solvesFor(R)(R elements, Element e) { 6373 int idx = 1; 6374 bool found = false; 6375 foreach(ele; elements) { 6376 if(of.length) { 6377 auto sel = Selector(of); 6378 if(!sel.matchesElement(ele)) 6379 continue; 6380 } 6381 if(ele is e) { 6382 found = true; 6383 break; 6384 } 6385 idx++; 6386 } 6387 if(!found) return false; 6388 6389 // multiplier* n + adder = idx 6390 // if there is a solution for integral n, it matches 6391 6392 idx -= adder; 6393 if(multiplier) { 6394 if(idx % multiplier == 0) 6395 return true; 6396 } else { 6397 return idx == 0; 6398 } 6399 return false; 6400 } 6401 6402 private void consumeWhitespace(ref string text) { 6403 while(text.length && text[0] == ' ') 6404 text = text[1 .. $]; 6405 } 6406 6407 private int parseNumber(ref string text) { 6408 consumeWhitespace(text); 6409 if(text.length == 0) return 0; 6410 bool negative = text[0] == '-'; 6411 if(text[0] == '+') 6412 text = text[1 .. $]; 6413 if(negative) text = text[1 .. $]; 6414 int i = 0; 6415 while(i < text.length && (text[i] >= '0' && text[i] <= '9')) 6416 i++; 6417 if(i == 0) 6418 return 0; 6419 int cool = to!int(text[0 .. i]); 6420 text = text[i .. $]; 6421 return negative ? -cool : cool; 6422 } 6423 } 6424 6425 // USEFUL 6426 /// ditto 6427 Element[] getElementsBySelectorParts(Element start, SelectorPart[] parts) { 6428 Element[] ret; 6429 if(!parts.length) { 6430 return [start]; // the null selector only matches the start point; it 6431 // is what terminates the recursion 6432 } 6433 6434 auto part = parts[0]; 6435 //writeln("checking ", part, " against ", start, " with ", part.separation); 6436 switch(part.separation) { 6437 default: assert(0); 6438 case -1: 6439 case 0: // tree 6440 foreach(e; start.tree) { 6441 if(part.separation == 0 && start is e) 6442 continue; // space doesn't match itself! 6443 if(part.matchElement(e)) { 6444 ret ~= getElementsBySelectorParts(e, parts[1..$]); 6445 } 6446 } 6447 break; 6448 case 1: // children 6449 foreach(e; start.childNodes) { 6450 if(part.matchElement(e)) { 6451 ret ~= getElementsBySelectorParts(e, parts[1..$]); 6452 } 6453 } 6454 break; 6455 case 2: // next-sibling 6456 auto e = start.nextSibling("*"); 6457 if(part.matchElement(e)) 6458 ret ~= getElementsBySelectorParts(e, parts[1..$]); 6459 break; 6460 case 3: // younger sibling 6461 auto tmp = start.parentNode; 6462 if(tmp !is null) { 6463 sizediff_t pos = -1; 6464 auto children = tmp.childElements; 6465 foreach(i, child; children) { 6466 if(child is start) { 6467 pos = i; 6468 break; 6469 } 6470 } 6471 assert(pos != -1); 6472 foreach(e; children[pos+1..$]) { 6473 if(part.matchElement(e)) 6474 ret ~= getElementsBySelectorParts(e, parts[1..$]); 6475 } 6476 } 6477 break; 6478 case 4: // immediate parent node, an extension of mine to walk back up the tree 6479 auto e = start.parentNode; 6480 if(part.matchElement(e)) { 6481 ret ~= getElementsBySelectorParts(e, parts[1..$]); 6482 } 6483 /* 6484 Example of usefulness: 6485 6486 Consider you have an HTML table. If you want to get all rows that have a th, you can do: 6487 6488 table th < tr 6489 6490 Get all th descendants of the table, then walk back up the tree to fetch their parent tr nodes 6491 */ 6492 break; 6493 case 5: // any parent note, another extension of mine to go up the tree (backward of the whitespace operator) 6494 /* 6495 Like with the < operator, this is best used to find some parent of a particular known element. 6496 6497 Say you have an anchor inside a 6498 */ 6499 } 6500 6501 return ret; 6502 } 6503 6504 /++ 6505 Represents a parsed CSS selector. You never have to use this directly, but you can if you know it is going to be reused a lot to avoid a bit of repeat parsing. 6506 6507 See_Also: 6508 $(LIST 6509 * [Element.querySelector] 6510 * [Element.querySelectorAll] 6511 * [Element.matches] 6512 * [Element.closest] 6513 * [Document.querySelector] 6514 * [Document.querySelectorAll] 6515 ) 6516 +/ 6517 /// Group: core_functionality 6518 struct Selector { 6519 SelectorComponent[] components; 6520 string original; 6521 /++ 6522 Parses the selector string and constructs the usable structure. 6523 +/ 6524 this(string cssSelector) { 6525 components = parseSelectorString(cssSelector); 6526 original = cssSelector; 6527 } 6528 6529 /++ 6530 Returns true if the given element matches this selector, 6531 considered relative to an arbitrary element. 6532 6533 You can do a form of lazy [Element.querySelectorAll|querySelectorAll] by using this 6534 with [std.algorithm.iteration.filter]: 6535 6536 --- 6537 Selector sel = Selector("foo > bar"); 6538 auto lazySelectorRange = element.tree.filter!(e => sel.matchElement(e))(document.root); 6539 --- 6540 +/ 6541 bool matchesElement(Element e, Element relativeTo = null) { 6542 foreach(component; components) 6543 if(component.matchElement(e, relativeTo)) 6544 return true; 6545 6546 return false; 6547 } 6548 6549 /++ 6550 Reciprocal of [Element.querySelectorAll] 6551 +/ 6552 Element[] getMatchingElements(Element start) { 6553 Element[] ret; 6554 foreach(component; components) 6555 ret ~= getElementsBySelectorParts(start, component.parts); 6556 return removeDuplicates(ret); 6557 } 6558 6559 /++ 6560 Like [getMatchingElements], but returns a lazy range. Be careful 6561 about mutating the dom as you iterate through this. 6562 +/ 6563 auto getMatchingElementsLazy(Element start, Element relativeTo = null) { 6564 import std.algorithm.iteration; 6565 return start.tree.filter!(a => this.matchesElement(a, relativeTo)); 6566 } 6567 6568 6569 /// Returns the string this was built from 6570 string toString() { 6571 return original; 6572 } 6573 6574 /++ 6575 Returns a string from the parsed result 6576 6577 6578 (may not match the original, this is mostly for debugging right now but in the future might be useful for pretty-printing) 6579 +/ 6580 string parsedToString() { 6581 string ret; 6582 6583 foreach(idx, component; components) { 6584 if(idx) ret ~= ", "; 6585 ret ~= component.toString(); 6586 } 6587 6588 return ret; 6589 } 6590 } 6591 6592 ///. 6593 struct SelectorComponent { 6594 ///. 6595 SelectorPart[] parts; 6596 6597 ///. 6598 string toString() { 6599 string ret; 6600 foreach(part; parts) 6601 ret ~= part.toString(); 6602 return ret; 6603 } 6604 6605 // USEFUL 6606 ///. 6607 Element[] getElements(Element start) { 6608 return removeDuplicates(getElementsBySelectorParts(start, parts)); 6609 } 6610 6611 // USEFUL (but not implemented) 6612 /// If relativeTo == null, it assumes the root of the parent document. 6613 bool matchElement(Element e, Element relativeTo = null) { 6614 if(e is null) return false; 6615 Element where = e; 6616 int lastSeparation = -1; 6617 6618 auto lparts = parts; 6619 6620 if(parts.length && parts[0].separation > 0) { 6621 // if it starts with a non-trivial separator, inject 6622 // a "*" matcher to act as a root. for cases like document.querySelector("> body") 6623 // which implies html 6624 6625 // however, if it is a child-matching selector and there are no children, 6626 // bail out early as it obviously cannot match. 6627 bool hasNonTextChildren = false; 6628 foreach(c; e.children) 6629 if(c.nodeType != 3) { 6630 hasNonTextChildren = true; 6631 break; 6632 } 6633 if(!hasNonTextChildren) 6634 return false; 6635 6636 // there is probably a MUCH better way to do this. 6637 auto dummy = SelectorPart.init; 6638 dummy.tagNameFilter = "*"; 6639 dummy.separation = 0; 6640 lparts = dummy ~ lparts; 6641 } 6642 6643 foreach(part; retro(lparts)) { 6644 6645 // writeln("matching ", where, " with ", part, " via ", lastSeparation); 6646 // writeln(parts); 6647 6648 if(lastSeparation == -1) { 6649 if(!part.matchElement(where)) 6650 return false; 6651 } else if(lastSeparation == 0) { // generic parent 6652 // need to go up the whole chain 6653 where = where.parentNode; 6654 6655 while(where !is null) { 6656 if(part.matchElement(where)) 6657 break; 6658 6659 if(where is relativeTo) 6660 return false; 6661 6662 where = where.parentNode; 6663 } 6664 6665 if(where is null) 6666 return false; 6667 } else if(lastSeparation == 1) { // the > operator 6668 where = where.parentNode; 6669 6670 if(!part.matchElement(where)) 6671 return false; 6672 } else if(lastSeparation == 2) { // the + operator 6673 //writeln("WHERE", where, " ", part); 6674 where = where.previousSibling("*"); 6675 6676 if(!part.matchElement(where)) 6677 return false; 6678 } else if(lastSeparation == 3) { // the ~ operator 6679 where = where.previousSibling("*"); 6680 while(where !is null) { 6681 if(part.matchElement(where)) 6682 break; 6683 6684 if(where is relativeTo) 6685 return false; 6686 6687 where = where.previousSibling("*"); 6688 } 6689 6690 if(where is null) 6691 return false; 6692 } else if(lastSeparation == 4) { // my bad idea extension < operator, don't use this anymore 6693 // FIXME 6694 } 6695 6696 lastSeparation = part.separation; 6697 6698 if(where is relativeTo) 6699 return false; // at end of line, if we aren't done by now, the match fails 6700 } 6701 return true; // if we got here, it is a success 6702 } 6703 6704 // the string should NOT have commas. Use parseSelectorString for that instead 6705 ///. 6706 static SelectorComponent fromString(string selector) { 6707 return parseSelector(lexSelector(selector)); 6708 } 6709 } 6710 6711 ///. 6712 SelectorComponent[] parseSelectorString(string selector, bool caseSensitiveTags = true) { 6713 SelectorComponent[] ret; 6714 auto tokens = lexSelector(selector); // this will parse commas too 6715 // and now do comma-separated slices (i haz phobosophobia!) 6716 int parensCount = 0; 6717 while (tokens.length > 0) { 6718 size_t end = 0; 6719 while (end < tokens.length && (parensCount > 0 || tokens[end] != ",")) { 6720 if(tokens[end] == "(") parensCount++; 6721 if(tokens[end] == ")") parensCount--; 6722 ++end; 6723 } 6724 if (end > 0) ret ~= parseSelector(tokens[0..end], caseSensitiveTags); 6725 if (tokens.length-end < 2) break; 6726 tokens = tokens[end+1..$]; 6727 } 6728 return ret; 6729 } 6730 6731 ///. 6732 SelectorComponent parseSelector(string[] tokens, bool caseSensitiveTags = true) { 6733 SelectorComponent s; 6734 6735 SelectorPart current; 6736 void commit() { 6737 // might as well skip null items 6738 if(!current.isCleanSlateExceptSeparation()) { 6739 s.parts ~= current; 6740 current = current.init; // start right over 6741 } 6742 } 6743 enum State { 6744 Starting, 6745 ReadingClass, 6746 ReadingId, 6747 ReadingAttributeSelector, 6748 ReadingAttributeComparison, 6749 ExpectingAttributeCloser, 6750 ReadingPseudoClass, 6751 ReadingAttributeValue, 6752 6753 SkippingFunctionalSelector, 6754 } 6755 State state = State.Starting; 6756 string attributeName, attributeValue, attributeComparison; 6757 int parensCount; 6758 foreach(idx, token; tokens) { 6759 string readFunctionalSelector() { 6760 string s; 6761 if(tokens[idx + 1] != "(") 6762 throw new Exception("parse error"); 6763 int pc = 1; 6764 foreach(t; tokens[idx + 2 .. $]) { 6765 if(t == "(") 6766 pc++; 6767 if(t == ")") 6768 pc--; 6769 if(pc == 0) 6770 break; 6771 s ~= t; 6772 } 6773 6774 return s; 6775 } 6776 6777 sizediff_t tid = -1; 6778 foreach(i, item; selectorTokens) 6779 if(token == item) { 6780 tid = i; 6781 break; 6782 } 6783 final switch(state) { 6784 case State.Starting: // fresh, might be reading an operator or a tagname 6785 if(tid == -1) { 6786 if(!caseSensitiveTags) 6787 token = token.toLower(); 6788 6789 if(current.isCleanSlateExceptSeparation()) { 6790 current.tagNameFilter = token; 6791 // default thing, see comment under "*" below 6792 if(current.separation == -1) current.separation = 0; 6793 } else { 6794 // if it was already set, we must see two thingies 6795 // separated by whitespace... 6796 commit(); 6797 current.separation = 0; // tree 6798 current.tagNameFilter = token; 6799 } 6800 } else { 6801 // Selector operators 6802 switch(token) { 6803 case "*": 6804 current.tagNameFilter = "*"; 6805 // the idea here is if we haven't actually set a separation 6806 // yet (e.g. the > operator), it should assume the generic 6807 // whitespace (descendant) mode to avoid matching self with -1 6808 if(current.separation == -1) current.separation = 0; 6809 break; 6810 case " ": 6811 // If some other separation has already been set, 6812 // this is irrelevant whitespace, so we should skip it. 6813 // this happens in the case of "foo > bar" for example. 6814 if(current.isCleanSlateExceptSeparation() && current.separation > 0) 6815 continue; 6816 commit(); 6817 current.separation = 0; // tree 6818 break; 6819 case ">>": 6820 commit(); 6821 current.separation = 0; // alternate syntax for tree from html5 css 6822 break; 6823 case ">": 6824 commit(); 6825 current.separation = 1; // child 6826 break; 6827 case "+": 6828 commit(); 6829 current.separation = 2; // sibling directly after 6830 break; 6831 case "~": 6832 commit(); 6833 current.separation = 3; // any sibling after 6834 break; 6835 case "<": 6836 commit(); 6837 current.separation = 4; // immediate parent of 6838 break; 6839 case "[": 6840 state = State.ReadingAttributeSelector; 6841 if(current.separation == -1) current.separation = 0; 6842 break; 6843 case ".": 6844 state = State.ReadingClass; 6845 if(current.separation == -1) current.separation = 0; 6846 break; 6847 case "#": 6848 state = State.ReadingId; 6849 if(current.separation == -1) current.separation = 0; 6850 break; 6851 case ":": 6852 case "::": 6853 state = State.ReadingPseudoClass; 6854 if(current.separation == -1) current.separation = 0; 6855 break; 6856 6857 default: 6858 assert(0, token); 6859 } 6860 } 6861 break; 6862 case State.ReadingClass: 6863 current.attributesIncludesSeparatedBySpaces ~= ["class", token]; 6864 state = State.Starting; 6865 break; 6866 case State.ReadingId: 6867 current.attributesEqual ~= ["id", token]; 6868 state = State.Starting; 6869 break; 6870 case State.ReadingPseudoClass: 6871 switch(token) { 6872 case "first-of-type": 6873 current.firstOfType = true; 6874 break; 6875 case "last-of-type": 6876 current.lastOfType = true; 6877 break; 6878 case "only-of-type": 6879 current.firstOfType = true; 6880 current.lastOfType = true; 6881 break; 6882 case "first-child": 6883 current.firstChild = true; 6884 break; 6885 case "last-child": 6886 current.lastChild = true; 6887 break; 6888 case "only-child": 6889 current.firstChild = true; 6890 current.lastChild = true; 6891 break; 6892 case "scope": 6893 current.scopeElement = true; 6894 break; 6895 case "empty": 6896 // one with no children 6897 current.emptyElement = true; 6898 break; 6899 case "whitespace-only": 6900 current.whitespaceOnly = true; 6901 break; 6902 case "link": 6903 current.attributesPresent ~= "href"; 6904 break; 6905 case "root": 6906 current.rootElement = true; 6907 break; 6908 case "nth-child": 6909 current.nthChild ~= ParsedNth(readFunctionalSelector()); 6910 state = State.SkippingFunctionalSelector; 6911 continue; 6912 case "nth-of-type": 6913 current.nthOfType ~= ParsedNth(readFunctionalSelector()); 6914 state = State.SkippingFunctionalSelector; 6915 continue; 6916 case "nth-last-of-type": 6917 current.nthLastOfType ~= ParsedNth(readFunctionalSelector()); 6918 state = State.SkippingFunctionalSelector; 6919 continue; 6920 case "is": 6921 state = State.SkippingFunctionalSelector; 6922 current.isSelectors ~= readFunctionalSelector(); 6923 continue; // now the rest of the parser skips past the parens we just handled 6924 case "where": 6925 state = State.SkippingFunctionalSelector; 6926 current.whereSelectors ~= readFunctionalSelector(); 6927 continue; // now the rest of the parser skips past the parens we just handled 6928 case "not": 6929 state = State.SkippingFunctionalSelector; 6930 current.notSelectors ~= readFunctionalSelector(); 6931 continue; // now the rest of the parser skips past the parens we just handled 6932 case "has": 6933 state = State.SkippingFunctionalSelector; 6934 current.hasSelectors ~= readFunctionalSelector(); 6935 continue; // now the rest of the parser skips past the parens we just handled 6936 // back to standards though not quite right lol 6937 case "disabled": 6938 current.attributesPresent ~= "disabled"; 6939 break; 6940 case "checked": 6941 current.attributesPresent ~= "checked"; 6942 break; 6943 6944 case "visited", "active", "hover", "target", "focus", "selected": 6945 current.attributesPresent ~= "nothing"; 6946 // FIXME 6947 /+ 6948 // extensions not implemented 6949 //case "text": // takes the text in the element and wraps it in an element, returning it 6950 +/ 6951 goto case; 6952 case "before", "after": 6953 current.attributesPresent ~= "FIXME"; 6954 6955 break; 6956 // My extensions 6957 case "odd-child": 6958 current.oddChild = true; 6959 break; 6960 case "even-child": 6961 current.evenChild = true; 6962 break; 6963 default: 6964 //if(token.indexOf("lang") == -1) 6965 //assert(0, token); 6966 break; 6967 } 6968 state = State.Starting; 6969 break; 6970 case State.SkippingFunctionalSelector: 6971 if(token == "(") { 6972 parensCount++; 6973 } else if(token == ")") { 6974 parensCount--; 6975 } 6976 6977 if(parensCount == 0) 6978 state = State.Starting; 6979 break; 6980 case State.ReadingAttributeSelector: 6981 attributeName = token; 6982 attributeComparison = null; 6983 attributeValue = null; 6984 state = State.ReadingAttributeComparison; 6985 break; 6986 case State.ReadingAttributeComparison: 6987 // FIXME: these things really should be quotable in the proper lexer... 6988 if(token != "]") { 6989 if(token.indexOf("=") == -1) { 6990 // not a comparison; consider it 6991 // part of the attribute 6992 attributeValue ~= token; 6993 } else { 6994 attributeComparison = token; 6995 state = State.ReadingAttributeValue; 6996 } 6997 break; 6998 } 6999 goto case; 7000 case State.ExpectingAttributeCloser: 7001 if(token != "]") { 7002 // not the closer; consider it part of comparison 7003 if(attributeComparison == "") 7004 attributeName ~= token; 7005 else 7006 attributeValue ~= token; 7007 break; 7008 } 7009 7010 // Selector operators 7011 switch(attributeComparison) { 7012 default: assert(0); 7013 case "": 7014 current.attributesPresent ~= attributeName; 7015 break; 7016 case "=": 7017 current.attributesEqual ~= [attributeName, attributeValue]; 7018 break; 7019 case "|=": 7020 current.attributesIncludesSeparatedByDashes ~= [attributeName, attributeValue]; 7021 break; 7022 case "~=": 7023 current.attributesIncludesSeparatedBySpaces ~= [attributeName, attributeValue]; 7024 break; 7025 case "$=": 7026 current.attributesEndsWith ~= [attributeName, attributeValue]; 7027 break; 7028 case "^=": 7029 current.attributesStartsWith ~= [attributeName, attributeValue]; 7030 break; 7031 case "*=": 7032 current.attributesInclude ~= [attributeName, attributeValue]; 7033 break; 7034 case "!=": 7035 current.attributesNotEqual ~= [attributeName, attributeValue]; 7036 break; 7037 } 7038 7039 state = State.Starting; 7040 break; 7041 case State.ReadingAttributeValue: 7042 attributeValue = token; 7043 state = State.ExpectingAttributeCloser; 7044 break; 7045 } 7046 } 7047 7048 commit(); 7049 7050 return s; 7051 } 7052 7053 ///. 7054 Element[] removeDuplicates(Element[] input) { 7055 Element[] ret; 7056 7057 bool[Element] already; 7058 foreach(e; input) { 7059 if(e in already) continue; 7060 already[e] = true; 7061 ret ~= e; 7062 } 7063 7064 return ret; 7065 } 7066 7067 // done with CSS selector handling 7068 7069 7070 // FIXME: use the better parser from html.d 7071 /// This is probably not useful to you unless you're writing a browser or something like that. 7072 /// It represents a *computed* style, like what the browser gives you after applying stylesheets, inline styles, and html attributes. 7073 /// From here, you can start to make a layout engine for the box model and have a css aware browser. 7074 class CssStyle { 7075 ///. 7076 this(string rule, string content) { 7077 rule = rule.strip(); 7078 content = content.strip(); 7079 7080 if(content.length == 0) 7081 return; 7082 7083 originatingRule = rule; 7084 originatingSpecificity = getSpecificityOfRule(rule); // FIXME: if there's commas, this won't actually work! 7085 7086 foreach(part; content.split(";")) { 7087 part = part.strip(); 7088 if(part.length == 0) 7089 continue; 7090 auto idx = part.indexOf(":"); 7091 if(idx == -1) 7092 continue; 7093 //throw new Exception("Bad css rule (no colon): " ~ part); 7094 7095 Property p; 7096 7097 p.name = part[0 .. idx].strip(); 7098 p.value = part[idx + 1 .. $].replace("! important", "!important").replace("!important", "").strip(); // FIXME don't drop important 7099 p.givenExplicitly = true; 7100 p.specificity = originatingSpecificity; 7101 7102 properties ~= p; 7103 } 7104 7105 foreach(property; properties) 7106 expandShortForm(property, originatingSpecificity); 7107 } 7108 7109 ///. 7110 Specificity getSpecificityOfRule(string rule) { 7111 Specificity s; 7112 if(rule.length == 0) { // inline 7113 // s.important = 2; 7114 } else { 7115 // FIXME 7116 } 7117 7118 return s; 7119 } 7120 7121 string originatingRule; ///. 7122 Specificity originatingSpecificity; ///. 7123 7124 ///. 7125 union Specificity { 7126 uint score; ///. 7127 // version(little_endian) 7128 ///. 7129 struct { 7130 ubyte tags; ///. 7131 ubyte classes; ///. 7132 ubyte ids; ///. 7133 ubyte important; /// 0 = none, 1 = stylesheet author, 2 = inline style, 3 = user important 7134 } 7135 } 7136 7137 ///. 7138 struct Property { 7139 bool givenExplicitly; /// this is false if for example the user said "padding" and this is "padding-left" 7140 string name; ///. 7141 string value; ///. 7142 Specificity specificity; ///. 7143 // do we care about the original source rule? 7144 } 7145 7146 ///. 7147 Property[] properties; 7148 7149 ///. 7150 string opDispatch(string nameGiven)(string value = null) if(nameGiven != "popFront") { 7151 string name = unCamelCase(nameGiven); 7152 if(value is null) 7153 return getValue(name); 7154 else 7155 return setValue(name, value, 0x02000000 /* inline specificity */); 7156 } 7157 7158 /// takes dash style name 7159 string getValue(string name) { 7160 foreach(property; properties) 7161 if(property.name == name) 7162 return property.value; 7163 return null; 7164 } 7165 7166 /// takes dash style name 7167 string setValue(string name, string value, Specificity newSpecificity, bool explicit = true) { 7168 value = value.replace("! important", "!important"); 7169 if(value.indexOf("!important") != -1) { 7170 newSpecificity.important = 1; // FIXME 7171 value = value.replace("!important", "").strip(); 7172 } 7173 7174 foreach(ref property; properties) 7175 if(property.name == name) { 7176 if(newSpecificity.score >= property.specificity.score) { 7177 property.givenExplicitly = explicit; 7178 expandShortForm(property, newSpecificity); 7179 return (property.value = value); 7180 } else { 7181 if(name == "display") 7182 {}//writeln("Not setting ", name, " to ", value, " because ", newSpecificity.score, " < ", property.specificity.score); 7183 return value; // do nothing - the specificity is too low 7184 } 7185 } 7186 7187 // it's not here... 7188 7189 Property p; 7190 p.givenExplicitly = true; 7191 p.name = name; 7192 p.value = value; 7193 p.specificity = originatingSpecificity; 7194 7195 properties ~= p; 7196 expandShortForm(p, originatingSpecificity); 7197 7198 return value; 7199 } 7200 7201 private void expandQuadShort(string name, string value, Specificity specificity) { 7202 auto parts = value.split(" "); 7203 switch(parts.length) { 7204 case 1: 7205 setValue(name ~"-left", parts[0], specificity, false); 7206 setValue(name ~"-right", parts[0], specificity, false); 7207 setValue(name ~"-top", parts[0], specificity, false); 7208 setValue(name ~"-bottom", parts[0], specificity, false); 7209 break; 7210 case 2: 7211 setValue(name ~"-left", parts[1], specificity, false); 7212 setValue(name ~"-right", parts[1], specificity, false); 7213 setValue(name ~"-top", parts[0], specificity, false); 7214 setValue(name ~"-bottom", parts[0], specificity, false); 7215 break; 7216 case 3: 7217 setValue(name ~"-top", parts[0], specificity, false); 7218 setValue(name ~"-right", parts[1], specificity, false); 7219 setValue(name ~"-bottom", parts[2], specificity, false); 7220 setValue(name ~"-left", parts[2], specificity, false); 7221 7222 break; 7223 case 4: 7224 setValue(name ~"-top", parts[0], specificity, false); 7225 setValue(name ~"-right", parts[1], specificity, false); 7226 setValue(name ~"-bottom", parts[2], specificity, false); 7227 setValue(name ~"-left", parts[3], specificity, false); 7228 break; 7229 default: 7230 assert(0, value); 7231 } 7232 } 7233 7234 ///. 7235 void expandShortForm(Property p, Specificity specificity) { 7236 switch(p.name) { 7237 case "margin": 7238 case "padding": 7239 expandQuadShort(p.name, p.value, specificity); 7240 break; 7241 case "border": 7242 case "outline": 7243 setValue(p.name ~ "-left", p.value, specificity, false); 7244 setValue(p.name ~ "-right", p.value, specificity, false); 7245 setValue(p.name ~ "-top", p.value, specificity, false); 7246 setValue(p.name ~ "-bottom", p.value, specificity, false); 7247 break; 7248 7249 case "border-top": 7250 case "border-bottom": 7251 case "border-left": 7252 case "border-right": 7253 case "outline-top": 7254 case "outline-bottom": 7255 case "outline-left": 7256 case "outline-right": 7257 7258 default: {} 7259 } 7260 } 7261 7262 ///. 7263 override string toString() { 7264 string ret; 7265 if(originatingRule.length) 7266 ret = originatingRule ~ " {"; 7267 7268 foreach(property; properties) { 7269 if(!property.givenExplicitly) 7270 continue; // skip the inferred shit 7271 7272 if(originatingRule.length) 7273 ret ~= "\n\t"; 7274 else 7275 ret ~= " "; 7276 7277 ret ~= property.name ~ ": " ~ property.value ~ ";"; 7278 } 7279 7280 if(originatingRule.length) 7281 ret ~= "\n}\n"; 7282 7283 return ret; 7284 } 7285 } 7286 7287 string cssUrl(string url) { 7288 return "url(\"" ~ url ~ "\")"; 7289 } 7290 7291 /// This probably isn't useful, unless you're writing a browser or something like that. 7292 /// You might want to look at arsd.html for css macro, nesting, etc., or just use standard css 7293 /// as text. 7294 /// 7295 /// The idea, however, is to represent a kind of CSS object model, complete with specificity, 7296 /// that you can apply to your documents to build the complete computedStyle object. 7297 class StyleSheet { 7298 ///. 7299 CssStyle[] rules; 7300 7301 ///. 7302 this(string source) { 7303 // FIXME: handle @ rules and probably could improve lexer 7304 // add nesting? 7305 int state; 7306 string currentRule; 7307 string currentValue; 7308 7309 string* currentThing = ¤tRule; 7310 foreach(c; source) { 7311 handle: switch(state) { 7312 default: assert(0); 7313 case 0: // starting - we assume we're reading a rule 7314 switch(c) { 7315 case '@': 7316 state = 4; 7317 break; 7318 case '/': 7319 state = 1; 7320 break; 7321 case '{': 7322 currentThing = ¤tValue; 7323 break; 7324 case '}': 7325 if(currentThing is ¤tValue) { 7326 rules ~= new CssStyle(currentRule, currentValue); 7327 7328 currentRule = ""; 7329 currentValue = ""; 7330 7331 currentThing = ¤tRule; 7332 } else { 7333 // idk what is going on here. 7334 // check sveit.com to reproduce 7335 currentRule = ""; 7336 currentValue = ""; 7337 } 7338 break; 7339 default: 7340 (*currentThing) ~= c; 7341 } 7342 break; 7343 case 1: // expecting * 7344 if(c == '*') 7345 state = 2; 7346 else { 7347 state = 0; 7348 (*currentThing) ~= "/" ~ c; 7349 } 7350 break; 7351 case 2: // inside comment 7352 if(c == '*') 7353 state = 3; 7354 break; 7355 case 3: // expecting / to end comment 7356 if(c == '/') 7357 state = 0; 7358 else 7359 state = 2; // it's just a comment so no need to append 7360 break; 7361 case 4: 7362 if(c == '{') 7363 state = 5; 7364 if(c == ';') 7365 state = 0; // just skipping import 7366 break; 7367 case 5: 7368 if(c == '}') 7369 state = 0; // skipping font face probably 7370 } 7371 } 7372 } 7373 7374 /// Run through the document and apply this stylesheet to it. The computedStyle member will be accurate after this call 7375 void apply(Document document) { 7376 foreach(rule; rules) { 7377 if(rule.originatingRule.length == 0) 7378 continue; // this shouldn't happen here in a stylesheet 7379 foreach(element; document.querySelectorAll(rule.originatingRule)) { 7380 // note: this should be a different object than the inline style 7381 // since givenExplicitly is likely destroyed here 7382 auto current = element.computedStyle; 7383 7384 foreach(item; rule.properties) 7385 current.setValue(item.name, item.value, item.specificity); 7386 } 7387 } 7388 } 7389 } 7390 7391 7392 /// This is kinda private; just a little utility container for use by the ElementStream class. 7393 final class Stack(T) { 7394 this() { 7395 internalLength = 0; 7396 arr = initialBuffer[]; 7397 } 7398 7399 ///. 7400 void push(T t) { 7401 if(internalLength >= arr.length) { 7402 auto oldarr = arr; 7403 if(arr.length < 4096) 7404 arr = new T[arr.length * 2]; 7405 else 7406 arr = new T[arr.length + 4096]; 7407 arr[0 .. oldarr.length] = oldarr[]; 7408 } 7409 7410 arr[internalLength] = t; 7411 internalLength++; 7412 } 7413 7414 ///. 7415 T pop() { 7416 assert(internalLength); 7417 internalLength--; 7418 return arr[internalLength]; 7419 } 7420 7421 ///. 7422 T peek() { 7423 assert(internalLength); 7424 return arr[internalLength - 1]; 7425 } 7426 7427 ///. 7428 @property bool empty() { 7429 return internalLength ? false : true; 7430 } 7431 7432 ///. 7433 private T[] arr; 7434 private size_t internalLength; 7435 private T[64] initialBuffer; 7436 // the static array is allocated with this object, so if we have a small stack (which we prolly do; dom trees usually aren't insanely deep), 7437 // using this saves us a bunch of trips to the GC. In my last profiling, I got about a 50x improvement in the push() 7438 // function thanks to this, and push() was actually one of the slowest individual functions in the code! 7439 } 7440 7441 /// This is the lazy range that walks the tree for you. It tries to go in the lexical order of the source: node, then children from first to last, each recursively. 7442 final class ElementStream { 7443 7444 ///. 7445 @property Element front() { 7446 return current.element; 7447 } 7448 7449 /// Use Element.tree instead. 7450 this(Element start) { 7451 current.element = start; 7452 current.childPosition = -1; 7453 isEmpty = false; 7454 stack = new Stack!(Current); 7455 } 7456 7457 /* 7458 Handle it 7459 handle its children 7460 7461 */ 7462 7463 ///. 7464 void popFront() { 7465 more: 7466 if(isEmpty) return; 7467 7468 // FIXME: the profiler says this function is somewhat slow (noticeable because it can be called a lot of times) 7469 7470 current.childPosition++; 7471 if(current.childPosition >= current.element.children.length) { 7472 if(stack.empty()) 7473 isEmpty = true; 7474 else { 7475 current = stack.pop(); 7476 goto more; 7477 } 7478 } else { 7479 stack.push(current); 7480 current.element = current.element.children[current.childPosition]; 7481 current.childPosition = -1; 7482 } 7483 } 7484 7485 /// You should call this when you remove an element from the tree. It then doesn't recurse into that node and adjusts the current position, keeping the range stable. 7486 void currentKilled() { 7487 if(stack.empty) // should never happen 7488 isEmpty = true; 7489 else { 7490 current = stack.pop(); 7491 current.childPosition--; // when it is killed, the parent is brought back a lil so when we popFront, this is then right 7492 } 7493 } 7494 7495 ///. 7496 @property bool empty() { 7497 return isEmpty; 7498 } 7499 7500 private: 7501 7502 struct Current { 7503 Element element; 7504 int childPosition; 7505 } 7506 7507 Current current; 7508 7509 Stack!(Current) stack; 7510 7511 bool isEmpty; 7512 } 7513 7514 7515 7516 // unbelievable. 7517 // Don't use any of these in your own code. Instead, try to use phobos or roll your own, as I might kill these at any time. 7518 sizediff_t indexOfBytes(immutable(ubyte)[] haystack, immutable(ubyte)[] needle) { 7519 static import std.algorithm; 7520 auto found = std.algorithm.find(haystack, needle); 7521 if(found.length == 0) 7522 return -1; 7523 return haystack.length - found.length; 7524 } 7525 7526 private T[] insertAfter(T)(T[] arr, int position, T[] what) { 7527 assert(position < arr.length); 7528 T[] ret; 7529 ret.length = arr.length + what.length; 7530 int a = 0; 7531 foreach(i; arr[0..position+1]) 7532 ret[a++] = i; 7533 7534 foreach(i; what) 7535 ret[a++] = i; 7536 7537 foreach(i; arr[position+1..$]) 7538 ret[a++] = i; 7539 7540 return ret; 7541 } 7542 7543 package bool isInArray(T)(T item, T[] arr) { 7544 foreach(i; arr) 7545 if(item == i) 7546 return true; 7547 return false; 7548 } 7549 7550 private string[string] aadup(in string[string] arr) { 7551 string[string] ret; 7552 foreach(k, v; arr) 7553 ret[k] = v; 7554 return ret; 7555 } 7556 7557 7558 7559 7560 7561 7562 7563 7564 7565 7566 7567 7568 7569 7570 7571 // These MUST be sorted. See generatedomcases.d for a program to generate it if you need to add more than a few (otherwise maybe you can work it in yourself but yikes) 7572 7573 immutable string[] availableEntities = 7574 ["AElig", "AElig", "AMP", "AMP", "Aacute", "Aacute", "Abreve", "Abreve", "Acirc", "Acirc", "Acy", "Acy", "Afr", "Afr", "Agrave", "Agrave", "Alpha", "Alpha", "Amacr", "Amacr", "And", "And", "Aogon", "Aogon", "Aopf", "Aopf", "ApplyFunction", "ApplyFunction", "Aring", "Aring", "Ascr", "Ascr", "Assign", "Assign", "Atilde", 7575 "Atilde", "Auml", "Auml", "Backslash", "Backslash", "Barv", "Barv", "Barwed", "Barwed", "Bcy", "Bcy", "Because", "Because", "Bernoullis", "Bernoullis", "Beta", "Beta", "Bfr", "Bfr", "Bopf", "Bopf", "Breve", "Breve", "Bscr", "Bscr", "Bumpeq", "Bumpeq", "CHcy", "CHcy", "COPY", "COPY", "Cacute", "Cacute", "Cap", "Cap", "CapitalDifferentialD", 7576 "CapitalDifferentialD", "Cayleys", "Cayleys", "Ccaron", "Ccaron", "Ccedil", "Ccedil", "Ccirc", "Ccirc", "Cconint", "Cconint", "Cdot", "Cdot", "Cedilla", "Cedilla", "CenterDot", "CenterDot", "Cfr", "Cfr", "Chi", "Chi", "CircleDot", "CircleDot", "CircleMinus", "CircleMinus", "CirclePlus", "CirclePlus", "CircleTimes", "CircleTimes", 7577 "ClockwiseContourIntegral", "ClockwiseContourIntegral", "CloseCurlyDoubleQuote", "CloseCurlyDoubleQuote", "CloseCurlyQuote", "CloseCurlyQuote", "Colon", "Colon", "Colone", "Colone", "Congruent", "Congruent", "Conint", "Conint", "ContourIntegral", "ContourIntegral", "Copf", "Copf", "Coproduct", "Coproduct", "CounterClockwiseContourIntegral", 7578 "CounterClockwiseContourIntegral", "Cross", "Cross", "Cscr", "Cscr", "Cup", "Cup", "CupCap", "CupCap", "DD", "DD", "DDotrahd", "DDotrahd", "DJcy", "DJcy", "DScy", "DScy", "DZcy", "DZcy", "Dagger", "Dagger", "Darr", "Darr", "Dashv", "Dashv", "Dcaron", "Dcaron", "Dcy", "Dcy", "Del", "Del", "Delta", "Delta", "Dfr", "Dfr", 7579 "DiacriticalAcute", "DiacriticalAcute", "DiacriticalDot", "DiacriticalDot", "DiacriticalDoubleAcute", "DiacriticalDoubleAcute", "DiacriticalGrave", "DiacriticalGrave", "DiacriticalTilde", "DiacriticalTilde", "Diamond", "Diamond", "DifferentialD", "DifferentialD", "Dopf", "Dopf", "Dot", "Dot", "DotDot", "DotDot", "DotEqual", 7580 "DotEqual", "DoubleContourIntegral", "DoubleContourIntegral", "DoubleDot", "DoubleDot", "DoubleDownArrow", "DoubleDownArrow", "DoubleLeftArrow", "DoubleLeftArrow", "DoubleLeftRightArrow", "DoubleLeftRightArrow", "DoubleLeftTee", "DoubleLeftTee", "DoubleLongLeftArrow", "DoubleLongLeftArrow", "DoubleLongLeftRightArrow", 7581 "DoubleLongLeftRightArrow", "DoubleLongRightArrow", "DoubleLongRightArrow", "DoubleRightArrow", "DoubleRightArrow", "DoubleRightTee", "DoubleRightTee", "DoubleUpArrow", "DoubleUpArrow", "DoubleUpDownArrow", "DoubleUpDownArrow", "DoubleVerticalBar", "DoubleVerticalBar", "DownArrow", "DownArrow", "DownArrowBar", "DownArrowBar", 7582 "DownArrowUpArrow", "DownArrowUpArrow", "DownBreve", "DownBreve", "DownLeftRightVector", "DownLeftRightVector", "DownLeftTeeVector", "DownLeftTeeVector", "DownLeftVector", "DownLeftVector", "DownLeftVectorBar", "DownLeftVectorBar", "DownRightTeeVector", "DownRightTeeVector", "DownRightVector", "DownRightVector", "DownRightVectorBar", 7583 "DownRightVectorBar", "DownTee", "DownTee", "DownTeeArrow", "DownTeeArrow", "Downarrow", "Downarrow", "Dscr", "Dscr", "Dstrok", "Dstrok", "ENG", "ENG", "ETH", "ETH", "Eacute", "Eacute", "Ecaron", "Ecaron", "Ecirc", "Ecirc", "Ecy", "Ecy", "Edot", "Edot", "Efr", "Efr", "Egrave", "Egrave", "Element", "Element", "Emacr", "Emacr", 7584 "EmptySmallSquare", "EmptySmallSquare", "EmptyVerySmallSquare", "EmptyVerySmallSquare", "Eogon", "Eogon", "Eopf", "Eopf", "Epsilon", "Epsilon", "Equal", "Equal", "EqualTilde", "EqualTilde", "Equilibrium", "Equilibrium", "Escr", "Escr", "Esim", "Esim", "Eta", "Eta", "Euml", "Euml", "Exists", "Exists", "ExponentialE", "ExponentialE", 7585 "Fcy", "Fcy", "Ffr", "Ffr", "FilledSmallSquare", "FilledSmallSquare", "FilledVerySmallSquare", "FilledVerySmallSquare", "Fopf", "Fopf", "ForAll", "ForAll", "Fouriertrf", "Fouriertrf", "Fscr", "Fscr", "GJcy", "GJcy", "GT", "GT", "Gamma", "Gamma", "Gammad", "Gammad", "Gbreve", "Gbreve", "Gcedil", "Gcedil", "Gcirc", "Gcirc", 7586 "Gcy", "Gcy", "Gdot", "Gdot", "Gfr", "Gfr", "Gg", "Gg", "Gopf", "Gopf", "GreaterEqual", "GreaterEqual", "GreaterEqualLess", "GreaterEqualLess", "GreaterFullEqual", "GreaterFullEqual", "GreaterGreater", "GreaterGreater", "GreaterLess", "GreaterLess", "GreaterSlantEqual", "GreaterSlantEqual", "GreaterTilde", "GreaterTilde", 7587 "Gscr", "Gscr", "Gt", "Gt", "HARDcy", "HARDcy", "Hacek", "Hacek", "Hat", "Hat", "Hcirc", "Hcirc", "Hfr", "Hfr", "HilbertSpace", "HilbertSpace", "Hopf", "Hopf", "HorizontalLine", "HorizontalLine", "Hscr", "Hscr", "Hstrok", "Hstrok", "HumpDownHump", "HumpDownHump", "HumpEqual", "HumpEqual", "IEcy", "IEcy", "IJlig", "IJlig", 7588 "IOcy", "IOcy", "Iacute", "Iacute", "Icirc", "Icirc", "Icy", "Icy", "Idot", "Idot", "Ifr", "Ifr", "Igrave", "Igrave", "Im", "Im", "Imacr", "Imacr", "ImaginaryI", "ImaginaryI", "Implies", "Implies", "Int", "Int", "Integral", "Integral", "Intersection", "Intersection", "InvisibleComma", "InvisibleComma", "InvisibleTimes", 7589 "InvisibleTimes", "Iogon", "Iogon", "Iopf", "Iopf", "Iota", "Iota", "Iscr", "Iscr", "Itilde", "Itilde", "Iukcy", "Iukcy", "Iuml", "Iuml", "Jcirc", "Jcirc", "Jcy", "Jcy", "Jfr", "Jfr", "Jopf", "Jopf", "Jscr", "Jscr", "Jsercy", "Jsercy", "Jukcy", "Jukcy", "KHcy", "KHcy", "KJcy", "KJcy", "Kappa", "Kappa", "Kcedil", "Kcedil", 7590 "Kcy", "Kcy", "Kfr", "Kfr", "Kopf", "Kopf", "Kscr", "Kscr", "LJcy", "LJcy", "LT", "LT", "Lacute", "Lacute", "Lambda", "Lambda", "Lang", "Lang", "Laplacetrf", "Laplacetrf", "Larr", "Larr", "Lcaron", "Lcaron", "Lcedil", "Lcedil", "Lcy", "Lcy", "LeftAngleBracket", "LeftAngleBracket", "LeftArrow", "LeftArrow", "LeftArrowBar", 7591 "LeftArrowBar", "LeftArrowRightArrow", "LeftArrowRightArrow", "LeftCeiling", "LeftCeiling", "LeftDoubleBracket", "LeftDoubleBracket", "LeftDownTeeVector", "LeftDownTeeVector", "LeftDownVector", "LeftDownVector", "LeftDownVectorBar", "LeftDownVectorBar", "LeftFloor", "LeftFloor", "LeftRightArrow", "LeftRightArrow", "LeftRightVector", 7592 "LeftRightVector", "LeftTee", "LeftTee", "LeftTeeArrow", "LeftTeeArrow", "LeftTeeVector", "LeftTeeVector", "LeftTriangle", "LeftTriangle", "LeftTriangleBar", "LeftTriangleBar", "LeftTriangleEqual", "LeftTriangleEqual", "LeftUpDownVector", "LeftUpDownVector", "LeftUpTeeVector", "LeftUpTeeVector", "LeftUpVector", "LeftUpVector", 7593 "LeftUpVectorBar", "LeftUpVectorBar", "LeftVector", "LeftVector", "LeftVectorBar", "LeftVectorBar", "Leftarrow", "Leftarrow", "Leftrightarrow", "Leftrightarrow", "LessEqualGreater", "LessEqualGreater", "LessFullEqual", "LessFullEqual", "LessGreater", "LessGreater", "LessLess", "LessLess", "LessSlantEqual", "LessSlantEqual", 7594 "LessTilde", "LessTilde", "Lfr", "Lfr", "Ll", "Ll", "Lleftarrow", "Lleftarrow", "Lmidot", "Lmidot", "LongLeftArrow", "LongLeftArrow", "LongLeftRightArrow", "LongLeftRightArrow", "LongRightArrow", "LongRightArrow", "Longleftarrow", "Longleftarrow", "Longleftrightarrow", "Longleftrightarrow", "Longrightarrow", "Longrightarrow", 7595 "Lopf", "Lopf", "LowerLeftArrow", "LowerLeftArrow", "LowerRightArrow", "LowerRightArrow", "Lscr", "Lscr", "Lsh", "Lsh", "Lstrok", "Lstrok", "Lt", "Lt", "Map", "Map", "Mcy", "Mcy", "MediumSpace", "MediumSpace", "Mellintrf", "Mellintrf", "Mfr", "Mfr", "MinusPlus", "MinusPlus", "Mopf", "Mopf", "Mscr", "Mscr", "Mu", "Mu", 7596 "NJcy", "NJcy", "Nacute", "Nacute", "Ncaron", "Ncaron", "Ncedil", "Ncedil", "Ncy", "Ncy", "NegativeMediumSpace", "NegativeMediumSpace", "NegativeThickSpace", "NegativeThickSpace", "NegativeThinSpace", "NegativeThinSpace", "NegativeVeryThinSpace", "NegativeVeryThinSpace", "NestedGreaterGreater", "NestedGreaterGreater", 7597 "NestedLessLess", "NestedLessLess", "NewLine", "NewLine", "Nfr", "Nfr", "NoBreak", "NoBreak", "NonBreakingSpace", "NonBreakingSpace", "Nopf", "Nopf", "Not", "Not", "NotCongruent", "NotCongruent", "NotCupCap", "NotCupCap", "NotDoubleVerticalBar", "NotDoubleVerticalBar", "NotElement", "NotElement", "NotEqual", "NotEqual", 7598 "NotExists", "NotExists", "NotGreater", "NotGreater", "NotGreaterEqual", "NotGreaterEqual", "NotGreaterLess", "NotGreaterLess", "NotGreaterTilde", "NotGreaterTilde", "NotLeftTriangle", "NotLeftTriangle", "NotLeftTriangleEqual", "NotLeftTriangleEqual", "NotLess", "NotLess", "NotLessEqual", "NotLessEqual", "NotLessGreater", 7599 "NotLessGreater", "NotLessTilde", "NotLessTilde", "NotPrecedes", "NotPrecedes", "NotPrecedesSlantEqual", "NotPrecedesSlantEqual", "NotReverseElement", "NotReverseElement", "NotRightTriangle", "NotRightTriangle", "NotRightTriangleEqual", "NotRightTriangleEqual", "NotSquareSubsetEqual", "NotSquareSubsetEqual", "NotSquareSupersetEqual", 7600 "NotSquareSupersetEqual", "NotSubsetEqual", "NotSubsetEqual", "NotSucceeds", "NotSucceeds", "NotSucceedsSlantEqual", "NotSucceedsSlantEqual", "NotSupersetEqual", "NotSupersetEqual", "NotTilde", "NotTilde", "NotTildeEqual", "NotTildeEqual", "NotTildeFullEqual", "NotTildeFullEqual", "NotTildeTilde", "NotTildeTilde", "NotVerticalBar", 7601 "NotVerticalBar", "Nscr", "Nscr", "Ntilde", "Ntilde", "Nu", "Nu", "OElig", "OElig", "Oacute", "Oacute", "Ocirc", "Ocirc", "Ocy", "Ocy", "Odblac", "Odblac", "Ofr", "Ofr", "Ograve", "Ograve", "Omacr", "Omacr", "Omega", "Omega", "Omicron", "Omicron", "Oopf", "Oopf", "OpenCurlyDoubleQuote", "OpenCurlyDoubleQuote", "OpenCurlyQuote", 7602 "OpenCurlyQuote", "Or", "Or", "Oscr", "Oscr", "Oslash", "Oslash", "Otilde", "Otilde", "Otimes", "Otimes", "Ouml", "Ouml", "OverBar", "OverBar", "OverBrace", "OverBrace", "OverBracket", "OverBracket", "OverParenthesis", "OverParenthesis", "PartialD", "PartialD", "Pcy", "Pcy", "Pfr", "Pfr", "Phi", "Phi", "Pi", "Pi", "PlusMinus", 7603 "PlusMinus", "Poincareplane", "Poincareplane", "Popf", "Popf", "Pr", "Pr", "Precedes", "Precedes", "PrecedesEqual", "PrecedesEqual", "PrecedesSlantEqual", "PrecedesSlantEqual", "PrecedesTilde", "PrecedesTilde", "Prime", "Prime", "Product", "Product", "Proportion", "Proportion", "Proportional", "Proportional", "Pscr", "Pscr", 7604 "Psi", "Psi", "QUOT", "QUOT", "Qfr", "Qfr", "Qopf", "Qopf", "Qscr", "Qscr", "RBarr", "RBarr", "REG", "REG", "Racute", "Racute", "Rang", "Rang", "Rarr", "Rarr", "Rarrtl", "Rarrtl", "Rcaron", "Rcaron", "Rcedil", "Rcedil", "Rcy", "Rcy", "Re", "Re", "ReverseElement", "ReverseElement", "ReverseEquilibrium", "ReverseEquilibrium", 7605 "ReverseUpEquilibrium", "ReverseUpEquilibrium", "Rfr", "Rfr", "Rho", "Rho", "RightAngleBracket", "RightAngleBracket", "RightArrow", "RightArrow", "RightArrowBar", "RightArrowBar", "RightArrowLeftArrow", "RightArrowLeftArrow", "RightCeiling", "RightCeiling", "RightDoubleBracket", "RightDoubleBracket", "RightDownTeeVector", 7606 "RightDownTeeVector", "RightDownVector", "RightDownVector", "RightDownVectorBar", "RightDownVectorBar", "RightFloor", "RightFloor", "RightTee", "RightTee", "RightTeeArrow", "RightTeeArrow", "RightTeeVector", "RightTeeVector", "RightTriangle", "RightTriangle", "RightTriangleBar", "RightTriangleBar", "RightTriangleEqual", 7607 "RightTriangleEqual", "RightUpDownVector", "RightUpDownVector", "RightUpTeeVector", "RightUpTeeVector", "RightUpVector", "RightUpVector", "RightUpVectorBar", "RightUpVectorBar", "RightVector", "RightVector", "RightVectorBar", "RightVectorBar", "Rightarrow", "Rightarrow", "Ropf", "Ropf", "RoundImplies", "RoundImplies", 7608 "Rrightarrow", "Rrightarrow", "Rscr", "Rscr", "Rsh", "Rsh", "RuleDelayed", "RuleDelayed", "SHCHcy", "SHCHcy", "SHcy", "SHcy", "SOFTcy", "SOFTcy", "Sacute", "Sacute", "Sc", "Sc", "Scaron", "Scaron", "Scedil", "Scedil", "Scirc", "Scirc", "Scy", "Scy", "Sfr", "Sfr", "ShortDownArrow", "ShortDownArrow", "ShortLeftArrow", "ShortLeftArrow", 7609 "ShortRightArrow", "ShortRightArrow", "ShortUpArrow", "ShortUpArrow", "Sigma", "Sigma", "SmallCircle", "SmallCircle", "Sopf", "Sopf", "Sqrt", "Sqrt", "Square", "Square", "SquareIntersection", "SquareIntersection", "SquareSubset", "SquareSubset", "SquareSubsetEqual", "SquareSubsetEqual", "SquareSuperset", "SquareSuperset", 7610 "SquareSupersetEqual", "SquareSupersetEqual", "SquareUnion", "SquareUnion", "Sscr", "Sscr", "Star", "Star", "Sub", "Sub", "Subset", "Subset", "SubsetEqual", "SubsetEqual", "Succeeds", "Succeeds", "SucceedsEqual", "SucceedsEqual", "SucceedsSlantEqual", "SucceedsSlantEqual", "SucceedsTilde", "SucceedsTilde", "SuchThat", 7611 "SuchThat", "Sum", "Sum", "Sup", "Sup", "Superset", "Superset", "SupersetEqual", "SupersetEqual", "Supset", "Supset", "THORN", "THORN", "TRADE", "TRADE", "TSHcy", "TSHcy", "TScy", "TScy", "Tab", "Tab", "Tau", "Tau", "Tcaron", "Tcaron", "Tcedil", "Tcedil", "Tcy", "Tcy", "Tfr", "Tfr", "Therefore", "Therefore", "Theta", "Theta", 7612 "ThinSpace", "ThinSpace", "Tilde", "Tilde", "TildeEqual", "TildeEqual", "TildeFullEqual", "TildeFullEqual", "TildeTilde", "TildeTilde", "Topf", "Topf", "TripleDot", "TripleDot", "Tscr", "Tscr", "Tstrok", "Tstrok", "Uacute", "Uacute", "Uarr", "Uarr", "Uarrocir", "Uarrocir", "Ubrcy", "Ubrcy", "Ubreve", "Ubreve", "Ucirc", 7613 "Ucirc", "Ucy", "Ucy", "Udblac", "Udblac", "Ufr", "Ufr", "Ugrave", "Ugrave", "Umacr", "Umacr", "UnderBar", "UnderBar", "UnderBrace", "UnderBrace", "UnderBracket", "UnderBracket", "UnderParenthesis", "UnderParenthesis", "Union", "Union", "UnionPlus", "UnionPlus", "Uogon", "Uogon", "Uopf", "Uopf", "UpArrow", "UpArrow", "UpArrowBar", 7614 "UpArrowBar", "UpArrowDownArrow", "UpArrowDownArrow", "UpDownArrow", "UpDownArrow", "UpEquilibrium", "UpEquilibrium", "UpTee", "UpTee", "UpTeeArrow", "UpTeeArrow", "Uparrow", "Uparrow", "Updownarrow", "Updownarrow", "UpperLeftArrow", "UpperLeftArrow", "UpperRightArrow", "UpperRightArrow", "Upsi", "Upsi", "Upsilon", "Upsilon", 7615 "Uring", "Uring", "Uscr", "Uscr", "Utilde", "Utilde", "Uuml", "Uuml", "VDash", "VDash", "Vbar", "Vbar", "Vcy", "Vcy", "Vdash", "Vdash", "Vdashl", "Vdashl", "Vee", "Vee", "Verbar", "Verbar", "Vert", "Vert", "VerticalBar", "VerticalBar", "VerticalLine", "VerticalLine", "VerticalSeparator", "VerticalSeparator", "VerticalTilde", 7616 "VerticalTilde", "VeryThinSpace", "VeryThinSpace", "Vfr", "Vfr", "Vopf", "Vopf", "Vscr", "Vscr", "Vvdash", "Vvdash", "Wcirc", "Wcirc", "Wedge", "Wedge", "Wfr", "Wfr", "Wopf", "Wopf", "Wscr", "Wscr", "Xfr", "Xfr", "Xi", "Xi", "Xopf", "Xopf", "Xscr", "Xscr", "YAcy", "YAcy", "YIcy", "YIcy", "YUcy", "YUcy", "Yacute", "Yacute", 7617 "Ycirc", "Ycirc", "Ycy", "Ycy", "Yfr", "Yfr", "Yopf", "Yopf", "Yscr", "Yscr", "Yuml", "Yuml", "ZHcy", "ZHcy", "Zacute", "Zacute", "Zcaron", "Zcaron", "Zcy", "Zcy", "Zdot", "Zdot", "ZeroWidthSpace", "ZeroWidthSpace", "Zeta", "Zeta", "Zfr", "Zfr", "Zopf", "Zopf", "Zscr", "Zscr", "aacute", "aacute", "abreve", "abreve", "ac", 7618 "ac", "acd", "acd", "acirc", "acirc", "acute", "acute", "acy", "acy", "aelig", "aelig", "af", "af", "afr", "afr", "agrave", "agrave", "alefsym", "alefsym", "aleph", "aleph", "alpha", "alpha", "amacr", "amacr", "amalg", "amalg", "and", "and", "andand", "andand", "andd", "andd", "andslope", "andslope", "andv", "andv", "ang", 7619 "ang", "ange", "ange", "angle", "angle", "angmsd", "angmsd", "angmsdaa", "angmsdaa", "angmsdab", "angmsdab", "angmsdac", "angmsdac", "angmsdad", "angmsdad", "angmsdae", "angmsdae", "angmsdaf", "angmsdaf", "angmsdag", "angmsdag", "angmsdah", "angmsdah", "angrt", "angrt", "angrtvb", "angrtvb", "angrtvbd", "angrtvbd", "angsph", 7620 "angsph", "angst", "angst", "angzarr", "angzarr", "aogon", "aogon", "aopf", "aopf", "ap", "ap", "apE", "apE", "apacir", "apacir", "ape", "ape", "apid", "apid", "approx", "approx", "approxeq", "approxeq", "aring", "aring", "ascr", "ascr", "ast", "ast", "asymp", "asymp", "asympeq", "asympeq", "atilde", "atilde", "auml", 7621 "auml", "awconint", "awconint", "awint", "awint", "bNot", "bNot", "backcong", "backcong", "backepsilon", "backepsilon", "backprime", "backprime", "backsim", "backsim", "backsimeq", "backsimeq", "barvee", "barvee", "barwed", "barwed", "barwedge", "barwedge", "bbrk", "bbrk", "bbrktbrk", "bbrktbrk", "bcong", "bcong", "bcy", 7622 "bcy", "bdquo", "bdquo", "becaus", "becaus", "because", "because", "bemptyv", "bemptyv", "bepsi", "bepsi", "bernou", "bernou", "beta", "beta", "beth", "beth", "between", "between", "bfr", "bfr", "bigcap", "bigcap", "bigcirc", "bigcirc", "bigcup", "bigcup", "bigodot", "bigodot", "bigoplus", "bigoplus", "bigotimes", "bigotimes", 7623 "bigsqcup", "bigsqcup", "bigstar", "bigstar", "bigtriangledown", "bigtriangledown", "bigtriangleup", "bigtriangleup", "biguplus", "biguplus", "bigvee", "bigvee", "bigwedge", "bigwedge", "bkarow", "bkarow", "blacklozenge", "blacklozenge", "blacksquare", "blacksquare", "blacktriangle", "blacktriangle", "blacktriangledown", 7624 "blacktriangledown", "blacktriangleleft", "blacktriangleleft", "blacktriangleright", "blacktriangleright", "blank", "blank", "blk12", "blk12", "blk14", "blk14", "blk34", "blk34", "block", "block", "bnot", "bnot", "bopf", "bopf", "bot", "bot", "bottom", "bottom", "bowtie", "bowtie", "boxDL", "boxDL", "boxDR", "boxDR", "boxDl", 7625 "boxDl", "boxDr", "boxDr", "boxH", "boxH", "boxHD", "boxHD", "boxHU", "boxHU", "boxHd", "boxHd", "boxHu", "boxHu", "boxUL", "boxUL", "boxUR", "boxUR", "boxUl", "boxUl", "boxUr", "boxUr", "boxV", "boxV", "boxVH", "boxVH", "boxVL", "boxVL", "boxVR", "boxVR", "boxVh", "boxVh", "boxVl", "boxVl", "boxVr", "boxVr", "boxbox", 7626 "boxbox", "boxdL", "boxdL", "boxdR", "boxdR", "boxdl", "boxdl", "boxdr", "boxdr", "boxh", "boxh", "boxhD", "boxhD", "boxhU", "boxhU", "boxhd", "boxhd", "boxhu", "boxhu", "boxminus", "boxminus", "boxplus", "boxplus", "boxtimes", "boxtimes", "boxuL", "boxuL", "boxuR", "boxuR", "boxul", "boxul", "boxur", "boxur", "boxv", 7627 "boxv", "boxvH", "boxvH", "boxvL", "boxvL", "boxvR", "boxvR", "boxvh", "boxvh", "boxvl", "boxvl", "boxvr", "boxvr", "bprime", "bprime", "breve", "breve", "brvbar", "brvbar", "bscr", "bscr", "bsemi", "bsemi", "bsim", "bsim", "bsime", "bsime", "bsol", "bsol", "bsolb", "bsolb", "bsolhsub", "bsolhsub", "bull", "bull", "bullet", 7628 "bullet", "bump", "bump", "bumpE", "bumpE", "bumpe", "bumpe", "bumpeq", "bumpeq", "cacute", "cacute", "cap", "cap", "capand", "capand", "capbrcup", "capbrcup", "capcap", "capcap", "capcup", "capcup", "capdot", "capdot", "caret", "caret", "caron", "caron", "ccaps", "ccaps", "ccaron", "ccaron", "ccedil", "ccedil", "ccirc", 7629 "ccirc", "ccups", "ccups", "ccupssm", "ccupssm", "cdot", "cdot", "cedil", "cedil", "cemptyv", "cemptyv", "cent", "cent", "centerdot", "centerdot", "cfr", "cfr", "chcy", "chcy", "check", "check", "checkmark", "checkmark", "chi", "chi", "cir", "cir", "cirE", "cirE", "circ", "circ", "circeq", "circeq", "circlearrowleft", 7630 "circlearrowleft", "circlearrowright", "circlearrowright", "circledR", "circledR", "circledS", "circledS", "circledast", "circledast", "circledcirc", "circledcirc", "circleddash", "circleddash", "cire", "cire", "cirfnint", "cirfnint", "cirmid", "cirmid", "cirscir", "cirscir", "clubs", "clubs", "clubsuit", "clubsuit", "colon", 7631 "colon", "colone", "colone", "coloneq", "coloneq", "comma", "comma", "commat", "commat", "comp", "comp", "compfn", "compfn", "complement", "complement", "complexes", "complexes", "cong", "cong", "congdot", "congdot", "conint", "conint", "copf", "copf", "coprod", "coprod", "copy", "copy", "copysr", "copysr", "crarr", "crarr", 7632 "cross", "cross", "cscr", "cscr", "csub", "csub", "csube", "csube", "csup", "csup", "csupe", "csupe", "ctdot", "ctdot", "cudarrl", "cudarrl", "cudarrr", "cudarrr", "cuepr", "cuepr", "cuesc", "cuesc", "cularr", "cularr", "cularrp", "cularrp", "cup", "cup", "cupbrcap", "cupbrcap", "cupcap", "cupcap", "cupcup", "cupcup", 7633 "cupdot", "cupdot", "cupor", "cupor", "curarr", "curarr", "curarrm", "curarrm", "curlyeqprec", "curlyeqprec", "curlyeqsucc", "curlyeqsucc", "curlyvee", "curlyvee", "curlywedge", "curlywedge", "curren", "curren", "curvearrowleft", "curvearrowleft", "curvearrowright", "curvearrowright", "cuvee", "cuvee", "cuwed", "cuwed", 7634 "cwconint", "cwconint", "cwint", "cwint", "cylcty", "cylcty", "dArr", "dArr", "dHar", "dHar", "dagger", "dagger", "daleth", "daleth", "darr", "darr", "dash", "dash", "dashv", "dashv", "dbkarow", "dbkarow", "dblac", "dblac", "dcaron", "dcaron", "dcy", "dcy", "dd", "dd", "ddagger", "ddagger", "ddarr", "ddarr", "ddotseq", 7635 "ddotseq", "deg", "deg", "delta", "delta", "demptyv", "demptyv", "dfisht", "dfisht", "dfr", "dfr", "dharl", "dharl", "dharr", "dharr", "diam", "diam", "diamond", "diamond", "diamondsuit", "diamondsuit", "diams", "diams", "die", "die", "digamma", "digamma", "disin", "disin", "div", "div", "divide", "divide", "divideontimes", 7636 "divideontimes", "divonx", "divonx", "djcy", "djcy", "dlcorn", "dlcorn", "dlcrop", "dlcrop", "dollar", "dollar", "dopf", "dopf", "dot", "dot", "doteq", "doteq", "doteqdot", "doteqdot", "dotminus", "dotminus", "dotplus", "dotplus", "dotsquare", "dotsquare", "doublebarwedge", "doublebarwedge", "downarrow", "downarrow", "downdownarrows", 7637 "downdownarrows", "downharpoonleft", "downharpoonleft", "downharpoonright", "downharpoonright", "drbkarow", "drbkarow", "drcorn", "drcorn", "drcrop", "drcrop", "dscr", "dscr", "dscy", "dscy", "dsol", "dsol", "dstrok", "dstrok", "dtdot", "dtdot", "dtri", "dtri", "dtrif", "dtrif", "duarr", "duarr", "duhar", "duhar", "dwangle", 7638 "dwangle", "dzcy", "dzcy", "dzigrarr", "dzigrarr", "eDDot", "eDDot", "eDot", "eDot", "eacute", "eacute", "easter", "easter", "ecaron", "ecaron", "ecir", "ecir", "ecirc", "ecirc", "ecolon", "ecolon", "ecy", "ecy", "edot", "edot", "ee", "ee", "efDot", "efDot", "efr", "efr", "eg", "eg", "egrave", "egrave", "egs", "egs", "egsdot", 7639 "egsdot", "el", "el", "elinters", "elinters", "ell", "ell", "els", "els", "elsdot", "elsdot", "emacr", "emacr", "empty", "empty", "emptyset", "emptyset", "emptyv", "emptyv", "emsp", "emsp", "emsp13", "emsp13", "emsp14", "emsp14", "eng", "eng", "ensp", "ensp", "eogon", "eogon", "eopf", "eopf", "epar", "epar", "eparsl", 7640 "eparsl", "eplus", "eplus", "epsi", "epsi", "epsilon", "epsilon", "epsiv", "epsiv", "eqcirc", "eqcirc", "eqcolon", "eqcolon", "eqsim", "eqsim", "eqslantgtr", "eqslantgtr", "eqslantless", "eqslantless", "equals", "equals", "equest", "equest", "equiv", "equiv", "equivDD", "equivDD", "eqvparsl", "eqvparsl", "erDot", "erDot", 7641 "erarr", "erarr", "escr", "escr", "esdot", "esdot", "esim", "esim", "eta", "eta", "eth", "eth", "euml", "euml", "euro", "euro", "excl", "excl", "exist", "exist", "expectation", "expectation", "exponentiale", "exponentiale", "fallingdotseq", "fallingdotseq", "fcy", "fcy", "female", "female", "ffilig", "ffilig", "fflig", 7642 "fflig", "ffllig", "ffllig", "ffr", "ffr", "filig", "filig", "flat", "flat", "fllig", "fllig", "fltns", "fltns", "fnof", "fnof", "fopf", "fopf", "forall", "forall", "fork", "fork", "forkv", "forkv", "fpartint", "fpartint", "frac12", "frac12", "frac13", "frac13", "frac14", "frac14", "frac15", "frac15", "frac16", "frac16", 7643 "frac18", "frac18", "frac23", "frac23", "frac25", "frac25", "frac34", "frac34", "frac35", "frac35", "frac38", "frac38", "frac45", "frac45", "frac56", "frac56", "frac58", "frac58", "frac78", "frac78", "frasl", "frasl", "frown", "frown", "fscr", "fscr", "gE", "gE", "gEl", "gEl", "gacute", "gacute", "gamma", "gamma", "gammad", 7644 "gammad", "gap", "gap", "gbreve", "gbreve", "gcirc", "gcirc", "gcy", "gcy", "gdot", "gdot", "ge", "ge", "gel", "gel", "geq", "geq", "geqq", "geqq", "geqslant", "geqslant", "ges", "ges", "gescc", "gescc", "gesdot", "gesdot", "gesdoto", "gesdoto", "gesdotol", "gesdotol", "gesles", "gesles", "gfr", "gfr", "gg", "gg", "ggg", 7645 "ggg", "gimel", "gimel", "gjcy", "gjcy", "gl", "gl", "glE", "glE", "gla", "gla", "glj", "glj", "gnE", "gnE", "gnap", "gnap", "gnapprox", "gnapprox", "gne", "gne", "gneq", "gneq", "gneqq", "gneqq", "gnsim", "gnsim", "gopf", "gopf", "grave", "grave", "gscr", "gscr", "gsim", "gsim", "gsime", "gsime", "gsiml", "gsiml", "gtcc", 7646 "gtcc", "gtcir", "gtcir", "gtdot", "gtdot", "gtlPar", "gtlPar", "gtquest", "gtquest", "gtrapprox", "gtrapprox", "gtrarr", "gtrarr", "gtrdot", "gtrdot", "gtreqless", "gtreqless", "gtreqqless", "gtreqqless", "gtrless", "gtrless", "gtrsim", "gtrsim", "hArr", "hArr", "hairsp", "hairsp", "half", "half", "hamilt", "hamilt", 7647 "hardcy", "hardcy", "harr", "harr", "harrcir", "harrcir", "harrw", "harrw", "hbar", "hbar", "hcirc", "hcirc", "hearts", "hearts", "heartsuit", "heartsuit", "hellip", "hellip", "hercon", "hercon", "hfr", "hfr", "hksearow", "hksearow", "hkswarow", "hkswarow", "hoarr", "hoarr", "homtht", "homtht", "hookleftarrow", "hookleftarrow", 7648 "hookrightarrow", "hookrightarrow", "hopf", "hopf", "horbar", "horbar", "hscr", "hscr", "hslash", "hslash", "hstrok", "hstrok", "hybull", "hybull", "hyphen", "hyphen", "iacute", "iacute", "ic", "ic", "icirc", "icirc", "icy", "icy", "iecy", "iecy", "iexcl", "iexcl", "iff", "iff", "ifr", "ifr", "igrave", "igrave", "ii", 7649 "ii", "iiiint", "iiiint", "iiint", "iiint", "iinfin", "iinfin", "iiota", "iiota", "ijlig", "ijlig", "imacr", "imacr", "image", "image", "imagline", "imagline", "imagpart", "imagpart", "imath", "imath", "imof", "imof", "imped", "imped", "in", "in", "incare", "incare", "infin", "infin", "infintie", "infintie", "inodot", 7650 "inodot", "int", "int", "intcal", "intcal", "integers", "integers", "intercal", "intercal", "intlarhk", "intlarhk", "intprod", "intprod", "iocy", "iocy", "iogon", "iogon", "iopf", "iopf", "iota", "iota", "iprod", "iprod", "iquest", "iquest", "iscr", "iscr", "isin", "isin", "isinE", "isinE", "isindot", "isindot", "isins", 7651 "isins", "isinsv", "isinsv", "isinv", "isinv", "it", "it", "itilde", "itilde", "iukcy", "iukcy", "iuml", "iuml", "jcirc", "jcirc", "jcy", "jcy", "jfr", "jfr", "jmath", "jmath", "jopf", "jopf", "jscr", "jscr", "jsercy", "jsercy", "jukcy", "jukcy", "kappa", "kappa", "kappav", "kappav", "kcedil", "kcedil", "kcy", "kcy", "kfr", 7652 "kfr", "kgreen", "kgreen", "khcy", "khcy", "kjcy", "kjcy", "kopf", "kopf", "kscr", "kscr", "lAarr", "lAarr", "lArr", "lArr", "lAtail", "lAtail", "lBarr", "lBarr", "lE", "lE", "lEg", "lEg", "lHar", "lHar", "lacute", "lacute", "laemptyv", "laemptyv", "lagran", "lagran", "lambda", "lambda", "lang", "lang", "langd", "langd", 7653 "langle", "langle", "lap", "lap", "laquo", "laquo", "larr", "larr", "larrb", "larrb", "larrbfs", "larrbfs", "larrfs", "larrfs", "larrhk", "larrhk", "larrlp", "larrlp", "larrpl", "larrpl", "larrsim", "larrsim", "larrtl", "larrtl", "lat", "lat", "latail", "latail", "late", "late", "lbarr", "lbarr", "lbbrk", "lbbrk", "lbrace", 7654 "lbrace", "lbrack", "lbrack", "lbrke", "lbrke", "lbrksld", "lbrksld", "lbrkslu", "lbrkslu", "lcaron", "lcaron", "lcedil", "lcedil", "lceil", "lceil", "lcub", "lcub", "lcy", "lcy", "ldca", "ldca", "ldquo", "ldquo", "ldquor", "ldquor", "ldrdhar", "ldrdhar", "ldrushar", "ldrushar", "ldsh", "ldsh", "le", "le", "leftarrow", 7655 "leftarrow", "leftarrowtail", "leftarrowtail", "leftharpoondown", "leftharpoondown", "leftharpoonup", "leftharpoonup", "leftleftarrows", "leftleftarrows", "leftrightarrow", "leftrightarrow", "leftrightarrows", "leftrightarrows", "leftrightharpoons", "leftrightharpoons", "leftrightsquigarrow", "leftrightsquigarrow", "leftthreetimes", 7656 "leftthreetimes", "leg", "leg", "leq", "leq", "leqq", "leqq", "leqslant", "leqslant", "les", "les", "lescc", "lescc", "lesdot", "lesdot", "lesdoto", "lesdoto", "lesdotor", "lesdotor", "lesges", "lesges", "lessapprox", "lessapprox", "lessdot", "lessdot", "lesseqgtr", "lesseqgtr", "lesseqqgtr", "lesseqqgtr", "lessgtr", "lessgtr", 7657 "lesssim", "lesssim", "lfisht", "lfisht", "lfloor", "lfloor", "lfr", "lfr", "lg", "lg", "lgE", "lgE", "lhard", "lhard", "lharu", "lharu", "lharul", "lharul", "lhblk", "lhblk", "ljcy", "ljcy", "ll", "ll", "llarr", "llarr", "llcorner", "llcorner", "llhard", "llhard", "lltri", "lltri", "lmidot", "lmidot", "lmoust", "lmoust", 7658 "lmoustache", "lmoustache", "lnE", "lnE", "lnap", "lnap", "lnapprox", "lnapprox", "lne", "lne", "lneq", "lneq", "lneqq", "lneqq", "lnsim", "lnsim", "loang", "loang", "loarr", "loarr", "lobrk", "lobrk", "longleftarrow", "longleftarrow", "longleftrightarrow", "longleftrightarrow", "longmapsto", "longmapsto", "longrightarrow", 7659 "longrightarrow", "looparrowleft", "looparrowleft", "looparrowright", "looparrowright", "lopar", "lopar", "lopf", "lopf", "loplus", "loplus", "lotimes", "lotimes", "lowast", "lowast", "lowbar", "lowbar", "loz", "loz", "lozenge", "lozenge", "lozf", "lozf", "lpar", "lpar", "lparlt", "lparlt", "lrarr", "lrarr", "lrcorner", 7660 "lrcorner", "lrhar", "lrhar", "lrhard", "lrhard", "lrm", "lrm", "lrtri", "lrtri", "lsaquo", "lsaquo", "lscr", "lscr", "lsh", "lsh", "lsim", "lsim", "lsime", "lsime", "lsimg", "lsimg", "lsqb", "lsqb", "lsquo", "lsquo", "lsquor", "lsquor", "lstrok", "lstrok", "ltcc", "ltcc", "ltcir", "ltcir", "ltdot", "ltdot", "lthree", 7661 "lthree", "ltimes", "ltimes", "ltlarr", "ltlarr", "ltquest", "ltquest", "ltrPar", "ltrPar", "ltri", "ltri", "ltrie", "ltrie", "ltrif", "ltrif", "lurdshar", "lurdshar", "luruhar", "luruhar", "mDDot", "mDDot", "macr", "macr", "male", "male", "malt", "malt", "maltese", "maltese", "map", "map", "mapsto", "mapsto", "mapstodown", 7662 "mapstodown", "mapstoleft", "mapstoleft", "mapstoup", "mapstoup", "marker", "marker", "mcomma", "mcomma", "mcy", "mcy", "mdash", "mdash", "measuredangle", "measuredangle", "mfr", "mfr", "mho", "mho", "micro", "micro", "mid", "mid", "midast", "midast", "midcir", "midcir", "middot", "middot", "minus", "minus", "minusb", 7663 "minusb", "minusd", "minusd", "minusdu", "minusdu", "mlcp", "mlcp", "mldr", "mldr", "mnplus", "mnplus", "models", "models", "mopf", "mopf", "mp", "mp", "mscr", "mscr", "mstpos", "mstpos", "mu", "mu", "multimap", "multimap", "mumap", "mumap", "nLeftarrow", "nLeftarrow", "nLeftrightarrow", "nLeftrightarrow", "nRightarrow", 7664 "nRightarrow", "nVDash", "nVDash", "nVdash", "nVdash", "nabla", "nabla", "nacute", "nacute", "nap", "nap", "napos", "napos", "napprox", "napprox", "natur", "natur", "natural", "natural", "naturals", "naturals", "nbsp", "nbsp", "ncap", "ncap", "ncaron", "ncaron", "ncedil", "ncedil", "ncong", "ncong", "ncup", "ncup", "ncy", 7665 "ncy", "ndash", "ndash", "ne", "ne", "neArr", "neArr", "nearhk", "nearhk", "nearr", "nearr", "nearrow", "nearrow", "nequiv", "nequiv", "nesear", "nesear", "nexist", "nexist", "nexists", "nexists", "nfr", "nfr", "nge", "nge", "ngeq", "ngeq", "ngsim", "ngsim", "ngt", "ngt", "ngtr", "ngtr", "nhArr", "nhArr", "nharr", "nharr", 7666 "nhpar", "nhpar", "ni", "ni", "nis", "nis", "nisd", "nisd", "niv", "niv", "njcy", "njcy", "nlArr", "nlArr", "nlarr", "nlarr", "nldr", "nldr", "nle", "nle", "nleftarrow", "nleftarrow", "nleftrightarrow", "nleftrightarrow", "nleq", "nleq", "nless", "nless", "nlsim", "nlsim", "nlt", "nlt", "nltri", "nltri", "nltrie", "nltrie", 7667 "nmid", "nmid", "nopf", "nopf", "not", "not", "notin", "notin", "notinva", "notinva", "notinvb", "notinvb", "notinvc", "notinvc", "notni", "notni", "notniva", "notniva", "notnivb", "notnivb", "notnivc", "notnivc", "npar", "npar", "nparallel", "nparallel", "npolint", "npolint", "npr", "npr", "nprcue", "nprcue", "nprec", 7668 "nprec", "nrArr", "nrArr", "nrarr", "nrarr", "nrightarrow", "nrightarrow", "nrtri", "nrtri", "nrtrie", "nrtrie", "nsc", "nsc", "nsccue", "nsccue", "nscr", "nscr", "nshortmid", "nshortmid", "nshortparallel", "nshortparallel", "nsim", "nsim", "nsime", "nsime", "nsimeq", "nsimeq", "nsmid", "nsmid", "nspar", "nspar", "nsqsube", 7669 "nsqsube", "nsqsupe", "nsqsupe", "nsub", "nsub", "nsube", "nsube", "nsubseteq", "nsubseteq", "nsucc", "nsucc", "nsup", "nsup", "nsupe", "nsupe", "nsupseteq", "nsupseteq", "ntgl", "ntgl", "ntilde", "ntilde", "ntlg", "ntlg", "ntriangleleft", "ntriangleleft", "ntrianglelefteq", "ntrianglelefteq", "ntriangleright", "ntriangleright", 7670 "ntrianglerighteq", "ntrianglerighteq", "nu", "nu", "num", "num", "numero", "numero", "numsp", "numsp", "nvDash", "nvDash", "nvHarr", "nvHarr", "nvdash", "nvdash", "nvinfin", "nvinfin", "nvlArr", "nvlArr", "nvrArr", "nvrArr", "nwArr", "nwArr", "nwarhk", "nwarhk", "nwarr", "nwarr", "nwarrow", "nwarrow", "nwnear", "nwnear", 7671 "oS", "oS", "oacute", "oacute", "oast", "oast", "ocir", "ocir", "ocirc", "ocirc", "ocy", "ocy", "odash", "odash", "odblac", "odblac", "odiv", "odiv", "odot", "odot", "odsold", "odsold", "oelig", "oelig", "ofcir", "ofcir", "ofr", "ofr", "ogon", "ogon", "ograve", "ograve", "ogt", "ogt", "ohbar", "ohbar", "ohm", "ohm", "oint", 7672 "oint", "olarr", "olarr", "olcir", "olcir", "olcross", "olcross", "oline", "oline", "olt", "olt", "omacr", "omacr", "omega", "omega", "omicron", "omicron", "omid", "omid", "ominus", "ominus", "oopf", "oopf", "opar", "opar", "operp", "operp", "oplus", "oplus", "or", "or", "orarr", "orarr", "ord", "ord", "order", "order", 7673 "orderof", "orderof", "ordf", "ordf", "ordm", "ordm", "origof", "origof", "oror", "oror", "orslope", "orslope", "orv", "orv", "oscr", "oscr", "oslash", "oslash", "osol", "osol", "otilde", "otilde", "otimes", "otimes", "otimesas", "otimesas", "ouml", "ouml", "ovbar", "ovbar", "par", "par", "para", "para", "parallel", "parallel", 7674 "parsim", "parsim", "parsl", "parsl", "part", "part", "pcy", "pcy", "percnt", "percnt", "period", "period", "permil", "permil", "perp", "perp", "pertenk", "pertenk", "pfr", "pfr", "phi", "phi", "phiv", "phiv", "phmmat", "phmmat", "phone", "phone", "pi", "pi", "pitchfork", "pitchfork", "piv", "piv", "planck", "planck", 7675 "planckh", "planckh", "plankv", "plankv", "plus", "plus", "plusacir", "plusacir", "plusb", "plusb", "pluscir", "pluscir", "plusdo", "plusdo", "plusdu", "plusdu", "pluse", "pluse", "plusmn", "plusmn", "plussim", "plussim", "plustwo", "plustwo", "pm", "pm", "pointint", "pointint", "popf", "popf", "pound", "pound", "pr", 7676 "pr", "prE", "prE", "prap", "prap", "prcue", "prcue", "pre", "pre", "prec", "prec", "precapprox", "precapprox", "preccurlyeq", "preccurlyeq", "preceq", "preceq", "precnapprox", "precnapprox", "precneqq", "precneqq", "precnsim", "precnsim", "precsim", "precsim", "prime", "prime", "primes", "primes", "prnE", "prnE", "prnap", 7677 "prnap", "prnsim", "prnsim", "prod", "prod", "profalar", "profalar", "profline", "profline", "profsurf", "profsurf", "prop", "prop", "propto", "propto", "prsim", "prsim", "prurel", "prurel", "pscr", "pscr", "psi", "psi", "puncsp", "puncsp", "qfr", "qfr", "qint", "qint", "qopf", "qopf", "qprime", "qprime", "qscr", "qscr", 7678 "quaternions", "quaternions", "quatint", "quatint", "quest", "quest", "questeq", "questeq", "rAarr", "rAarr", "rArr", "rArr", "rAtail", "rAtail", "rBarr", "rBarr", "rHar", "rHar", "racute", "racute", "radic", "radic", "raemptyv", "raemptyv", "rang", "rang", "rangd", "rangd", "range", "range", "rangle", "rangle", "raquo", 7679 "raquo", "rarr", "rarr", "rarrap", "rarrap", "rarrb", "rarrb", "rarrbfs", "rarrbfs", "rarrc", "rarrc", "rarrfs", "rarrfs", "rarrhk", "rarrhk", "rarrlp", "rarrlp", "rarrpl", "rarrpl", "rarrsim", "rarrsim", "rarrtl", "rarrtl", "rarrw", "rarrw", "ratail", "ratail", "ratio", "ratio", "rationals", "rationals", "rbarr", "rbarr", 7680 "rbbrk", "rbbrk", "rbrace", "rbrace", "rbrack", "rbrack", "rbrke", "rbrke", "rbrksld", "rbrksld", "rbrkslu", "rbrkslu", "rcaron", "rcaron", "rcedil", "rcedil", "rceil", "rceil", "rcub", "rcub", "rcy", "rcy", "rdca", "rdca", "rdldhar", "rdldhar", "rdquo", "rdquo", "rdquor", "rdquor", "rdsh", "rdsh", "real", "real", "realine", 7681 "realine", "realpart", "realpart", "reals", "reals", "rect", "rect", "reg", "reg", "rfisht", "rfisht", "rfloor", "rfloor", "rfr", "rfr", "rhard", "rhard", "rharu", "rharu", "rharul", "rharul", "rho", "rho", "rhov", "rhov", "rightarrow", "rightarrow", "rightarrowtail", "rightarrowtail", "rightharpoondown", "rightharpoondown", 7682 "rightharpoonup", "rightharpoonup", "rightleftarrows", "rightleftarrows", "rightleftharpoons", "rightleftharpoons", "rightrightarrows", "rightrightarrows", "rightsquigarrow", "rightsquigarrow", "rightthreetimes", "rightthreetimes", "ring", "ring", "risingdotseq", "risingdotseq", "rlarr", "rlarr", "rlhar", "rlhar", "rlm", 7683 "rlm", "rmoust", "rmoust", "rmoustache", "rmoustache", "rnmid", "rnmid", "roang", "roang", "roarr", "roarr", "robrk", "robrk", "ropar", "ropar", "ropf", "ropf", "roplus", "roplus", "rotimes", "rotimes", "rpar", "rpar", "rpargt", "rpargt", "rppolint", "rppolint", "rrarr", "rrarr", "rsaquo", "rsaquo", "rscr", "rscr", "rsh", 7684 "rsh", "rsqb", "rsqb", "rsquo", "rsquo", "rsquor", "rsquor", "rthree", "rthree", "rtimes", "rtimes", "rtri", "rtri", "rtrie", "rtrie", "rtrif", "rtrif", "rtriltri", "rtriltri", "ruluhar", "ruluhar", "rx", "rx", "sacute", "sacute", "sbquo", "sbquo", "sc", "sc", "scE", "scE", "scap", "scap", "scaron", "scaron", "sccue", 7685 "sccue", "sce", "sce", "scedil", "scedil", "scirc", "scirc", "scnE", "scnE", "scnap", "scnap", "scnsim", "scnsim", "scpolint", "scpolint", "scsim", "scsim", "scy", "scy", "sdot", "sdot", "sdotb", "sdotb", "sdote", "sdote", "seArr", "seArr", "searhk", "searhk", "searr", "searr", "searrow", "searrow", "sect", "sect", "semi", 7686 "semi", "seswar", "seswar", "setminus", "setminus", "setmn", "setmn", "sext", "sext", "sfr", "sfr", "sfrown", "sfrown", "sharp", "sharp", "shchcy", "shchcy", "shcy", "shcy", "shortmid", "shortmid", "shortparallel", "shortparallel", "shy", "shy", "sigma", "sigma", "sigmaf", "sigmaf", "sigmav", "sigmav", "sim", "sim", "simdot", 7687 "simdot", "sime", "sime", "simeq", "simeq", "simg", "simg", "simgE", "simgE", "siml", "siml", "simlE", "simlE", "simne", "simne", "simplus", "simplus", "simrarr", "simrarr", "slarr", "slarr", "smallsetminus", "smallsetminus", "smashp", "smashp", "smeparsl", "smeparsl", "smid", "smid", "smile", "smile", "smt", "smt", "smte", 7688 "smte", "softcy", "softcy", "sol", "sol", "solb", "solb", "solbar", "solbar", "sopf", "sopf", "spades", "spades", "spadesuit", "spadesuit", "spar", "spar", "sqcap", "sqcap", "sqcup", "sqcup", "sqsub", "sqsub", "sqsube", "sqsube", "sqsubset", "sqsubset", "sqsubseteq", "sqsubseteq", "sqsup", "sqsup", "sqsupe", "sqsupe", 7689 "sqsupset", "sqsupset", "sqsupseteq", "sqsupseteq", "squ", "squ", "square", "square", "squarf", "squarf", "squf", "squf", "srarr", "srarr", "sscr", "sscr", "ssetmn", "ssetmn", "ssmile", "ssmile", "sstarf", "sstarf", "star", "star", "starf", "starf", "straightepsilon", "straightepsilon", "straightphi", "straightphi", "strns", 7690 "strns", "sub", "sub", "subE", "subE", "subdot", "subdot", "sube", "sube", "subedot", "subedot", "submult", "submult", "subnE", "subnE", "subne", "subne", "subplus", "subplus", "subrarr", "subrarr", "subset", "subset", "subseteq", "subseteq", "subseteqq", "subseteqq", "subsetneq", "subsetneq", "subsetneqq", "subsetneqq", 7691 "subsim", "subsim", "subsub", "subsub", "subsup", "subsup", "succ", "succ", "succapprox", "succapprox", "succcurlyeq", "succcurlyeq", "succeq", "succeq", "succnapprox", "succnapprox", "succneqq", "succneqq", "succnsim", "succnsim", "succsim", "succsim", "sum", "sum", "sung", "sung", "sup", "sup", "sup1", "sup1", "sup2", 7692 "sup2", "sup3", "sup3", "supE", "supE", "supdot", "supdot", "supdsub", "supdsub", "supe", "supe", "supedot", "supedot", "suphsol", "suphsol", "suphsub", "suphsub", "suplarr", "suplarr", "supmult", "supmult", "supnE", "supnE", "supne", "supne", "supplus", "supplus", "supset", "supset", "supseteq", "supseteq", "supseteqq", 7693 "supseteqq", "supsetneq", "supsetneq", "supsetneqq", "supsetneqq", "supsim", "supsim", "supsub", "supsub", "supsup", "supsup", "swArr", "swArr", "swarhk", "swarhk", "swarr", "swarr", "swarrow", "swarrow", "swnwar", "swnwar", "szlig", "szlig", "target", "target", "tau", "tau", "tbrk", "tbrk", "tcaron", "tcaron", "tcedil", 7694 "tcedil", "tcy", "tcy", "tdot", "tdot", "telrec", "telrec", "tfr", "tfr", "there4", "there4", "therefore", "therefore", "theta", "theta", "thetasym", "thetasym", "thetav", "thetav", "thickapprox", "thickapprox", "thicksim", "thicksim", "thinsp", "thinsp", "thkap", "thkap", "thksim", "thksim", "thorn", "thorn", "tilde", 7695 "tilde", "times", "times", "timesb", "timesb", "timesbar", "timesbar", "timesd", "timesd", "tint", "tint", "toea", "toea", "top", "top", "topbot", "topbot", "topcir", "topcir", "topf", "topf", "topfork", "topfork", "tosa", "tosa", "tprime", "tprime", "trade", "trade", "triangle", "triangle", "triangledown", "triangledown", 7696 "triangleleft", "triangleleft", "trianglelefteq", "trianglelefteq", "triangleq", "triangleq", "triangleright", "triangleright", "trianglerighteq", "trianglerighteq", "tridot", "tridot", "trie", "trie", "triminus", "triminus", "triplus", "triplus", "trisb", "trisb", "tritime", "tritime", "trpezium", "trpezium", "tscr", 7697 "tscr", "tscy", "tscy", "tshcy", "tshcy", "tstrok", "tstrok", "twixt", "twixt", "twoheadleftarrow", "twoheadleftarrow", "twoheadrightarrow", "twoheadrightarrow", "uArr", "uArr", "uHar", "uHar", "uacute", "uacute", "uarr", "uarr", "ubrcy", "ubrcy", "ubreve", "ubreve", "ucirc", "ucirc", "ucy", "ucy", "udarr", "udarr", "udblac", 7698 "udblac", "udhar", "udhar", "ufisht", "ufisht", "ufr", "ufr", "ugrave", "ugrave", "uharl", "uharl", "uharr", "uharr", "uhblk", "uhblk", "ulcorn", "ulcorn", "ulcorner", "ulcorner", "ulcrop", "ulcrop", "ultri", "ultri", "umacr", "umacr", "uml", "uml", "uogon", "uogon", "uopf", "uopf", "uparrow", "uparrow", "updownarrow", 7699 "updownarrow", "upharpoonleft", "upharpoonleft", "upharpoonright", "upharpoonright", "uplus", "uplus", "upsi", "upsi", "upsih", "upsih", "upsilon", "upsilon", "upuparrows", "upuparrows", "urcorn", "urcorn", "urcorner", "urcorner", "urcrop", "urcrop", "uring", "uring", "urtri", "urtri", "uscr", "uscr", "utdot", "utdot", 7700 "utilde", "utilde", "utri", "utri", "utrif", "utrif", "uuarr", "uuarr", "uuml", "uuml", "uwangle", "uwangle", "vArr", "vArr", "vBar", "vBar", "vBarv", "vBarv", "vDash", "vDash", "vangrt", "vangrt", "varepsilon", "varepsilon", "varkappa", "varkappa", "varnothing", "varnothing", "varphi", "varphi", "varpi", "varpi", "varpropto", 7701 "varpropto", "varr", "varr", "varrho", "varrho", "varsigma", "varsigma", "vartheta", "vartheta", "vartriangleleft", "vartriangleleft", "vartriangleright", "vartriangleright", "vcy", "vcy", "vdash", "vdash", "vee", "vee", "veebar", "veebar", "veeeq", "veeeq", "vellip", "vellip", "verbar", "verbar", "vert", "vert", "vfr", 7702 "vfr", "vltri", "vltri", "vopf", "vopf", "vprop", "vprop", "vrtri", "vrtri", "vscr", "vscr", "vzigzag", "vzigzag", "wcirc", "wcirc", "wedbar", "wedbar", "wedge", "wedge", "wedgeq", "wedgeq", "weierp", "weierp", "wfr", "wfr", "wopf", "wopf", "wp", "wp", "wr", "wr", "wreath", "wreath", "wscr", "wscr", "xcap", "xcap", "xcirc", 7703 "xcirc", "xcup", "xcup", "xdtri", "xdtri", "xfr", "xfr", "xhArr", "xhArr", "xharr", "xharr", "xi", "xi", "xlArr", "xlArr", "xlarr", "xlarr", "xmap", "xmap", "xnis", "xnis", "xodot", "xodot", "xopf", "xopf", "xoplus", "xoplus", "xotime", "xotime", "xrArr", "xrArr", "xrarr", "xrarr", "xscr", "xscr", "xsqcup", "xsqcup", "xuplus", 7704 "xuplus", "xutri", "xutri", "xvee", "xvee", "xwedge", "xwedge", "yacute", "yacute", "yacy", "yacy", "ycirc", "ycirc", "ycy", "ycy", "yen", "yen", "yfr", "yfr", "yicy", "yicy", "yopf", "yopf", "yscr", "yscr", "yucy", "yucy", "yuml", "yuml", "zacute", "zacute", "zcaron", "zcaron", "zcy", "zcy", "zdot", "zdot", "zeetrf", 7705 "zeetrf", "zeta", "zeta", "zfr", "zfr", "zhcy", "zhcy", "zigrarr", "zigrarr", "zopf", "zopf", "zscr", "zscr", "zwj", "zwj", "zwnj", "zwnj", ]; 7706 7707 immutable dchar[] availableEntitiesValues = 7708 ['\u00c6', '\u00c6', '\u0026', '\u0026', '\u00c1', '\u00c1', '\u0102', '\u0102', '\u00c2', '\u00c2', '\u0410', '\u0410', '\U0001d504', '\U0001d504', '\u00c0', '\u00c0', '\u0391', '\u0391', '\u0100', '\u0100', '\u2a53', '\u2a53', '\u0104', '\u0104', '\U0001d538', '\U0001d538', '\u2061', '\u2061', '\u00c5', '\u00c5', '\U0001d49c', '\U0001d49c', '\u2254', '\u2254', '\u00c3', 7709 '\u00c3', '\u00c4', '\u00c4', '\u2216', '\u2216', '\u2ae7', '\u2ae7', '\u2306', '\u2306', '\u0411', '\u0411', '\u2235', '\u2235', '\u212c', '\u212c', '\u0392', '\u0392', '\U0001d505', '\U0001d505', '\U0001d539', '\U0001d539', '\u02d8', '\u02d8', '\u212c', '\u212c', '\u224e', '\u224e', '\u0427', '\u0427', '\u00a9', '\u00a9', '\u0106', '\u0106', '\u22d2', '\u22d2', '\u2145', 7710 '\u2145', '\u212d', '\u212d', '\u010c', '\u010c', '\u00c7', '\u00c7', '\u0108', '\u0108', '\u2230', '\u2230', '\u010a', '\u010a', '\u00b8', '\u00b8', '\u00b7', '\u00b7', '\u212d', '\u212d', '\u03a7', '\u03a7', '\u2299', '\u2299', '\u2296', '\u2296', '\u2295', '\u2295', '\u2297', '\u2297', 7711 '\u2232', '\u2232', '\u201d', '\u201d', '\u2019', '\u2019', '\u2237', '\u2237', '\u2a74', '\u2a74', '\u2261', '\u2261', '\u222f', '\u222f', '\u222e', '\u222e', '\u2102', '\u2102', '\u2210', '\u2210', '\u2233', 7712 '\u2233', '\u2a2f', '\u2a2f', '\U0001d49e', '\U0001d49e', '\u22d3', '\u22d3', '\u224d', '\u224d', '\u2145', '\u2145', '\u2911', '\u2911', '\u0402', '\u0402', '\u0405', '\u0405', '\u040f', '\u040f', '\u2021', '\u2021', '\u21a1', '\u21a1', '\u2ae4', '\u2ae4', '\u010e', '\u010e', '\u0414', '\u0414', '\u2207', '\u2207', '\u0394', '\u0394', '\U0001d507', '\U0001d507', 7713 '\u00b4', '\u00b4', '\u02d9', '\u02d9', '\u02dd', '\u02dd', '\u0060', '\u0060', '\u02dc', '\u02dc', '\u22c4', '\u22c4', '\u2146', '\u2146', '\U0001d53b', '\U0001d53b', '\u00a8', '\u00a8', '\u20dc', '\u20dc', '\u2250', 7714 '\u2250', '\u222f', '\u222f', '\u00a8', '\u00a8', '\u21d3', '\u21d3', '\u21d0', '\u21d0', '\u21d4', '\u21d4', '\u2ae4', '\u2ae4', '\u27f8', '\u27f8', '\u27fa', 7715 '\u27fa', '\u27f9', '\u27f9', '\u21d2', '\u21d2', '\u22a8', '\u22a8', '\u21d1', '\u21d1', '\u21d5', '\u21d5', '\u2225', '\u2225', '\u2193', '\u2193', '\u2913', '\u2913', 7716 '\u21f5', '\u21f5', '\u0311', '\u0311', '\u2950', '\u2950', '\u295e', '\u295e', '\u21bd', '\u21bd', '\u2956', '\u2956', '\u295f', '\u295f', '\u21c1', '\u21c1', '\u2957', 7717 '\u2957', '\u22a4', '\u22a4', '\u21a7', '\u21a7', '\u21d3', '\u21d3', '\U0001d49f', '\U0001d49f', '\u0110', '\u0110', '\u014a', '\u014a', '\u00d0', '\u00d0', '\u00c9', '\u00c9', '\u011a', '\u011a', '\u00ca', '\u00ca', '\u042d', '\u042d', '\u0116', '\u0116', '\U0001d508', '\U0001d508', '\u00c8', '\u00c8', '\u2208', '\u2208', '\u0112', '\u0112', 7718 '\u25fb', '\u25fb', '\u25ab', '\u25ab', '\u0118', '\u0118', '\U0001d53c', '\U0001d53c', '\u0395', '\u0395', '\u2a75', '\u2a75', '\u2242', '\u2242', '\u21cc', '\u21cc', '\u2130', '\u2130', '\u2a73', '\u2a73', '\u0397', '\u0397', '\u00cb', '\u00cb', '\u2203', '\u2203', '\u2147', '\u2147', 7719 '\u0424', '\u0424', '\U0001d509', '\U0001d509', '\u25fc', '\u25fc', '\u25aa', '\u25aa', '\U0001d53d', '\U0001d53d', '\u2200', '\u2200', '\u2131', '\u2131', '\u2131', '\u2131', '\u0403', '\u0403', '\u003e', '\u003e', '\u0393', '\u0393', '\u03dc', '\u03dc', '\u011e', '\u011e', '\u0122', '\u0122', '\u011c', '\u011c', 7720 '\u0413', '\u0413', '\u0120', '\u0120', '\U0001d50a', '\U0001d50a', '\u22d9', '\u22d9', '\U0001d53e', '\U0001d53e', '\u2265', '\u2265', '\u22db', '\u22db', '\u2267', '\u2267', '\u2aa2', '\u2aa2', '\u2277', '\u2277', '\u2a7e', '\u2a7e', '\u2273', '\u2273', 7721 '\U0001d4a2', '\U0001d4a2', '\u226b', '\u226b', '\u042a', '\u042a', '\u02c7', '\u02c7', '\u005e', '\u005e', '\u0124', '\u0124', '\u210c', '\u210c', '\u210b', '\u210b', '\u210d', '\u210d', '\u2500', '\u2500', '\u210b', '\u210b', '\u0126', '\u0126', '\u224e', '\u224e', '\u224f', '\u224f', '\u0415', '\u0415', '\u0132', '\u0132', 7722 '\u0401', '\u0401', '\u00cd', '\u00cd', '\u00ce', '\u00ce', '\u0418', '\u0418', '\u0130', '\u0130', '\u2111', '\u2111', '\u00cc', '\u00cc', '\u2111', '\u2111', '\u012a', '\u012a', '\u2148', '\u2148', '\u21d2', '\u21d2', '\u222c', '\u222c', '\u222b', '\u222b', '\u22c2', '\u22c2', '\u2063', '\u2063', '\u2062', 7723 '\u2062', '\u012e', '\u012e', '\U0001d540', '\U0001d540', '\u0399', '\u0399', '\u2110', '\u2110', '\u0128', '\u0128', '\u0406', '\u0406', '\u00cf', '\u00cf', '\u0134', '\u0134', '\u0419', '\u0419', '\U0001d50d', '\U0001d50d', '\U0001d541', '\U0001d541', '\U0001d4a5', '\U0001d4a5', '\u0408', '\u0408', '\u0404', '\u0404', '\u0425', '\u0425', '\u040c', '\u040c', '\u039a', '\u039a', '\u0136', '\u0136', 7724 '\u041a', '\u041a', '\U0001d50e', '\U0001d50e', '\U0001d542', '\U0001d542', '\U0001d4a6', '\U0001d4a6', '\u0409', '\u0409', '\u003c', '\u003c', '\u0139', '\u0139', '\u039b', '\u039b', '\u27ea', '\u27ea', '\u2112', '\u2112', '\u219e', '\u219e', '\u013d', '\u013d', '\u013b', '\u013b', '\u041b', '\u041b', '\u27e8', '\u27e8', '\u2190', '\u2190', '\u21e4', 7725 '\u21e4', '\u21c6', '\u21c6', '\u2308', '\u2308', '\u27e6', '\u27e6', '\u2961', '\u2961', '\u21c3', '\u21c3', '\u2959', '\u2959', '\u230a', '\u230a', '\u2194', '\u2194', '\u294e', 7726 '\u294e', '\u22a3', '\u22a3', '\u21a4', '\u21a4', '\u295a', '\u295a', '\u22b2', '\u22b2', '\u29cf', '\u29cf', '\u22b4', '\u22b4', '\u2951', '\u2951', '\u2960', '\u2960', '\u21bf', '\u21bf', 7727 '\u2958', '\u2958', '\u21bc', '\u21bc', '\u2952', '\u2952', '\u21d0', '\u21d0', '\u21d4', '\u21d4', '\u22da', '\u22da', '\u2266', '\u2266', '\u2276', '\u2276', '\u2aa1', '\u2aa1', '\u2a7d', '\u2a7d', 7728 '\u2272', '\u2272', '\U0001d50f', '\U0001d50f', '\u22d8', '\u22d8', '\u21da', '\u21da', '\u013f', '\u013f', '\u27f5', '\u27f5', '\u27f7', '\u27f7', '\u27f6', '\u27f6', '\u27f8', '\u27f8', '\u27fa', '\u27fa', '\u27f9', '\u27f9', 7729 '\U0001d543', '\U0001d543', '\u2199', '\u2199', '\u2198', '\u2198', '\u2112', '\u2112', '\u21b0', '\u21b0', '\u0141', '\u0141', '\u226a', '\u226a', '\u2905', '\u2905', '\u041c', '\u041c', '\u205f', '\u205f', '\u2133', '\u2133', '\U0001d510', '\U0001d510', '\u2213', '\u2213', '\U0001d544', '\U0001d544', '\u2133', '\u2133', '\u039c', '\u039c', 7730 '\u040a', '\u040a', '\u0143', '\u0143', '\u0147', '\u0147', '\u0145', '\u0145', '\u041d', '\u041d', '\u200b', '\u200b', '\u200b', '\u200b', '\u200b', '\u200b', '\u200b', '\u200b', '\u226b', '\u226b', 7731 '\u226a', '\u226a', '\u000a', '\u000a', '\U0001d511', '\U0001d511', '\u2060', '\u2060', '\u00a0', '\u00a0', '\u2115', '\u2115', '\u2aec', '\u2aec', '\u2262', '\u2262', '\u226d', '\u226d', '\u2226', '\u2226', '\u2209', '\u2209', '\u2260', '\u2260', 7732 '\u2204', '\u2204', '\u226f', '\u226f', '\u2271', '\u2271', '\u2279', '\u2279', '\u2275', '\u2275', '\u22ea', '\u22ea', '\u22ec', '\u22ec', '\u226e', '\u226e', '\u2270', '\u2270', '\u2278', 7733 '\u2278', '\u2274', '\u2274', '\u2280', '\u2280', '\u22e0', '\u22e0', '\u220c', '\u220c', '\u22eb', '\u22eb', '\u22ed', '\u22ed', '\u22e2', '\u22e2', '\u22e3', 7734 '\u22e3', '\u2288', '\u2288', '\u2281', '\u2281', '\u22e1', '\u22e1', '\u2289', '\u2289', '\u2241', '\u2241', '\u2244', '\u2244', '\u2247', '\u2247', '\u2249', '\u2249', '\u2224', 7735 '\u2224', '\U0001d4a9', '\U0001d4a9', '\u00d1', '\u00d1', '\u039d', '\u039d', '\u0152', '\u0152', '\u00d3', '\u00d3', '\u00d4', '\u00d4', '\u041e', '\u041e', '\u0150', '\u0150', '\U0001d512', '\U0001d512', '\u00d2', '\u00d2', '\u014c', '\u014c', '\u03a9', '\u03a9', '\u039f', '\u039f', '\U0001d546', '\U0001d546', '\u201c', '\u201c', '\u2018', 7736 '\u2018', '\u2a54', '\u2a54', '\U0001d4aa', '\U0001d4aa', '\u00d8', '\u00d8', '\u00d5', '\u00d5', '\u2a37', '\u2a37', '\u00d6', '\u00d6', '\u203e', '\u203e', '\u23de', '\u23de', '\u23b4', '\u23b4', '\u23dc', '\u23dc', '\u2202', '\u2202', '\u041f', '\u041f', '\U0001d513', '\U0001d513', '\u03a6', '\u03a6', '\u03a0', '\u03a0', '\u00b1', 7737 '\u00b1', '\u210c', '\u210c', '\u2119', '\u2119', '\u2abb', '\u2abb', '\u227a', '\u227a', '\u2aaf', '\u2aaf', '\u227c', '\u227c', '\u227e', '\u227e', '\u2033', '\u2033', '\u220f', '\u220f', '\u2237', '\u2237', '\u221d', '\u221d', '\U0001d4ab', '\U0001d4ab', 7738 '\u03a8', '\u03a8', '\u0022', '\u0022', '\U0001d514', '\U0001d514', '\u211a', '\u211a', '\U0001d4ac', '\U0001d4ac', '\u2910', '\u2910', '\u00ae', '\u00ae', '\u0154', '\u0154', '\u27eb', '\u27eb', '\u21a0', '\u21a0', '\u2916', '\u2916', '\u0158', '\u0158', '\u0156', '\u0156', '\u0420', '\u0420', '\u211c', '\u211c', '\u220b', '\u220b', '\u21cb', '\u21cb', 7739 '\u296f', '\u296f', '\u211c', '\u211c', '\u03a1', '\u03a1', '\u27e9', '\u27e9', '\u2192', '\u2192', '\u21e5', '\u21e5', '\u21c4', '\u21c4', '\u2309', '\u2309', '\u27e7', '\u27e7', '\u295d', 7740 '\u295d', '\u21c2', '\u21c2', '\u2955', '\u2955', '\u230b', '\u230b', '\u22a2', '\u22a2', '\u21a6', '\u21a6', '\u295b', '\u295b', '\u22b3', '\u22b3', '\u29d0', '\u29d0', '\u22b5', 7741 '\u22b5', '\u294f', '\u294f', '\u295c', '\u295c', '\u21be', '\u21be', '\u2954', '\u2954', '\u21c0', '\u21c0', '\u2953', '\u2953', '\u21d2', '\u21d2', '\u211d', '\u211d', '\u2970', '\u2970', 7742 '\u21db', '\u21db', '\u211b', '\u211b', '\u21b1', '\u21b1', '\u29f4', '\u29f4', '\u0429', '\u0429', '\u0428', '\u0428', '\u042c', '\u042c', '\u015a', '\u015a', '\u2abc', '\u2abc', '\u0160', '\u0160', '\u015e', '\u015e', '\u015c', '\u015c', '\u0421', '\u0421', '\U0001d516', '\U0001d516', '\u2193', '\u2193', '\u2190', '\u2190', 7743 '\u2192', '\u2192', '\u2191', '\u2191', '\u03a3', '\u03a3', '\u2218', '\u2218', '\U0001d54a', '\U0001d54a', '\u221a', '\u221a', '\u25a1', '\u25a1', '\u2293', '\u2293', '\u228f', '\u228f', '\u2291', '\u2291', '\u2290', '\u2290', 7744 '\u2292', '\u2292', '\u2294', '\u2294', '\U0001d4ae', '\U0001d4ae', '\u22c6', '\u22c6', '\u22d0', '\u22d0', '\u22d0', '\u22d0', '\u2286', '\u2286', '\u227b', '\u227b', '\u2ab0', '\u2ab0', '\u227d', '\u227d', '\u227f', '\u227f', '\u220b', 7745 '\u220b', '\u2211', '\u2211', '\u22d1', '\u22d1', '\u2283', '\u2283', '\u2287', '\u2287', '\u22d1', '\u22d1', '\u00de', '\u00de', '\u2122', '\u2122', '\u040b', '\u040b', '\u0426', '\u0426', '\u0009', '\u0009', '\u03a4', '\u03a4', '\u0164', '\u0164', '\u0162', '\u0162', '\u0422', '\u0422', '\U0001d517', '\U0001d517', '\u2234', '\u2234', '\u0398', '\u0398', 7746 '\u2009', '\u2009', '\u223c', '\u223c', '\u2243', '\u2243', '\u2245', '\u2245', '\u2248', '\u2248', '\U0001d54b', '\U0001d54b', '\u20db', '\u20db', '\U0001d4af', '\U0001d4af', '\u0166', '\u0166', '\u00da', '\u00da', '\u219f', '\u219f', '\u2949', '\u2949', '\u040e', '\u040e', '\u016c', '\u016c', '\u00db', 7747 '\u00db', '\u0423', '\u0423', '\u0170', '\u0170', '\U0001d518', '\U0001d518', '\u00d9', '\u00d9', '\u016a', '\u016a', '\u005f', '\u005f', '\u23df', '\u23df', '\u23b5', '\u23b5', '\u23dd', '\u23dd', '\u22c3', '\u22c3', '\u228e', '\u228e', '\u0172', '\u0172', '\U0001d54c', '\U0001d54c', '\u2191', '\u2191', '\u2912', 7748 '\u2912', '\u21c5', '\u21c5', '\u2195', '\u2195', '\u296e', '\u296e', '\u22a5', '\u22a5', '\u21a5', '\u21a5', '\u21d1', '\u21d1', '\u21d5', '\u21d5', '\u2196', '\u2196', '\u2197', '\u2197', '\u03d2', '\u03d2', '\u03a5', '\u03a5', 7749 '\u016e', '\u016e', '\U0001d4b0', '\U0001d4b0', '\u0168', '\u0168', '\u00dc', '\u00dc', '\u22ab', '\u22ab', '\u2aeb', '\u2aeb', '\u0412', '\u0412', '\u22a9', '\u22a9', '\u2ae6', '\u2ae6', '\u22c1', '\u22c1', '\u2016', '\u2016', '\u2016', '\u2016', '\u2223', '\u2223', '\u007c', '\u007c', '\u2758', '\u2758', '\u2240', 7750 '\u2240', '\u200a', '\u200a', '\U0001d519', '\U0001d519', '\U0001d54d', '\U0001d54d', '\U0001d4b1', '\U0001d4b1', '\u22aa', '\u22aa', '\u0174', '\u0174', '\u22c0', '\u22c0', '\U0001d51a', '\U0001d51a', '\U0001d54e', '\U0001d54e', '\U0001d4b2', '\U0001d4b2', '\U0001d51b', '\U0001d51b', '\u039e', '\u039e', '\U0001d54f', '\U0001d54f', '\U0001d4b3', '\U0001d4b3', '\u042f', '\u042f', '\u0407', '\u0407', '\u042e', '\u042e', '\u00dd', '\u00dd', 7751 '\u0176', '\u0176', '\u042b', '\u042b', '\U0001d51c', '\U0001d51c', '\U0001d550', '\U0001d550', '\U0001d4b4', '\U0001d4b4', '\u0178', '\u0178', '\u0416', '\u0416', '\u0179', '\u0179', '\u017d', '\u017d', '\u0417', '\u0417', '\u017b', '\u017b', '\u200b', '\u200b', '\u0396', '\u0396', '\u2128', '\u2128', '\u2124', '\u2124', '\U0001d4b5', '\U0001d4b5', '\u00e1', '\u00e1', '\u0103', '\u0103', '\u223e', 7752 '\u223e', '\u223f', '\u223f', '\u00e2', '\u00e2', '\u00b4', '\u00b4', '\u0430', '\u0430', '\u00e6', '\u00e6', '\u2061', '\u2061', '\U0001d51e', '\U0001d51e', '\u00e0', '\u00e0', '\u2135', '\u2135', '\u2135', '\u2135', '\u03b1', '\u03b1', '\u0101', '\u0101', '\u2a3f', '\u2a3f', '\u2227', '\u2227', '\u2a55', '\u2a55', '\u2a5c', '\u2a5c', '\u2a58', '\u2a58', '\u2a5a', '\u2a5a', '\u2220', 7753 '\u2220', '\u29a4', '\u29a4', '\u2220', '\u2220', '\u2221', '\u2221', '\u29a8', '\u29a8', '\u29a9', '\u29a9', '\u29aa', '\u29aa', '\u29ab', '\u29ab', '\u29ac', '\u29ac', '\u29ad', '\u29ad', '\u29ae', '\u29ae', '\u29af', '\u29af', '\u221f', '\u221f', '\u22be', '\u22be', '\u299d', '\u299d', '\u2222', 7754 '\u2222', '\u00c5', '\u00c5', '\u237c', '\u237c', '\u0105', '\u0105', '\U0001d552', '\U0001d552', '\u2248', '\u2248', '\u2a70', '\u2a70', '\u2a6f', '\u2a6f', '\u224a', '\u224a', '\u224b', '\u224b', '\u2248', '\u2248', '\u224a', '\u224a', '\u00e5', '\u00e5', '\U0001d4b6', '\U0001d4b6', '\u002a', '\u002a', '\u2248', '\u2248', '\u224d', '\u224d', '\u00e3', '\u00e3', '\u00e4', 7755 '\u00e4', '\u2233', '\u2233', '\u2a11', '\u2a11', '\u2aed', '\u2aed', '\u224c', '\u224c', '\u03f6', '\u03f6', '\u2035', '\u2035', '\u223d', '\u223d', '\u22cd', '\u22cd', '\u22bd', '\u22bd', '\u2305', '\u2305', '\u2305', '\u2305', '\u23b5', '\u23b5', '\u23b6', '\u23b6', '\u224c', '\u224c', '\u0431', 7756 '\u0431', '\u201e', '\u201e', '\u2235', '\u2235', '\u2235', '\u2235', '\u29b0', '\u29b0', '\u03f6', '\u03f6', '\u212c', '\u212c', '\u03b2', '\u03b2', '\u2136', '\u2136', '\u226c', '\u226c', '\U0001d51f', '\U0001d51f', '\u22c2', '\u22c2', '\u25ef', '\u25ef', '\u22c3', '\u22c3', '\u2a00', '\u2a00', '\u2a01', '\u2a01', '\u2a02', '\u2a02', 7757 '\u2a06', '\u2a06', '\u2605', '\u2605', '\u25bd', '\u25bd', '\u25b3', '\u25b3', '\u2a04', '\u2a04', '\u22c1', '\u22c1', '\u22c0', '\u22c0', '\u290d', '\u290d', '\u29eb', '\u29eb', '\u25aa', '\u25aa', '\u25b4', '\u25b4', '\u25be', 7758 '\u25be', '\u25c2', '\u25c2', '\u25b8', '\u25b8', '\u2423', '\u2423', '\u2592', '\u2592', '\u2591', '\u2591', '\u2593', '\u2593', '\u2588', '\u2588', '\u2310', '\u2310', '\U0001d553', '\U0001d553', '\u22a5', '\u22a5', '\u22a5', '\u22a5', '\u22c8', '\u22c8', '\u2557', '\u2557', '\u2554', '\u2554', '\u2556', 7759 '\u2556', '\u2553', '\u2553', '\u2550', '\u2550', '\u2566', '\u2566', '\u2569', '\u2569', '\u2564', '\u2564', '\u2567', '\u2567', '\u255d', '\u255d', '\u255a', '\u255a', '\u255c', '\u255c', '\u2559', '\u2559', '\u2551', '\u2551', '\u256c', '\u256c', '\u2563', '\u2563', '\u2560', '\u2560', '\u256b', '\u256b', '\u2562', '\u2562', '\u255f', '\u255f', '\u29c9', 7760 '\u29c9', '\u2555', '\u2555', '\u2552', '\u2552', '\u2510', '\u2510', '\u250c', '\u250c', '\u2500', '\u2500', '\u2565', '\u2565', '\u2568', '\u2568', '\u252c', '\u252c', '\u2534', '\u2534', '\u229f', '\u229f', '\u229e', '\u229e', '\u22a0', '\u22a0', '\u255b', '\u255b', '\u2558', '\u2558', '\u2518', '\u2518', '\u2514', '\u2514', '\u2502', 7761 '\u2502', '\u256a', '\u256a', '\u2561', '\u2561', '\u255e', '\u255e', '\u253c', '\u253c', '\u2524', '\u2524', '\u251c', '\u251c', '\u2035', '\u2035', '\u02d8', '\u02d8', '\u00a6', '\u00a6', '\U0001d4b7', '\U0001d4b7', '\u204f', '\u204f', '\u223d', '\u223d', '\u22cd', '\u22cd', '\u005c', '\u005c', '\u29c5', '\u29c5', '\u27c8', '\u27c8', '\u2022', '\u2022', '\u2022', 7762 '\u2022', '\u224e', '\u224e', '\u2aae', '\u2aae', '\u224f', '\u224f', '\u224f', '\u224f', '\u0107', '\u0107', '\u2229', '\u2229', '\u2a44', '\u2a44', '\u2a49', '\u2a49', '\u2a4b', '\u2a4b', '\u2a47', '\u2a47', '\u2a40', '\u2a40', '\u2041', '\u2041', '\u02c7', '\u02c7', '\u2a4d', '\u2a4d', '\u010d', '\u010d', '\u00e7', '\u00e7', '\u0109', 7763 '\u0109', '\u2a4c', '\u2a4c', '\u2a50', '\u2a50', '\u010b', '\u010b', '\u00b8', '\u00b8', '\u29b2', '\u29b2', '\u00a2', '\u00a2', '\u00b7', '\u00b7', '\U0001d520', '\U0001d520', '\u0447', '\u0447', '\u2713', '\u2713', '\u2713', '\u2713', '\u03c7', '\u03c7', '\u25cb', '\u25cb', '\u29c3', '\u29c3', '\u02c6', '\u02c6', '\u2257', '\u2257', '\u21ba', 7764 '\u21ba', '\u21bb', '\u21bb', '\u00ae', '\u00ae', '\u24c8', '\u24c8', '\u229b', '\u229b', '\u229a', '\u229a', '\u229d', '\u229d', '\u2257', '\u2257', '\u2a10', '\u2a10', '\u2aef', '\u2aef', '\u29c2', '\u29c2', '\u2663', '\u2663', '\u2663', '\u2663', '\u003a', 7765 '\u003a', '\u2254', '\u2254', '\u2254', '\u2254', '\u002c', '\u002c', '\u0040', '\u0040', '\u2201', '\u2201', '\u2218', '\u2218', '\u2201', '\u2201', '\u2102', '\u2102', '\u2245', '\u2245', '\u2a6d', '\u2a6d', '\u222e', '\u222e', '\U0001d554', '\U0001d554', '\u2210', '\u2210', '\u00a9', '\u00a9', '\u2117', '\u2117', '\u21b5', '\u21b5', 7766 '\u2717', '\u2717', '\U0001d4b8', '\U0001d4b8', '\u2acf', '\u2acf', '\u2ad1', '\u2ad1', '\u2ad0', '\u2ad0', '\u2ad2', '\u2ad2', '\u22ef', '\u22ef', '\u2938', '\u2938', '\u2935', '\u2935', '\u22de', '\u22de', '\u22df', '\u22df', '\u21b6', '\u21b6', '\u293d', '\u293d', '\u222a', '\u222a', '\u2a48', '\u2a48', '\u2a46', '\u2a46', '\u2a4a', '\u2a4a', 7767 '\u228d', '\u228d', '\u2a45', '\u2a45', '\u21b7', '\u21b7', '\u293c', '\u293c', '\u22de', '\u22de', '\u22df', '\u22df', '\u22ce', '\u22ce', '\u22cf', '\u22cf', '\u00a4', '\u00a4', '\u21b6', '\u21b6', '\u21b7', '\u21b7', '\u22ce', '\u22ce', '\u22cf', '\u22cf', 7768 '\u2232', '\u2232', '\u2231', '\u2231', '\u232d', '\u232d', '\u21d3', '\u21d3', '\u2965', '\u2965', '\u2020', '\u2020', '\u2138', '\u2138', '\u2193', '\u2193', '\u2010', '\u2010', '\u22a3', '\u22a3', '\u290f', '\u290f', '\u02dd', '\u02dd', '\u010f', '\u010f', '\u0434', '\u0434', '\u2146', '\u2146', '\u2021', '\u2021', '\u21ca', '\u21ca', '\u2a77', 7769 '\u2a77', '\u00b0', '\u00b0', '\u03b4', '\u03b4', '\u29b1', '\u29b1', '\u297f', '\u297f', '\U0001d521', '\U0001d521', '\u21c3', '\u21c3', '\u21c2', '\u21c2', '\u22c4', '\u22c4', '\u22c4', '\u22c4', '\u2666', '\u2666', '\u2666', '\u2666', '\u00a8', '\u00a8', '\u03dd', '\u03dd', '\u22f2', '\u22f2', '\u00f7', '\u00f7', '\u00f7', '\u00f7', '\u22c7', 7770 '\u22c7', '\u22c7', '\u22c7', '\u0452', '\u0452', '\u231e', '\u231e', '\u230d', '\u230d', '\u0024', '\u0024', '\U0001d555', '\U0001d555', '\u02d9', '\u02d9', '\u2250', '\u2250', '\u2251', '\u2251', '\u2238', '\u2238', '\u2214', '\u2214', '\u22a1', '\u22a1', '\u2306', '\u2306', '\u2193', '\u2193', '\u21ca', 7771 '\u21ca', '\u21c3', '\u21c3', '\u21c2', '\u21c2', '\u2910', '\u2910', '\u231f', '\u231f', '\u230c', '\u230c', '\U0001d4b9', '\U0001d4b9', '\u0455', '\u0455', '\u29f6', '\u29f6', '\u0111', '\u0111', '\u22f1', '\u22f1', '\u25bf', '\u25bf', '\u25be', '\u25be', '\u21f5', '\u21f5', '\u296f', '\u296f', '\u29a6', 7772 '\u29a6', '\u045f', '\u045f', '\u27ff', '\u27ff', '\u2a77', '\u2a77', '\u2251', '\u2251', '\u00e9', '\u00e9', '\u2a6e', '\u2a6e', '\u011b', '\u011b', '\u2256', '\u2256', '\u00ea', '\u00ea', '\u2255', '\u2255', '\u044d', '\u044d', '\u0117', '\u0117', '\u2147', '\u2147', '\u2252', '\u2252', '\U0001d522', '\U0001d522', '\u2a9a', '\u2a9a', '\u00e8', '\u00e8', '\u2a96', '\u2a96', '\u2a98', 7773 '\u2a98', '\u2a99', '\u2a99', '\u23e7', '\u23e7', '\u2113', '\u2113', '\u2a95', '\u2a95', '\u2a97', '\u2a97', '\u0113', '\u0113', '\u2205', '\u2205', '\u2205', '\u2205', '\u2205', '\u2205', '\u2003', '\u2003', '\u2004', '\u2004', '\u2005', '\u2005', '\u014b', '\u014b', '\u2002', '\u2002', '\u0119', '\u0119', '\U0001d556', '\U0001d556', '\u22d5', '\u22d5', '\u29e3', 7774 '\u29e3', '\u2a71', '\u2a71', '\u03b5', '\u03b5', '\u03b5', '\u03b5', '\u03f5', '\u03f5', '\u2256', '\u2256', '\u2255', '\u2255', '\u2242', '\u2242', '\u2a96', '\u2a96', '\u2a95', '\u2a95', '\u003d', '\u003d', '\u225f', '\u225f', '\u2261', '\u2261', '\u2a78', '\u2a78', '\u29e5', '\u29e5', '\u2253', '\u2253', 7775 '\u2971', '\u2971', '\u212f', '\u212f', '\u2250', '\u2250', '\u2242', '\u2242', '\u03b7', '\u03b7', '\u00f0', '\u00f0', '\u00eb', '\u00eb', '\u20ac', '\u20ac', '\u0021', '\u0021', '\u2203', '\u2203', '\u2130', '\u2130', '\u2147', '\u2147', '\u2252', '\u2252', '\u0444', '\u0444', '\u2640', '\u2640', '\ufb03', '\ufb03', '\ufb00', 7776 '\ufb00', '\ufb04', '\ufb04', '\U0001d523', '\U0001d523', '\ufb01', '\ufb01', '\u266d', '\u266d', '\ufb02', '\ufb02', '\u25b1', '\u25b1', '\u0192', '\u0192', '\U0001d557', '\U0001d557', '\u2200', '\u2200', '\u22d4', '\u22d4', '\u2ad9', '\u2ad9', '\u2a0d', '\u2a0d', '\u00bd', '\u00bd', '\u2153', '\u2153', '\u00bc', '\u00bc', '\u2155', '\u2155', '\u2159', '\u2159', 7777 '\u215b', '\u215b', '\u2154', '\u2154', '\u2156', '\u2156', '\u00be', '\u00be', '\u2157', '\u2157', '\u215c', '\u215c', '\u2158', '\u2158', '\u215a', '\u215a', '\u215d', '\u215d', '\u215e', '\u215e', '\u2044', '\u2044', '\u2322', '\u2322', '\U0001d4bb', '\U0001d4bb', '\u2267', '\u2267', '\u2a8c', '\u2a8c', '\u01f5', '\u01f5', '\u03b3', '\u03b3', '\u03dd', 7778 '\u03dd', '\u2a86', '\u2a86', '\u011f', '\u011f', '\u011d', '\u011d', '\u0433', '\u0433', '\u0121', '\u0121', '\u2265', '\u2265', '\u22db', '\u22db', '\u2265', '\u2265', '\u2267', '\u2267', '\u2a7e', '\u2a7e', '\u2a7e', '\u2a7e', '\u2aa9', '\u2aa9', '\u2a80', '\u2a80', '\u2a82', '\u2a82', '\u2a84', '\u2a84', '\u2a94', '\u2a94', '\U0001d524', '\U0001d524', '\u226b', '\u226b', '\u22d9', 7779 '\u22d9', '\u2137', '\u2137', '\u0453', '\u0453', '\u2277', '\u2277', '\u2a92', '\u2a92', '\u2aa5', '\u2aa5', '\u2aa4', '\u2aa4', '\u2269', '\u2269', '\u2a8a', '\u2a8a', '\u2a8a', '\u2a8a', '\u2a88', '\u2a88', '\u2a88', '\u2a88', '\u2269', '\u2269', '\u22e7', '\u22e7', '\U0001d558', '\U0001d558', '\u0060', '\u0060', '\u210a', '\u210a', '\u2273', '\u2273', '\u2a8e', '\u2a8e', '\u2a90', '\u2a90', '\u2aa7', 7780 '\u2aa7', '\u2a7a', '\u2a7a', '\u22d7', '\u22d7', '\u2995', '\u2995', '\u2a7c', '\u2a7c', '\u2a86', '\u2a86', '\u2978', '\u2978', '\u22d7', '\u22d7', '\u22db', '\u22db', '\u2a8c', '\u2a8c', '\u2277', '\u2277', '\u2273', '\u2273', '\u21d4', '\u21d4', '\u200a', '\u200a', '\u00bd', '\u00bd', '\u210b', '\u210b', 7781 '\u044a', '\u044a', '\u2194', '\u2194', '\u2948', '\u2948', '\u21ad', '\u21ad', '\u210f', '\u210f', '\u0125', '\u0125', '\u2665', '\u2665', '\u2665', '\u2665', '\u2026', '\u2026', '\u22b9', '\u22b9', '\U0001d525', '\U0001d525', '\u2925', '\u2925', '\u2926', '\u2926', '\u21ff', '\u21ff', '\u223b', '\u223b', '\u21a9', '\u21a9', 7782 '\u21aa', '\u21aa', '\U0001d559', '\U0001d559', '\u2015', '\u2015', '\U0001d4bd', '\U0001d4bd', '\u210f', '\u210f', '\u0127', '\u0127', '\u2043', '\u2043', '\u2010', '\u2010', '\u00ed', '\u00ed', '\u2063', '\u2063', '\u00ee', '\u00ee', '\u0438', '\u0438', '\u0435', '\u0435', '\u00a1', '\u00a1', '\u21d4', '\u21d4', '\U0001d526', '\U0001d526', '\u00ec', '\u00ec', '\u2148', 7783 '\u2148', '\u2a0c', '\u2a0c', '\u222d', '\u222d', '\u29dc', '\u29dc', '\u2129', '\u2129', '\u0133', '\u0133', '\u012b', '\u012b', '\u2111', '\u2111', '\u2110', '\u2110', '\u2111', '\u2111', '\u0131', '\u0131', '\u22b7', '\u22b7', '\u01b5', '\u01b5', '\u2208', '\u2208', '\u2105', '\u2105', '\u221e', '\u221e', '\u29dd', '\u29dd', '\u0131', 7784 '\u0131', '\u222b', '\u222b', '\u22ba', '\u22ba', '\u2124', '\u2124', '\u22ba', '\u22ba', '\u2a17', '\u2a17', '\u2a3c', '\u2a3c', '\u0451', '\u0451', '\u012f', '\u012f', '\U0001d55a', '\U0001d55a', '\u03b9', '\u03b9', '\u2a3c', '\u2a3c', '\u00bf', '\u00bf', '\U0001d4be', '\U0001d4be', '\u2208', '\u2208', '\u22f9', '\u22f9', '\u22f5', '\u22f5', '\u22f4', 7785 '\u22f4', '\u22f3', '\u22f3', '\u2208', '\u2208', '\u2062', '\u2062', '\u0129', '\u0129', '\u0456', '\u0456', '\u00ef', '\u00ef', '\u0135', '\u0135', '\u0439', '\u0439', '\U0001d527', '\U0001d527', '\u0237', '\u0237', '\U0001d55b', '\U0001d55b', '\U0001d4bf', '\U0001d4bf', '\u0458', '\u0458', '\u0454', '\u0454', '\u03ba', '\u03ba', '\u03f0', '\u03f0', '\u0137', '\u0137', '\u043a', '\u043a', '\U0001d528', 7786 '\U0001d528', '\u0138', '\u0138', '\u0445', '\u0445', '\u045c', '\u045c', '\U0001d55c', '\U0001d55c', '\U0001d4c0', '\U0001d4c0', '\u21da', '\u21da', '\u21d0', '\u21d0', '\u291b', '\u291b', '\u290e', '\u290e', '\u2266', '\u2266', '\u2a8b', '\u2a8b', '\u2962', '\u2962', '\u013a', '\u013a', '\u29b4', '\u29b4', '\u2112', '\u2112', '\u03bb', '\u03bb', '\u27e8', '\u27e8', '\u2991', '\u2991', 7787 '\u27e8', '\u27e8', '\u2a85', '\u2a85', '\u00ab', '\u00ab', '\u2190', '\u2190', '\u21e4', '\u21e4', '\u291f', '\u291f', '\u291d', '\u291d', '\u21a9', '\u21a9', '\u21ab', '\u21ab', '\u2939', '\u2939', '\u2973', '\u2973', '\u21a2', '\u21a2', '\u2aab', '\u2aab', '\u2919', '\u2919', '\u2aad', '\u2aad', '\u290c', '\u290c', '\u2772', '\u2772', '\u007b', 7788 '\u007b', '\u005b', '\u005b', '\u298b', '\u298b', '\u298f', '\u298f', '\u298d', '\u298d', '\u013e', '\u013e', '\u013c', '\u013c', '\u2308', '\u2308', '\u007b', '\u007b', '\u043b', '\u043b', '\u2936', '\u2936', '\u201c', '\u201c', '\u201e', '\u201e', '\u2967', '\u2967', '\u294b', '\u294b', '\u21b2', '\u21b2', '\u2264', '\u2264', '\u2190', 7789 '\u2190', '\u21a2', '\u21a2', '\u21bd', '\u21bd', '\u21bc', '\u21bc', '\u21c7', '\u21c7', '\u2194', '\u2194', '\u21c6', '\u21c6', '\u21cb', '\u21cb', '\u21ad', '\u21ad', '\u22cb', 7790 '\u22cb', '\u22da', '\u22da', '\u2264', '\u2264', '\u2266', '\u2266', '\u2a7d', '\u2a7d', '\u2a7d', '\u2a7d', '\u2aa8', '\u2aa8', '\u2a7f', '\u2a7f', '\u2a81', '\u2a81', '\u2a83', '\u2a83', '\u2a93', '\u2a93', '\u2a85', '\u2a85', '\u22d6', '\u22d6', '\u22da', '\u22da', '\u2a8b', '\u2a8b', '\u2276', '\u2276', 7791 '\u2272', '\u2272', '\u297c', '\u297c', '\u230a', '\u230a', '\U0001d529', '\U0001d529', '\u2276', '\u2276', '\u2a91', '\u2a91', '\u21bd', '\u21bd', '\u21bc', '\u21bc', '\u296a', '\u296a', '\u2584', '\u2584', '\u0459', '\u0459', '\u226a', '\u226a', '\u21c7', '\u21c7', '\u231e', '\u231e', '\u296b', '\u296b', '\u25fa', '\u25fa', '\u0140', '\u0140', '\u23b0', '\u23b0', 7792 '\u23b0', '\u23b0', '\u2268', '\u2268', '\u2a89', '\u2a89', '\u2a89', '\u2a89', '\u2a87', '\u2a87', '\u2a87', '\u2a87', '\u2268', '\u2268', '\u22e6', '\u22e6', '\u27ec', '\u27ec', '\u21fd', '\u21fd', '\u27e6', '\u27e6', '\u27f5', '\u27f5', '\u27f7', '\u27f7', '\u27fc', '\u27fc', '\u27f6', 7793 '\u27f6', '\u21ab', '\u21ab', '\u21ac', '\u21ac', '\u2985', '\u2985', '\U0001d55d', '\U0001d55d', '\u2a2d', '\u2a2d', '\u2a34', '\u2a34', '\u2217', '\u2217', '\u005f', '\u005f', '\u25ca', '\u25ca', '\u25ca', '\u25ca', '\u29eb', '\u29eb', '\u0028', '\u0028', '\u2993', '\u2993', '\u21c6', '\u21c6', '\u231f', 7794 '\u231f', '\u21cb', '\u21cb', '\u296d', '\u296d', '\u200e', '\u200e', '\u22bf', '\u22bf', '\u2039', '\u2039', '\U0001d4c1', '\U0001d4c1', '\u21b0', '\u21b0', '\u2272', '\u2272', '\u2a8d', '\u2a8d', '\u2a8f', '\u2a8f', '\u005b', '\u005b', '\u2018', '\u2018', '\u201a', '\u201a', '\u0142', '\u0142', '\u2aa6', '\u2aa6', '\u2a79', '\u2a79', '\u22d6', '\u22d6', '\u22cb', 7795 '\u22cb', '\u22c9', '\u22c9', '\u2976', '\u2976', '\u2a7b', '\u2a7b', '\u2996', '\u2996', '\u25c3', '\u25c3', '\u22b4', '\u22b4', '\u25c2', '\u25c2', '\u294a', '\u294a', '\u2966', '\u2966', '\u223a', '\u223a', '\u00af', '\u00af', '\u2642', '\u2642', '\u2720', '\u2720', '\u2720', '\u2720', '\u21a6', '\u21a6', '\u21a6', '\u21a6', '\u21a7', 7796 '\u21a7', '\u21a4', '\u21a4', '\u21a5', '\u21a5', '\u25ae', '\u25ae', '\u2a29', '\u2a29', '\u043c', '\u043c', '\u2014', '\u2014', '\u2221', '\u2221', '\U0001d52a', '\U0001d52a', '\u2127', '\u2127', '\u00b5', '\u00b5', '\u2223', '\u2223', '\u002a', '\u002a', '\u2af0', '\u2af0', '\u00b7', '\u00b7', '\u2212', '\u2212', '\u229f', 7797 '\u229f', '\u2238', '\u2238', '\u2a2a', '\u2a2a', '\u2adb', '\u2adb', '\u2026', '\u2026', '\u2213', '\u2213', '\u22a7', '\u22a7', '\U0001d55e', '\U0001d55e', '\u2213', '\u2213', '\U0001d4c2', '\U0001d4c2', '\u223e', '\u223e', '\u03bc', '\u03bc', '\u22b8', '\u22b8', '\u22b8', '\u22b8', '\u21cd', '\u21cd', '\u21ce', '\u21ce', '\u21cf', 7798 '\u21cf', '\u22af', '\u22af', '\u22ae', '\u22ae', '\u2207', '\u2207', '\u0144', '\u0144', '\u2249', '\u2249', '\u0149', '\u0149', '\u2249', '\u2249', '\u266e', '\u266e', '\u266e', '\u266e', '\u2115', '\u2115', '\u00a0', '\u00a0', '\u2a43', '\u2a43', '\u0148', '\u0148', '\u0146', '\u0146', '\u2247', '\u2247', '\u2a42', '\u2a42', '\u043d', 7799 '\u043d', '\u2013', '\u2013', '\u2260', '\u2260', '\u21d7', '\u21d7', '\u2924', '\u2924', '\u2197', '\u2197', '\u2197', '\u2197', '\u2262', '\u2262', '\u2928', '\u2928', '\u2204', '\u2204', '\u2204', '\u2204', '\U0001d52b', '\U0001d52b', '\u2271', '\u2271', '\u2271', '\u2271', '\u2275', '\u2275', '\u226f', '\u226f', '\u226f', '\u226f', '\u21ce', '\u21ce', '\u21ae', '\u21ae', 7800 '\u2af2', '\u2af2', '\u220b', '\u220b', '\u22fc', '\u22fc', '\u22fa', '\u22fa', '\u220b', '\u220b', '\u045a', '\u045a', '\u21cd', '\u21cd', '\u219a', '\u219a', '\u2025', '\u2025', '\u2270', '\u2270', '\u219a', '\u219a', '\u21ae', '\u21ae', '\u2270', '\u2270', '\u226e', '\u226e', '\u2274', '\u2274', '\u226e', '\u226e', '\u22ea', '\u22ea', '\u22ec', '\u22ec', 7801 '\u2224', '\u2224', '\U0001d55f', '\U0001d55f', '\u00ac', '\u00ac', '\u2209', '\u2209', '\u2209', '\u2209', '\u22f7', '\u22f7', '\u22f6', '\u22f6', '\u220c', '\u220c', '\u220c', '\u220c', '\u22fe', '\u22fe', '\u22fd', '\u22fd', '\u2226', '\u2226', '\u2226', '\u2226', '\u2a14', '\u2a14', '\u2280', '\u2280', '\u22e0', '\u22e0', '\u2280', 7802 '\u2280', '\u21cf', '\u21cf', '\u219b', '\u219b', '\u219b', '\u219b', '\u22eb', '\u22eb', '\u22ed', '\u22ed', '\u2281', '\u2281', '\u22e1', '\u22e1', '\U0001d4c3', '\U0001d4c3', '\u2224', '\u2224', '\u2226', '\u2226', '\u2241', '\u2241', '\u2244', '\u2244', '\u2244', '\u2244', '\u2224', '\u2224', '\u2226', '\u2226', '\u22e2', 7803 '\u22e2', '\u22e3', '\u22e3', '\u2284', '\u2284', '\u2288', '\u2288', '\u2288', '\u2288', '\u2281', '\u2281', '\u2285', '\u2285', '\u2289', '\u2289', '\u2289', '\u2289', '\u2279', '\u2279', '\u00f1', '\u00f1', '\u2278', '\u2278', '\u22ea', '\u22ea', '\u22ec', '\u22ec', '\u22eb', '\u22eb', 7804 '\u22ed', '\u22ed', '\u03bd', '\u03bd', '\u0023', '\u0023', '\u2116', '\u2116', '\u2007', '\u2007', '\u22ad', '\u22ad', '\u2904', '\u2904', '\u22ac', '\u22ac', '\u29de', '\u29de', '\u2902', '\u2902', '\u2903', '\u2903', '\u21d6', '\u21d6', '\u2923', '\u2923', '\u2196', '\u2196', '\u2196', '\u2196', '\u2927', '\u2927', 7805 '\u24c8', '\u24c8', '\u00f3', '\u00f3', '\u229b', '\u229b', '\u229a', '\u229a', '\u00f4', '\u00f4', '\u043e', '\u043e', '\u229d', '\u229d', '\u0151', '\u0151', '\u2a38', '\u2a38', '\u2299', '\u2299', '\u29bc', '\u29bc', '\u0153', '\u0153', '\u29bf', '\u29bf', '\U0001d52c', '\U0001d52c', '\u02db', '\u02db', '\u00f2', '\u00f2', '\u29c1', '\u29c1', '\u29b5', '\u29b5', '\u03a9', '\u03a9', '\u222e', 7806 '\u222e', '\u21ba', '\u21ba', '\u29be', '\u29be', '\u29bb', '\u29bb', '\u203e', '\u203e', '\u29c0', '\u29c0', '\u014d', '\u014d', '\u03c9', '\u03c9', '\u03bf', '\u03bf', '\u29b6', '\u29b6', '\u2296', '\u2296', '\U0001d560', '\U0001d560', '\u29b7', '\u29b7', '\u29b9', '\u29b9', '\u2295', '\u2295', '\u2228', '\u2228', '\u21bb', '\u21bb', '\u2a5d', '\u2a5d', '\u2134', '\u2134', 7807 '\u2134', '\u2134', '\u00aa', '\u00aa', '\u00ba', '\u00ba', '\u22b6', '\u22b6', '\u2a56', '\u2a56', '\u2a57', '\u2a57', '\u2a5b', '\u2a5b', '\u2134', '\u2134', '\u00f8', '\u00f8', '\u2298', '\u2298', '\u00f5', '\u00f5', '\u2297', '\u2297', '\u2a36', '\u2a36', '\u00f6', '\u00f6', '\u233d', '\u233d', '\u2225', '\u2225', '\u00b6', '\u00b6', '\u2225', '\u2225', 7808 '\u2af3', '\u2af3', '\u2afd', '\u2afd', '\u2202', '\u2202', '\u043f', '\u043f', '\u0025', '\u0025', '\u002e', '\u002e', '\u2030', '\u2030', '\u22a5', '\u22a5', '\u2031', '\u2031', '\U0001d52d', '\U0001d52d', '\u03c6', '\u03c6', '\u03d5', '\u03d5', '\u2133', '\u2133', '\u260e', '\u260e', '\u03c0', '\u03c0', '\u22d4', '\u22d4', '\u03d6', '\u03d6', '\u210f', '\u210f', 7809 '\u210e', '\u210e', '\u210f', '\u210f', '\u002b', '\u002b', '\u2a23', '\u2a23', '\u229e', '\u229e', '\u2a22', '\u2a22', '\u2214', '\u2214', '\u2a25', '\u2a25', '\u2a72', '\u2a72', '\u00b1', '\u00b1', '\u2a26', '\u2a26', '\u2a27', '\u2a27', '\u00b1', '\u00b1', '\u2a15', '\u2a15', '\U0001d561', '\U0001d561', '\u00a3', '\u00a3', '\u227a', 7810 '\u227a', '\u2ab3', '\u2ab3', '\u2ab7', '\u2ab7', '\u227c', '\u227c', '\u2aaf', '\u2aaf', '\u227a', '\u227a', '\u2ab7', '\u2ab7', '\u227c', '\u227c', '\u2aaf', '\u2aaf', '\u2ab9', '\u2ab9', '\u2ab5', '\u2ab5', '\u22e8', '\u22e8', '\u227e', '\u227e', '\u2032', '\u2032', '\u2119', '\u2119', '\u2ab5', '\u2ab5', '\u2ab9', 7811 '\u2ab9', '\u22e8', '\u22e8', '\u220f', '\u220f', '\u232e', '\u232e', '\u2312', '\u2312', '\u2313', '\u2313', '\u221d', '\u221d', '\u221d', '\u221d', '\u227e', '\u227e', '\u22b0', '\u22b0', '\U0001d4c5', '\U0001d4c5', '\u03c8', '\u03c8', '\u2008', '\u2008', '\U0001d52e', '\U0001d52e', '\u2a0c', '\u2a0c', '\U0001d562', '\U0001d562', '\u2057', '\u2057', '\U0001d4c6', '\U0001d4c6', 7812 '\u210d', '\u210d', '\u2a16', '\u2a16', '\u003f', '\u003f', '\u225f', '\u225f', '\u21db', '\u21db', '\u21d2', '\u21d2', '\u291c', '\u291c', '\u290f', '\u290f', '\u2964', '\u2964', '\u0155', '\u0155', '\u221a', '\u221a', '\u29b3', '\u29b3', '\u27e9', '\u27e9', '\u2992', '\u2992', '\u29a5', '\u29a5', '\u27e9', '\u27e9', '\u00bb', 7813 '\u00bb', '\u2192', '\u2192', '\u2975', '\u2975', '\u21e5', '\u21e5', '\u2920', '\u2920', '\u2933', '\u2933', '\u291e', '\u291e', '\u21aa', '\u21aa', '\u21ac', '\u21ac', '\u2945', '\u2945', '\u2974', '\u2974', '\u21a3', '\u21a3', '\u219d', '\u219d', '\u291a', '\u291a', '\u2236', '\u2236', '\u211a', '\u211a', '\u290d', '\u290d', 7814 '\u2773', '\u2773', '\u007d', '\u007d', '\u005d', '\u005d', '\u298c', '\u298c', '\u298e', '\u298e', '\u2990', '\u2990', '\u0159', '\u0159', '\u0157', '\u0157', '\u2309', '\u2309', '\u007d', '\u007d', '\u0440', '\u0440', '\u2937', '\u2937', '\u2969', '\u2969', '\u201d', '\u201d', '\u201d', '\u201d', '\u21b3', '\u21b3', '\u211c', '\u211c', '\u211b', 7815 '\u211b', '\u211c', '\u211c', '\u211d', '\u211d', '\u25ad', '\u25ad', '\u00ae', '\u00ae', '\u297d', '\u297d', '\u230b', '\u230b', '\U0001d52f', '\U0001d52f', '\u21c1', '\u21c1', '\u21c0', '\u21c0', '\u296c', '\u296c', '\u03c1', '\u03c1', '\u03f1', '\u03f1', '\u2192', '\u2192', '\u21a3', '\u21a3', '\u21c1', '\u21c1', 7816 '\u21c0', '\u21c0', '\u21c4', '\u21c4', '\u21cc', '\u21cc', '\u21c9', '\u21c9', '\u219d', '\u219d', '\u22cc', '\u22cc', '\u02da', '\u02da', '\u2253', '\u2253', '\u21c4', '\u21c4', '\u21cc', '\u21cc', '\u200f', 7817 '\u200f', '\u23b1', '\u23b1', '\u23b1', '\u23b1', '\u2aee', '\u2aee', '\u27ed', '\u27ed', '\u21fe', '\u21fe', '\u27e7', '\u27e7', '\u2986', '\u2986', '\U0001d563', '\U0001d563', '\u2a2e', '\u2a2e', '\u2a35', '\u2a35', '\u0029', '\u0029', '\u2994', '\u2994', '\u2a12', '\u2a12', '\u21c9', '\u21c9', '\u203a', '\u203a', '\U0001d4c7', '\U0001d4c7', '\u21b1', 7818 '\u21b1', '\u005d', '\u005d', '\u2019', '\u2019', '\u2019', '\u2019', '\u22cc', '\u22cc', '\u22ca', '\u22ca', '\u25b9', '\u25b9', '\u22b5', '\u22b5', '\u25b8', '\u25b8', '\u29ce', '\u29ce', '\u2968', '\u2968', '\u211e', '\u211e', '\u015b', '\u015b', '\u201a', '\u201a', '\u227b', '\u227b', '\u2ab4', '\u2ab4', '\u2ab8', '\u2ab8', '\u0161', '\u0161', '\u227d', 7819 '\u227d', '\u2ab0', '\u2ab0', '\u015f', '\u015f', '\u015d', '\u015d', '\u2ab6', '\u2ab6', '\u2aba', '\u2aba', '\u22e9', '\u22e9', '\u2a13', '\u2a13', '\u227f', '\u227f', '\u0441', '\u0441', '\u22c5', '\u22c5', '\u22a1', '\u22a1', '\u2a66', '\u2a66', '\u21d8', '\u21d8', '\u2925', '\u2925', '\u2198', '\u2198', '\u2198', '\u2198', '\u00a7', '\u00a7', '\u003b', 7820 '\u003b', '\u2929', '\u2929', '\u2216', '\u2216', '\u2216', '\u2216', '\u2736', '\u2736', '\U0001d530', '\U0001d530', '\u2322', '\u2322', '\u266f', '\u266f', '\u0449', '\u0449', '\u0448', '\u0448', '\u2223', '\u2223', '\u2225', '\u2225', '\u00ad', '\u00ad', '\u03c3', '\u03c3', '\u03c2', '\u03c2', '\u03c2', '\u03c2', '\u223c', '\u223c', '\u2a6a', 7821 '\u2a6a', '\u2243', '\u2243', '\u2243', '\u2243', '\u2a9e', '\u2a9e', '\u2aa0', '\u2aa0', '\u2a9d', '\u2a9d', '\u2a9f', '\u2a9f', '\u2246', '\u2246', '\u2a24', '\u2a24', '\u2972', '\u2972', '\u2190', '\u2190', '\u2216', '\u2216', '\u2a33', '\u2a33', '\u29e4', '\u29e4', '\u2223', '\u2223', '\u2323', '\u2323', '\u2aaa', '\u2aaa', '\u2aac', 7822 '\u2aac', '\u044c', '\u044c', '\u002f', '\u002f', '\u29c4', '\u29c4', '\u233f', '\u233f', '\U0001d564', '\U0001d564', '\u2660', '\u2660', '\u2660', '\u2660', '\u2225', '\u2225', '\u2293', '\u2293', '\u2294', '\u2294', '\u228f', '\u228f', '\u2291', '\u2291', '\u228f', '\u228f', '\u2291', '\u2291', '\u2290', '\u2290', '\u2292', '\u2292', 7823 '\u2290', '\u2290', '\u2292', '\u2292', '\u25a1', '\u25a1', '\u25a1', '\u25a1', '\u25aa', '\u25aa', '\u25aa', '\u25aa', '\u2192', '\u2192', '\U0001d4c8', '\U0001d4c8', '\u2216', '\u2216', '\u2323', '\u2323', '\u22c6', '\u22c6', '\u2606', '\u2606', '\u2605', '\u2605', '\u03f5', '\u03f5', '\u03d5', '\u03d5', '\u00af', 7824 '\u00af', '\u2282', '\u2282', '\u2ac5', '\u2ac5', '\u2abd', '\u2abd', '\u2286', '\u2286', '\u2ac3', '\u2ac3', '\u2ac1', '\u2ac1', '\u2acb', '\u2acb', '\u228a', '\u228a', '\u2abf', '\u2abf', '\u2979', '\u2979', '\u2282', '\u2282', '\u2286', '\u2286', '\u2ac5', '\u2ac5', '\u228a', '\u228a', '\u2acb', '\u2acb', 7825 '\u2ac7', '\u2ac7', '\u2ad5', '\u2ad5', '\u2ad3', '\u2ad3', '\u227b', '\u227b', '\u2ab8', '\u2ab8', '\u227d', '\u227d', '\u2ab0', '\u2ab0', '\u2aba', '\u2aba', '\u2ab6', '\u2ab6', '\u22e9', '\u22e9', '\u227f', '\u227f', '\u2211', '\u2211', '\u266a', '\u266a', '\u2283', '\u2283', '\u00b9', '\u00b9', '\u00b2', 7826 '\u00b2', '\u00b3', '\u00b3', '\u2ac6', '\u2ac6', '\u2abe', '\u2abe', '\u2ad8', '\u2ad8', '\u2287', '\u2287', '\u2ac4', '\u2ac4', '\u27c9', '\u27c9', '\u2ad7', '\u2ad7', '\u297b', '\u297b', '\u2ac2', '\u2ac2', '\u2acc', '\u2acc', '\u228b', '\u228b', '\u2ac0', '\u2ac0', '\u2283', '\u2283', '\u2287', '\u2287', '\u2ac6', 7827 '\u2ac6', '\u228b', '\u228b', '\u2acc', '\u2acc', '\u2ac8', '\u2ac8', '\u2ad4', '\u2ad4', '\u2ad6', '\u2ad6', '\u21d9', '\u21d9', '\u2926', '\u2926', '\u2199', '\u2199', '\u2199', '\u2199', '\u292a', '\u292a', '\u00df', '\u00df', '\u2316', '\u2316', '\u03c4', '\u03c4', '\u23b4', '\u23b4', '\u0165', '\u0165', '\u0163', 7828 '\u0163', '\u0442', '\u0442', '\u20db', '\u20db', '\u2315', '\u2315', '\U0001d531', '\U0001d531', '\u2234', '\u2234', '\u2234', '\u2234', '\u03b8', '\u03b8', '\u03d1', '\u03d1', '\u03d1', '\u03d1', '\u2248', '\u2248', '\u223c', '\u223c', '\u2009', '\u2009', '\u2248', '\u2248', '\u223c', '\u223c', '\u00fe', '\u00fe', '\u02dc', 7829 '\u02dc', '\u00d7', '\u00d7', '\u22a0', '\u22a0', '\u2a31', '\u2a31', '\u2a30', '\u2a30', '\u222d', '\u222d', '\u2928', '\u2928', '\u22a4', '\u22a4', '\u2336', '\u2336', '\u2af1', '\u2af1', '\U0001d565', '\U0001d565', '\u2ada', '\u2ada', '\u2929', '\u2929', '\u2034', '\u2034', '\u2122', '\u2122', '\u25b5', '\u25b5', '\u25bf', '\u25bf', 7830 '\u25c3', '\u25c3', '\u22b4', '\u22b4', '\u225c', '\u225c', '\u25b9', '\u25b9', '\u22b5', '\u22b5', '\u25ec', '\u25ec', '\u225c', '\u225c', '\u2a3a', '\u2a3a', '\u2a39', '\u2a39', '\u29cd', '\u29cd', '\u2a3b', '\u2a3b', '\u23e2', '\u23e2', '\U0001d4c9', 7831 '\U0001d4c9', '\u0446', '\u0446', '\u045b', '\u045b', '\u0167', '\u0167', '\u226c', '\u226c', '\u219e', '\u219e', '\u21a0', '\u21a0', '\u21d1', '\u21d1', '\u2963', '\u2963', '\u00fa', '\u00fa', '\u2191', '\u2191', '\u045e', '\u045e', '\u016d', '\u016d', '\u00fb', '\u00fb', '\u0443', '\u0443', '\u21c5', '\u21c5', '\u0171', 7832 '\u0171', '\u296e', '\u296e', '\u297e', '\u297e', '\U0001d532', '\U0001d532', '\u00f9', '\u00f9', '\u21bf', '\u21bf', '\u21be', '\u21be', '\u2580', '\u2580', '\u231c', '\u231c', '\u231c', '\u231c', '\u230f', '\u230f', '\u25f8', '\u25f8', '\u016b', '\u016b', '\u00a8', '\u00a8', '\u0173', '\u0173', '\U0001d566', '\U0001d566', '\u2191', '\u2191', '\u2195', 7833 '\u2195', '\u21bf', '\u21bf', '\u21be', '\u21be', '\u228e', '\u228e', '\u03c5', '\u03c5', '\u03d2', '\u03d2', '\u03c5', '\u03c5', '\u21c8', '\u21c8', '\u231d', '\u231d', '\u231d', '\u231d', '\u230e', '\u230e', '\u016f', '\u016f', '\u25f9', '\u25f9', '\U0001d4ca', '\U0001d4ca', '\u22f0', '\u22f0', 7834 '\u0169', '\u0169', '\u25b5', '\u25b5', '\u25b4', '\u25b4', '\u21c8', '\u21c8', '\u00fc', '\u00fc', '\u29a7', '\u29a7', '\u21d5', '\u21d5', '\u2ae8', '\u2ae8', '\u2ae9', '\u2ae9', '\u22a8', '\u22a8', '\u299c', '\u299c', '\u03f5', '\u03f5', '\u03f0', '\u03f0', '\u2205', '\u2205', '\u03d5', '\u03d5', '\u03d6', '\u03d6', '\u221d', 7835 '\u221d', '\u2195', '\u2195', '\u03f1', '\u03f1', '\u03c2', '\u03c2', '\u03d1', '\u03d1', '\u22b2', '\u22b2', '\u22b3', '\u22b3', '\u0432', '\u0432', '\u22a2', '\u22a2', '\u2228', '\u2228', '\u22bb', '\u22bb', '\u225a', '\u225a', '\u22ee', '\u22ee', '\u007c', '\u007c', '\u007c', '\u007c', '\U0001d533', 7836 '\U0001d533', '\u22b2', '\u22b2', '\U0001d567', '\U0001d567', '\u221d', '\u221d', '\u22b3', '\u22b3', '\U0001d4cb', '\U0001d4cb', '\u299a', '\u299a', '\u0175', '\u0175', '\u2a5f', '\u2a5f', '\u2227', '\u2227', '\u2259', '\u2259', '\u2118', '\u2118', '\U0001d534', '\U0001d534', '\U0001d568', '\U0001d568', '\u2118', '\u2118', '\u2240', '\u2240', '\u2240', '\u2240', '\U0001d4cc', '\U0001d4cc', '\u22c2', '\u22c2', '\u25ef', 7837 '\u25ef', '\u22c3', '\u22c3', '\u25bd', '\u25bd', '\U0001d535', '\U0001d535', '\u27fa', '\u27fa', '\u27f7', '\u27f7', '\u03be', '\u03be', '\u27f8', '\u27f8', '\u27f5', '\u27f5', '\u27fc', '\u27fc', '\u22fb', '\u22fb', '\u2a00', '\u2a00', '\U0001d569', '\U0001d569', '\u2a01', '\u2a01', '\u2a02', '\u2a02', '\u27f9', '\u27f9', '\u27f6', '\u27f6', '\U0001d4cd', '\U0001d4cd', '\u2a06', '\u2a06', '\u2a04', 7838 '\u2a04', '\u25b3', '\u25b3', '\u22c1', '\u22c1', '\u22c0', '\u22c0', '\u00fd', '\u00fd', '\u044f', '\u044f', '\u0177', '\u0177', '\u044b', '\u044b', '\u00a5', '\u00a5', '\U0001d536', '\U0001d536', '\u0457', '\u0457', '\U0001d56a', '\U0001d56a', '\U0001d4ce', '\U0001d4ce', '\u044e', '\u044e', '\u00ff', '\u00ff', '\u017a', '\u017a', '\u017e', '\u017e', '\u0437', '\u0437', '\u017c', '\u017c', '\u2128', 7839 '\u2128', '\u03b6', '\u03b6', '\U0001d537', '\U0001d537', '\u0436', '\u0436', '\u21dd', '\u21dd', '\U0001d56b', '\U0001d56b', '\U0001d4cf', '\U0001d4cf', '\u200d', '\u200d', '\u200c', '\u200c', ]; 7840 7841 7842 7843 7844 7845 7846 7847 7848 7849 7850 7851 7852 7853 7854 7855 7856 7857 7858 7859 7860 7861 7862 7863 // dom event support, if you want to use it 7864 7865 /// used for DOM events 7866 version(dom_with_events) 7867 alias EventHandler = void delegate(Element handlerAttachedTo, Event event); 7868 7869 /// This is a DOM event, like in javascript. Note that this library never fires events - it is only here for you to use if you want it. 7870 version(dom_with_events) 7871 class Event { 7872 this(string eventName, Element target) { 7873 this.eventName = eventName; 7874 this.srcElement = target; 7875 } 7876 7877 /// Prevents the default event handler (if there is one) from being called 7878 void preventDefault() { 7879 defaultPrevented = true; 7880 } 7881 7882 /// Stops the event propagation immediately. 7883 void stopPropagation() { 7884 propagationStopped = true; 7885 } 7886 7887 bool defaultPrevented; 7888 bool propagationStopped; 7889 string eventName; 7890 7891 Element srcElement; 7892 alias srcElement target; 7893 7894 Element relatedTarget; 7895 7896 int clientX; 7897 int clientY; 7898 7899 int button; 7900 7901 bool isBubbling; 7902 7903 /// this sends it only to the target. If you want propagation, use dispatch() instead. 7904 void send() { 7905 if(srcElement is null) 7906 return; 7907 7908 auto e = srcElement; 7909 7910 if(eventName in e.bubblingEventHandlers) 7911 foreach(handler; e.bubblingEventHandlers[eventName]) 7912 handler(e, this); 7913 7914 if(!defaultPrevented) 7915 if(eventName in e.defaultEventHandlers) 7916 e.defaultEventHandlers[eventName](e, this); 7917 } 7918 7919 /// this dispatches the element using the capture -> target -> bubble process 7920 void dispatch() { 7921 if(srcElement is null) 7922 return; 7923 7924 // first capture, then bubble 7925 7926 Element[] chain; 7927 Element curr = srcElement; 7928 while(curr) { 7929 auto l = curr; 7930 chain ~= l; 7931 curr = curr.parentNode; 7932 7933 } 7934 7935 isBubbling = false; 7936 7937 foreach(e; chain.retro()) { 7938 if(eventName in e.capturingEventHandlers) 7939 foreach(handler; e.capturingEventHandlers[eventName]) 7940 handler(e, this); 7941 7942 // the default on capture should really be to always do nothing 7943 7944 //if(!defaultPrevented) 7945 // if(eventName in e.defaultEventHandlers) 7946 // e.defaultEventHandlers[eventName](e.element, this); 7947 7948 if(propagationStopped) 7949 break; 7950 } 7951 7952 isBubbling = true; 7953 if(!propagationStopped) 7954 foreach(e; chain) { 7955 if(eventName in e.bubblingEventHandlers) 7956 foreach(handler; e.bubblingEventHandlers[eventName]) 7957 handler(e, this); 7958 7959 if(propagationStopped) 7960 break; 7961 } 7962 7963 if(!defaultPrevented) 7964 foreach(e; chain) { 7965 if(eventName in e.defaultEventHandlers) 7966 e.defaultEventHandlers[eventName](e, this); 7967 } 7968 } 7969 } 7970 7971 struct FormFieldOptions { 7972 // usable for any 7973 7974 /// this is a regex pattern used to validate the field 7975 string pattern; 7976 /// must the field be filled in? Even with a regex, it can be submitted blank if this is false. 7977 bool isRequired; 7978 /// this is displayed as an example to the user 7979 string placeholder; 7980 7981 // usable for numeric ones 7982 7983 7984 // convenience methods to quickly get some options 7985 @property static FormFieldOptions none() { 7986 FormFieldOptions f; 7987 return f; 7988 } 7989 7990 static FormFieldOptions required() { 7991 FormFieldOptions f; 7992 f.isRequired = true; 7993 return f; 7994 } 7995 7996 static FormFieldOptions regex(string pattern, bool required = false) { 7997 FormFieldOptions f; 7998 f.pattern = pattern; 7999 f.isRequired = required; 8000 return f; 8001 } 8002 8003 static FormFieldOptions fromElement(Element e) { 8004 FormFieldOptions f; 8005 if(e.hasAttribute("required")) 8006 f.isRequired = true; 8007 if(e.hasAttribute("pattern")) 8008 f.pattern = e.pattern; 8009 if(e.hasAttribute("placeholder")) 8010 f.placeholder = e.placeholder; 8011 return f; 8012 } 8013 8014 Element applyToElement(Element e) { 8015 if(this.isRequired) 8016 e.required = "required"; 8017 if(this.pattern.length) 8018 e.pattern = this.pattern; 8019 if(this.placeholder.length) 8020 e.placeholder = this.placeholder; 8021 return e; 8022 } 8023 } 8024 8025 // this needs to look just like a string, but can expand as needed 8026 version(no_dom_stream) 8027 alias string Utf8Stream; 8028 else 8029 class Utf8Stream { 8030 protected: 8031 // these two should be overridden in subclasses to actually do the stream magic 8032 string getMore() { 8033 if(getMoreHelper !is null) 8034 return getMoreHelper(); 8035 return null; 8036 } 8037 8038 bool hasMore() { 8039 if(hasMoreHelper !is null) 8040 return hasMoreHelper(); 8041 return false; 8042 } 8043 // the rest should be ok 8044 8045 public: 8046 this(string d) { 8047 this.data = d; 8048 } 8049 8050 this(string delegate() getMoreHelper, bool delegate() hasMoreHelper) { 8051 this.getMoreHelper = getMoreHelper; 8052 this.hasMoreHelper = hasMoreHelper; 8053 8054 if(hasMore()) 8055 this.data ~= getMore(); 8056 8057 stdout.flush(); 8058 } 8059 8060 @property final size_t length() { 8061 // the parser checks length primarily directly before accessing the next character 8062 // so this is the place we'll hook to append more if possible and needed. 8063 if(lastIdx + 1 >= data.length && hasMore()) { 8064 data ~= getMore(); 8065 } 8066 return data.length; 8067 } 8068 8069 final char opIndex(size_t idx) { 8070 if(idx > lastIdx) 8071 lastIdx = idx; 8072 return data[idx]; 8073 } 8074 8075 final string opSlice(size_t start, size_t end) { 8076 if(end > lastIdx) 8077 lastIdx = end; 8078 return data[start .. end]; 8079 } 8080 8081 final size_t opDollar() { 8082 return length(); 8083 } 8084 8085 final Utf8Stream opBinary(string op : "~")(string s) { 8086 this.data ~= s; 8087 return this; 8088 } 8089 8090 final Utf8Stream opOpAssign(string op : "~")(string s) { 8091 this.data ~= s; 8092 return this; 8093 } 8094 8095 final Utf8Stream opAssign(string rhs) { 8096 this.data = rhs; 8097 return this; 8098 } 8099 private: 8100 string data; 8101 8102 size_t lastIdx; 8103 8104 bool delegate() hasMoreHelper; 8105 string delegate() getMoreHelper; 8106 8107 8108 /+ 8109 // used to maybe clear some old stuff 8110 // you might have to remove elements parsed with it too since they can hold slices into the 8111 // old stuff, preventing gc 8112 void dropFront(int bytes) { 8113 posAdjustment += bytes; 8114 data = data[bytes .. $]; 8115 } 8116 8117 int posAdjustment; 8118 +/ 8119 } 8120 8121 void fillForm(T)(Form form, T obj, string name) { 8122 import arsd.database; 8123 fillData((k, v) => form.setValue(k, v), obj, name); 8124 } 8125 8126 /++ 8127 Normalizes the whitespace in the given text according to HTML rules. 8128 8129 History: 8130 Added March 25, 2022 (dub v10.8) 8131 +/ 8132 string normalizeWhitespace(string text) { 8133 string ret; 8134 ret.reserve(text.length); 8135 bool lastWasWhite = true; 8136 foreach(char ch; text) { 8137 if(ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r') { 8138 if(lastWasWhite) 8139 continue; 8140 lastWasWhite = true; 8141 ch = ' '; 8142 } else { 8143 lastWasWhite = false; 8144 } 8145 8146 ret ~= ch; 8147 } 8148 8149 return ret.stripRight; 8150 } 8151 8152 unittest { 8153 assert(normalizeWhitespace(" foo ") == "foo"); 8154 assert(normalizeWhitespace(" f\n \t oo ") == "f oo"); 8155 } 8156 8157 unittest { 8158 Document document; 8159 8160 document = new Document("<test> foo \r </test>"); 8161 assert(document.root.visibleText == "foo"); 8162 8163 document = new Document("<test> foo \r <br>hi</test>"); 8164 assert(document.root.visibleText == "foo\nhi"); 8165 8166 document = new Document("<test> foo \r <br>hi<pre>hi\nthere\n indent<br />line</pre></test>"); 8167 assert(document.root.visibleText == "foo\nhihi\nthere\n indent\nline", document.root.visibleText); 8168 } 8169 8170 /+ 8171 /+ 8172 Syntax: 8173 8174 Tag: tagname#id.class 8175 Tree: Tag(Children, comma, separated...) 8176 Children: Tee or Variable 8177 Variable: $varname with optional |funcname following. 8178 8179 If a variable has a tree after it, it breaks the variable down: 8180 * if array, foreach it does the tree 8181 * if struct, it breaks down the member variables 8182 8183 stolen from georgy on irc, see: https://github.com/georgy7/stringplate 8184 +/ 8185 struct Stringplate { 8186 /++ 8187 8188 +/ 8189 this(string s) { 8190 8191 } 8192 8193 /++ 8194 8195 +/ 8196 Element expand(T...)(T vars) { 8197 return null; 8198 } 8199 } 8200 /// 8201 unittest { 8202 auto stringplate = Stringplate("#bar(.foo($foo), .baz($baz))"); 8203 assert(stringplate.expand.innerHTML == `<div id="bar"><div class="foo">$foo</div><div class="baz">$baz</div></div>`); 8204 } 8205 +/ 8206 8207 bool allAreInlineHtml(const(Element)[] children, const string[] inlineElements) { 8208 foreach(child; children) { 8209 if(child.nodeType == NodeType.Text && child.nodeValue.strip.length) { 8210 // cool 8211 } else if(child.tagName.isInArray(inlineElements) && allAreInlineHtml(child.children, inlineElements)) { 8212 // cool, this is an inline element and none of its children contradict that 8213 } else { 8214 // prolly block 8215 return false; 8216 } 8217 } 8218 return true; 8219 } 8220 8221 private bool isSimpleWhite(dchar c) { 8222 return c == ' ' || c == '\r' || c == '\n' || c == '\t'; 8223 } 8224 8225 unittest { 8226 // Test for issue #120 8227 string s = `<html> 8228 <body> 8229 <P>AN 8230 <P>bubbles</P> 8231 <P>giggles</P> 8232 </body> 8233 </html>`; 8234 auto doc = new Document(); 8235 doc.parseUtf8(s, false, false); 8236 auto s2 = doc.toString(); 8237 assert( 8238 s2.indexOf("bubbles") < s2.indexOf("giggles"), 8239 "paragraph order incorrect:\n" ~ s2); 8240 } 8241 8242 unittest { 8243 // test for suncarpet email dec 24 2019 8244 // arbitrary id asduiwh 8245 auto document = new Document("<html> 8246 <head> 8247 <meta charset=\"utf-8\"></meta> 8248 <title>Element.querySelector Test</title> 8249 </head> 8250 <body> 8251 <div id=\"foo\"> 8252 <div>Foo</div> 8253 <div>Bar</div> 8254 </div> 8255 <div id=\"empty\"></div> 8256 <div id=\"empty-but-text\">test</div> 8257 </body> 8258 </html>"); 8259 8260 auto doc = document; 8261 8262 { 8263 auto empty = doc.requireElementById("empty"); 8264 assert(empty.querySelector(" > *") is null, empty.querySelector(" > *").toString); 8265 } 8266 { 8267 auto empty = doc.requireElementById("empty-but-text"); 8268 assert(empty.querySelector(" > *") is null, empty.querySelector(" > *").toString); 8269 } 8270 8271 assert(doc.querySelectorAll("div div").length == 2); 8272 assert(doc.querySelector("div").querySelectorAll("div").length == 2); 8273 assert(doc.querySelectorAll("> html").length == 0); 8274 assert(doc.querySelector("head").querySelectorAll("> title").length == 1); 8275 assert(doc.querySelector("head").querySelectorAll("> meta[charset]").length == 1); 8276 8277 8278 assert(doc.root.matches("html")); 8279 assert(!doc.root.matches("nothtml")); 8280 assert(doc.querySelector("#foo > div").matches("div")); 8281 assert(doc.querySelector("body > #foo").matches("#foo")); 8282 8283 assert(doc.root.querySelectorAll(":root > body").length == 0); // the root has no CHILD root! 8284 assert(doc.querySelectorAll(":root > body").length == 1); // but the DOCUMENT does 8285 assert(doc.querySelectorAll(" > body").length == 1); // should mean the same thing 8286 assert(doc.root.querySelectorAll(" > body").length == 1); // the root of HTML has this 8287 assert(doc.root.querySelectorAll(" > html").length == 0); // but not this 8288 8289 // also confirming the querySelector works via the mdn definition 8290 auto foo = doc.requireSelector("#foo"); 8291 assert(foo.querySelector("#foo > div") !is null); 8292 assert(foo.querySelector("body #foo > div") !is null); 8293 8294 // this is SUPPOSED to work according to the spec but never has in dom.d since it limits the scope. 8295 // the new css :scope thing is designed to bring this in. and meh idk if i even care. 8296 //assert(foo.querySelectorAll("#foo > div").length == 2); 8297 } 8298 8299 unittest { 8300 // based on https://developer.mozilla.org/en-US/docs/Web/API/Element/closest example 8301 auto document = new Document(`<article> 8302 <div id="div-01">Here is div-01 8303 <div id="div-02">Here is div-02 8304 <div id="div-03">Here is div-03</div> 8305 </div> 8306 </div> 8307 </article>`, true, true); 8308 8309 auto el = document.getElementById("div-03"); 8310 assert(el.closest("#div-02").id == "div-02"); 8311 assert(el.closest("div div").id == "div-03"); 8312 assert(el.closest("article > div").id == "div-01"); 8313 assert(el.closest(":not(div)").tagName == "article"); 8314 8315 assert(el.closest("p") is null); 8316 assert(el.closest("p, div") is el); 8317 } 8318 8319 unittest { 8320 // https://developer.mozilla.org/en-US/docs/Web/CSS/:is 8321 auto document = new Document(`<test> 8322 <div class="foo"><p>cool</p><span>bar</span></div> 8323 <main><p>two</p></main> 8324 </test>`); 8325 8326 assert(document.querySelectorAll(":is(.foo, main) p").length == 2); 8327 assert(document.querySelector("div:where(.foo)") !is null); 8328 } 8329 8330 unittest { 8331 immutable string html = q{ 8332 <root> 8333 <div class="roundedbox"> 8334 <table> 8335 <caption class="boxheader">Recent Reviews</caption> 8336 <tr> 8337 <th>Game</th> 8338 <th>User</th> 8339 <th>Rating</th> 8340 <th>Created</th> 8341 </tr> 8342 8343 <tr> 8344 <td>June 13, 2020 15:10</td> 8345 <td><a href="/reviews/8833">[Show]</a></td> 8346 </tr> 8347 8348 <tr> 8349 <td>June 13, 2020 15:02</td> 8350 <td><a href="/reviews/8832">[Show]</a></td> 8351 </tr> 8352 8353 <tr> 8354 <td>June 13, 2020 14:41</td> 8355 <td><a href="/reviews/8831">[Show]</a></td> 8356 </tr> 8357 </table> 8358 </div> 8359 </root> 8360 }; 8361 8362 auto doc = new Document(cast(string)html); 8363 // this should select the second table row, but... 8364 auto rd = doc.root.querySelector(`div.roundedbox > table > caption.boxheader + tr + tr + tr > td > a[href^=/reviews/]`); 8365 assert(rd !is null); 8366 assert(rd.href == "/reviews/8832"); 8367 8368 rd = doc.querySelector(`div.roundedbox > table > caption.boxheader + tr + tr + tr > td > a[href^=/reviews/]`); 8369 assert(rd !is null); 8370 assert(rd.href == "/reviews/8832"); 8371 } 8372 8373 unittest { 8374 try { 8375 auto doc = new XmlDocument("<testxmlns:foo=\"/\"></test>"); 8376 assert(0); 8377 } catch(Exception e) { 8378 // good; it should throw an exception, not an error. 8379 } 8380 } 8381 8382 unittest { 8383 // toPrettyString is not stable, but these are some best-effort attempts 8384 // despite these being in a test, I might change these anyway! 8385 assert(Element.make("a").toPrettyString == "<a></a>"); 8386 assert(Element.make("a", "").toPrettyString(false, 0, " ") == "<a></a>"); 8387 assert(Element.make("a", " ").toPrettyString(false, 0, " ") == "<a> </a>");//, Element.make("a", " ").toPrettyString(false, 0, " ")); 8388 assert(Element.make("a", "b").toPrettyString == "<a>b</a>"); 8389 assert(Element.make("a", "b").toPrettyString(false, 0, "") == "<a>b</a>"); 8390 8391 { 8392 auto document = new Document("<html><body><p>hello <a href=\"world\">world</a></p></body></html>"); 8393 auto pretty = document.toPrettyString(false, 0, " "); 8394 assert(pretty == 8395 `<!DOCTYPE html> 8396 <html> 8397 <body> 8398 <p>hello <a href="world">world</a></p> 8399 </body> 8400 </html>`, pretty); 8401 } 8402 8403 { 8404 auto document = new XmlDocument("<html><body><p>hello <a href=\"world\">world</a></p></body></html>"); 8405 assert(document.toPrettyString(false, 0, " ") == 8406 `<?xml version="1.0" encoding="UTF-8"?> 8407 <html> 8408 <body> 8409 <p> 8410 hello 8411 <a href="world">world</a> 8412 </p> 8413 </body> 8414 </html>`); 8415 } 8416 8417 foreach(test; [ 8418 "<a att=\"http://ele\"><b><ele1>Hello</ele1>\n <c>\n <d>\n <ele2>How are you?</ele2>\n </d>\n <e>\n <ele3>Good & you?</ele3>\n </e>\n </c>\n </b>\n</a>", 8419 "<a att=\"http://ele\"><b><ele1>Hello</ele1><c><d><ele2>How are you?</ele2></d><e><ele3>Good & you?</ele3></e></c></b></a>", 8420 ] ) 8421 { 8422 auto document = new XmlDocument(test); 8423 assert(document.root.toPrettyString(false, 0, " ") == "<a att=\"http://ele\">\n <b>\n <ele1>Hello</ele1>\n <c>\n <d>\n <ele2>How are you?</ele2>\n </d>\n <e>\n <ele3>Good & you?</ele3>\n </e>\n </c>\n </b>\n</a>"); 8424 assert(document.toPrettyString(false, 0, " ") == "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<a att=\"http://ele\">\n <b>\n <ele1>Hello</ele1>\n <c>\n <d>\n <ele2>How are you?</ele2>\n </d>\n <e>\n <ele3>Good & you?</ele3>\n </e>\n </c>\n </b>\n</a>"); 8425 auto omg = document.root; 8426 omg.parent_ = null; 8427 assert(omg.toPrettyString(false, 0, " ") == "<a att=\"http://ele\">\n <b>\n <ele1>Hello</ele1>\n <c>\n <d>\n <ele2>How are you?</ele2>\n </d>\n <e>\n <ele3>Good & you?</ele3>\n </e>\n </c>\n </b>\n</a>"); 8428 } 8429 8430 { 8431 auto document = new XmlDocument(`<a><b>toto</b><c></c></a>`); 8432 assert(document.root.toPrettyString(false, 0, null) == `<a><b>toto</b><c></c></a>`); 8433 assert(document.root.toPrettyString(false, 0, " ") == `<a> 8434 <b>toto</b> 8435 <c></c> 8436 </a>`); 8437 } 8438 8439 { 8440 auto str = `<!DOCTYPE html> 8441 <html> 8442 <head> 8443 <title>Test</title> 8444 </head> 8445 <body> 8446 <p>Hello there</p> 8447 <p>I like <a href="">Links</a></p> 8448 <div> 8449 this is indented since there's a block inside 8450 <p>this is the block</p> 8451 and this gets its own line 8452 </div> 8453 </body> 8454 </html>`; 8455 auto doc = new Document(str, true, true); 8456 assert(doc.toPrettyString == str); 8457 } 8458 8459 } 8460 8461 unittest { 8462 auto document = new Document("broken"); // just ensuring it doesn't crash 8463 } 8464 8465 /* 8466 Copyright: Adam D. Ruppe, 2010 - 2022 8467 License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>. 8468 Authors: Adam D. Ruppe, with contributions by Nick Sabalausky, Trass3r, and ketmar among others 8469 8470 Copyright Adam D. Ruppe 2010-2022. 8471 Distributed under the Boost Software License, Version 1.0. 8472 (See accompanying file LICENSE_1_0.txt or copy at 8473 http://www.boost.org/LICENSE_1_0.txt) 8474 */ 8475 8476