1 /// This module provides syntax for generating HTML and XML documents from inside of D. 2 /// It supports embedded control flow (`if`, `foreach`, etc) and writing to output ranges. 3 /// 4 /// $(B See the [#examples|examples] to get started!) 5 /// 6 /// Note that the generator, at the moment, only supports HTML generation. While its output should 7 /// be a valid XML document, it is unable to create tags other than HTML's own. The old 8 /// [XML API](elemi.xml.html) should still be suitable for use with XML. 9 /// 10 /// History: $(LIST 11 /// * Introduced in Elemi 1.4.0 12 /// ) 13 module elemi.generator; 14 15 /// The [buildHTML] function is the entrypoint to generating HTML. Connect it to a HTML source: 16 /// a function to fill the HTML document with content. 17 @safe unittest { 18 string output = buildHTML() ~ (html) { 19 20 }; 21 } 22 23 /// Elements are created using a tilde, followed by curly braces. This syntax is called an 24 /// $(B element block). 25 @safe unittest { 26 string output = buildHTML() ~ (html) { 27 html.div ~ { 28 html.span ~ { }; 29 }; 30 }; 31 assert(output == "<div><span></span></div>"); 32 } 33 34 /// Instead of a block, you can follow an element with a string to specify text content. 35 @safe unittest { 36 string output = buildHTML() ~ (html) { 37 html.p ~ "Hello, World!"; 38 }; 39 assert(output == "<p>Hello, World!</p>"); 40 } 41 42 /// You can add attributes to an element using the [Tag.attr] method. 43 @safe unittest { 44 string output = buildHTML() ~ (html) { 45 html.a.attr("href", "https://example.com") ~ "Visit my website!"; 46 }; 47 assert(output == `<a href="https://example.com">Visit my website!</a>`); 48 } 49 50 /// Common HTML attributes are available through specialized methods. 51 @safe unittest { 52 string output = buildHTML() ~ (html) { 53 html.div.id("my-div") ~ { }; 54 html.div.classes("one", "two", "three") ~ { }; 55 html.a.href("https://example.com") ~ { }; 56 }; 57 assert(output == `<div id="my-div"></div>` 58 ~ `<div class="one two three"></div>` 59 ~ `<a href="https://example.com"></a>`); 60 } 61 62 /// Use control flow statements like `if` and `foreach` to generate HTML with code. 63 @safe unittest { 64 string output = buildHTML() ~ (html) { 65 html.ul ~ { 66 foreach (item; ["1", "2", "3"]) { 67 html.li ~ item; 68 } 69 }; 70 }; 71 assert(output == `<ul><li>1</li><li>2</li><li>3</li></ul>`); 72 } 73 74 /// Omit the tag name to append text directly. 75 @safe unittest { 76 string output = buildHTML() ~ (html) { 77 html ~ "Hello, "; 78 html.strong ~ "World!"; 79 }; 80 assert(output == "Hello, <strong>World!</strong>"); 81 } 82 83 /// Keep your document safe from XSS injections: all HTML content is escaped automatically. 84 /// 85 /// Warning: Elemi will $(B not) escape 86 /// [javascript links](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/javascript). 87 /// If you let your users input URLs, make sure to block the `javascript:` schema. 88 @safe unittest { 89 string output = buildHTML() ~ (html) { 90 html ~ "<script>alert('fool!')</script>"; 91 html.input.attr("value", "\" can't quit me") ~ { }; 92 html.div.classes("classes are <no exception>") ~ { }; 93 }; 94 95 assert(output == `<script>alert('fool!')</script>` 96 ~ `<input value="" can't quit me"/>` 97 ~ `<div class="classes are <no exception>"></div>`); 98 } 99 100 static if (withInterpolation) { 101 /// Interpolated expression strings, aka istrings, are supported. 102 unittest { 103 string output = buildHTML() ~ (html) { 104 const user = "<user>"; 105 html ~ i"Hello $(user)"; 106 html.div ~ i"Hello $(user)"; 107 html.div.attr("name", i"$(user)") ~ { }; 108 html.a.href(i"https://example.com/$(user)") ~ { }; 109 html.a 110 .classes("button", "special") 111 .href("https://example.com/") ~ { }; 112 }; 113 assert(output == `Hello <user>` 114 ~ `<div>Hello <user></div>` 115 ~ `<div name="<user>"></div>` 116 ~ `<a href="https://example.com/<user>"></a>` 117 ~ `<a class="button special" href="https://example.com/"></a>`); 118 } 119 } 120 121 static if (withInterpolation) { 122 123 /// This syntax can be mixed with elements generated by the old syntax. 124 @safe unittest { 125 string output = buildHTML() ~ (html) { 126 html ~ elem!"span"(); 127 html.div ~ elem!"span"(); 128 html ~ i"$(elem!"span"())"; 129 }; 130 assert(output == `<span></span>` 131 ~ `<div><span></span></div>` 132 ~ `<span></span>`); 133 } 134 135 } 136 137 /// Elemi works at compile time! 138 @safe unittest { 139 enum output = buildHTML ~ (html) { 140 html.div ~ { 141 html.p ~ "Hello, World!"; 142 }; 143 }; 144 assert(output == "<div><p>Hello, World!</p></div>"); 145 } 146 147 148 @safe unittest { 149 string output = buildHTML() ~ (html) { 150 html ~ elem!"p"("Hello, World!"); 151 }; 152 153 assert(output == "<p>Hello, World!</p>"); 154 } 155 156 @safe unittest { 157 // const(edgecase) 158 const element = elem!"p"("Hello, World!"); 159 string output = buildHTML() ~ (html) { 160 html ~ element; 161 html ~ i" with $(element)"; 162 }; 163 164 assert(output == "<p>Hello, World!</p> with <p>Hello, World!</p>"); 165 } 166 167 @safe unittest { 168 // buildHTML() ~ null should work 169 void delegate(HTML) @safe htmlBuilder; 170 void delegate(HTML) @safe builder; 171 172 string output1 = buildHTML() ~ htmlBuilder; 173 assert(output1 == ""); 174 175 string output2 = buildHTML() ~ (html) { 176 html ~ builder; 177 }; 178 assert(output2 == ""); 179 180 string output3 = buildHTML ~ builder; 181 assert(output3 == ""); 182 183 } 184 185 import std.meta; 186 import std.range; 187 import std.string; 188 import std.functional; 189 190 import elemi.html; 191 import elemi.element; 192 import elemi.internal; 193 import elemi.attribute; 194 195 static if (__traits(compiles, { import core.attribute : mustuse; })) { 196 import core.attribute : mustuse; 197 } 198 else { 199 alias mustuse = AliasSeq!(); 200 } 201 202 @safe: 203 204 /// Generic XML or HTML tag. 205 @mustuse 206 struct Tag { 207 208 /// Output target for this tag; XML/HTML code will be written to this destination. 209 DocumentOutput output; 210 211 /// Name of the XML or HTML tag, for example `book` for `<book>` or `div` for `<div></div>`. 212 string tagName; 213 214 /// If true, this is a "self-closing" tag. No child nodes or text can be added. 215 bool isSelfClosing; 216 217 /// The tag can be marked as self-closing so it does not generate content nor an end tag. 218 unittest { 219 auto normal = buildHTML() ~ (html) { 220 Tag(html, "a") ~ { }; 221 }; 222 auto selfClosing = buildHTML() ~ (html) { 223 Tag(html, "a").makeSelfClosing() ~ { }; 224 }; 225 226 assert(normal == "<a></a>"); 227 assert(selfClosing == "<a/>"); 228 } 229 230 unittest { 231 auto normal = buildHTML() ~ (html) { 232 Tag(html, "a").attr("k", "k") ~ { }; 233 }; 234 auto selfClosing = buildHTML() ~ (html) { 235 Tag(html, "a").attr("k", "k").makeSelfClosing() ~ { }; 236 }; 237 238 assert(normal == `<a k="k"></a>`); 239 assert(selfClosing == `<a k="k"/>`); 240 } 241 242 /// True if an attribute has been added to this tag. 243 /// 244 /// This changes whether the whole opening tag will be added when content starts (no 245 /// attributes), or just the right bracket (with attributes). 246 bool withAttributes; 247 248 alias output this; 249 250 /// Returns: 251 /// The same tag, but marked using [withAttributes]. This should be used to return from 252 /// attribute-adding methods. 253 Tag attributed() const @safe { 254 return Tag(output, tagName, isSelfClosing, true); 255 } 256 257 /// Returns: 258 /// This tag, but edited to be self-closing. 259 Tag makeSelfClosing() const @safe { 260 return Tag(output, tagName, true, withAttributes); 261 } 262 263 void opBinary(string op : "~")(typeof(null)) @safe { 264 begin(); 265 end(); 266 } 267 268 void opBinary(string op : "~", T)(T content) @safe { 269 begin(); 270 if (!isSelfClosing) { 271 static if (is(T : const Element)) { 272 pushElementMarkup(content); 273 } 274 else { 275 pushElementText(content); 276 } 277 end(); 278 } 279 } 280 281 static if (withInterpolation) 282 void opBinary(string op : "~", Ts...)(InterpolationHeader, Ts text) { 283 begin(); 284 if (!isSelfClosing) { 285 pushElementText(text); 286 end(); 287 } 288 } 289 290 void opBinary(string op : "~")(void delegate() @safe builder) @safe { 291 begin(); 292 if (!isSelfClosing && builder !is null) { 293 builder(); 294 } 295 end(); 296 } 297 298 void opBinary(string op : "~")(void delegate() @system builder) @system { 299 begin(); 300 if (!isSelfClosing && builder !is null) { 301 builder(); 302 } 303 end(); 304 } 305 306 /// Specify attributes using strings as key-value pairs. Interpolated strings are also 307 /// accepted. 308 /// 309 /// Note: 310 /// At present, when using interpolated strings, the equals sign MUST be part of the 311 /// literal, and cannot be interpolated. 312 /// 313 /// An expression like this is allowed: `i"$(key)=$(value)"`. However, if the 314 /// equal sign is interpolated, it will produce malformed code. Thus, a piece like 315 /// `i"$("key=value")"` would fail. 316 Tag opIndex(Ts...)(Ts args) @safe { 317 import std.algorithm : findSplit, filter, joiner; 318 319 bool isValue; 320 321 foreach (i, arg; args) { 322 alias A = typeof(arg); 323 static if (isInterpolation!(i, Ts)) { 324 static if (is(A == InterpolationHeader)) { 325 beginAttributes(); 326 withAttributes = true; 327 isValue = false; 328 pushElementMarkup(" "); 329 } 330 else static if (is(A == InterpolationFooter)) { 331 if (isValue) { 332 pushElementMarkup(`"`); 333 } 334 else { 335 pushElementMarkup(`=""`); 336 } 337 } 338 else static if (is(A == InterpolatedLiteral!text, string text)) { 339 auto pair = text.findSplit("="); 340 if (!isValue && pair) { 341 pushElementText(pair[0]); 342 pushElementMarkup(`="`); 343 pushElementText(pair[2]); 344 isValue = true; 345 } 346 else { 347 pushElementText(text); 348 } 349 } 350 else static if (isInputRange!A && is(ElementType!A : string)) { 351 pushElementText(arg 352 .filter!(a => a != "") 353 .joiner(" ")); 354 } 355 else { 356 pushElementText(arg); 357 } 358 } 359 else static if (is(A == string)) { 360 auto pair = arg.findSplit("="); 361 this = attr(pair[0], pair[2]); 362 } 363 else static assert(false, "Unsupported argument type " ~ A.stringof); 364 } 365 366 return attributed; 367 } 368 369 /// Each string (or istring) defines a single attribute. The first equals-sign in the string 370 /// separates the key from the value: 371 unittest { 372 string output = buildHTML() ~ (html) { 373 html.div["id=mydiv", "class=apple orange"] ~ { }; 374 }; 375 assert(output == `<div id="mydiv" class="apple orange"></div>`); 376 } 377 378 /// The equals sign is optional. If missing, the value will be empty. 379 unittest { 380 string output = buildHTML() ~ (html) { 381 html.input["type=checkbox", "checked"] ~ { }; 382 }; 383 assert(output == `<input type="checkbox" checked=""/>`); 384 } 385 386 /// Interpolated strings are supported. Ranges of strings are automatically joined with spaces. 387 static if (withInterpolation) { 388 unittest { 389 int number = 1; 390 string[] classes = ["two", "three", "four"]; 391 392 string output = buildHTML() ~ (html) { 393 html.div[i"id=item-$(number)", i"class=one $(classes) five", "checked"] ~ { }; 394 }; 395 assert(output == `<div id="item-1" class="one two three four five" checked=""></div>`); 396 } 397 398 /// Attribute names can also be interpolated. 399 unittest { 400 string key = "data-foo"; 401 string output1 = buildHTML() ~ (html) { 402 html.div[i"$(key)=value"] ~ { }; 403 }; 404 assert(output1 == `<div data-foo="value"></div>`); 405 406 string state = "checked"; 407 string output2 = buildHTML() ~ (html) { 408 html.input[i"$(state)", "type=checkbox"] ~ { }; 409 }; 410 assert(output2 == `<input checked="" type="checkbox"/>`); 411 } 412 413 unittest { 414 string key = "data"; 415 string output = buildHTML() ~ (html) { 416 html.div[i"$(key)=$(key)=$(key)"] ~ { }; 417 }; 418 assert(output == `<div data="data=data"></div>`); 419 } 420 421 static assert( __traits(compiles, Tag()[i"$(1)"])); 422 static assert(!__traits(compiles, Tag()[1])); 423 } 424 425 /// Add an attribute to the element. 426 /// Params: 427 /// name = Name of the attribute. 428 /// value = Value for the attribute. Supports istrings. 429 /// Returns: 430 /// Tag builder. 431 Tag attr(string name, string value) @safe { 432 return attr(Attribute(name, value)); 433 } 434 435 /// ditto 436 static if (withInterpolation) { 437 Tag attr(Ts...)(string name, InterpolationHeader, Ts value) @safe { 438 beginAttributes(); 439 pushElementMarkup(" "); 440 pushElementMarkup(name); 441 pushElementMarkup(`="`); 442 pushElementText(value); 443 pushElementMarkup(`"`); 444 return attributed; 445 } 446 } 447 448 /// Add a prepared set of attributes to the element. 449 /// Params: 450 /// attributes = Attributes to add to the element. 451 /// Returns: 452 /// Tag builder. 453 Tag attr(Attribute[] attributes...) @safe { 454 beginAttributes(); 455 foreach (attribute; attributes) { 456 pushElementMarkup(" "); 457 pushElementMarkup(attribute.name); 458 pushElementMarkup(`="`); 459 pushElementText(attribute.value); 460 pushElementMarkup(`"`); 461 } 462 return attributed; 463 } 464 465 void begin() @safe { 466 if (tagName is null) return; 467 if (!withAttributes) { 468 pushElementMarkup("<"); 469 pushElementMarkup(tagName); 470 } 471 if (isSelfClosing) { 472 pushElementMarkup("/>"); 473 } 474 else { 475 pushElementMarkup(">"); 476 } 477 } 478 479 void beginAttributes() @safe { 480 if (tagName is null) return; 481 if (!withAttributes) { 482 pushElementMarkup("<"); 483 pushElementMarkup(tagName); 484 } 485 } 486 487 void end() @safe { 488 if (tagName is null) return; 489 if (!isSelfClosing) { 490 pushElementMarkup("</"); 491 pushElementMarkup(tagName); 492 pushElementMarkup(">"); 493 } 494 } 495 496 } 497 498 /// A wrapper over a function, with additional methods added to support XML/HTML generation. 499 struct DocumentOutput { 500 501 /// Function to call to append a string fragment to the output. 502 void delegate(string fragment) @safe elementOutput; 503 504 /// Append a string or [Element] to the stream. 505 /// Params: 506 /// rhs = `string` or `Element` to append. 507 void opBinary(string op : "~", T : string)(T rhs) @safe { 508 static if (is(T : const Element)) { 509 pushElementMarkup(rhs); 510 } 511 else { 512 pushElementText(rhs); 513 } 514 } 515 516 static if (withInterpolation) { 517 /// Write an [istring](https://dlang.org/spec/istring.html) to the stream. 518 /// Params: 519 /// text = Text to write. 520 void opBinary(string op : "~", Ts...)(InterpolationHeader, Ts text) { 521 pushElementText(text); 522 } 523 } 524 525 /// Low-level function to write elements into current document context. Raw markup can be used, 526 /// i.e. ("<b>Hi</b>")` will include unescaped HTML code. 527 /// 528 /// If escaping is desired, try [pushElementText]. 529 /// 530 /// Params: 531 /// content = Markup to output. 532 void pushElementMarkup(string content) @safe { 533 assert(elementOutput !is null, 534 "No Elemi context is currently active. Try prepending the document with " 535 ~ "`buildDocument() ~`."); 536 elementOutput(content); 537 } 538 539 /// Low-level function to write escaped text. 540 /// 541 /// Any of the characters `<` (less than sign), `>` (greater than sign), `&` (ampersand), 542 /// `"` (quote mark), or `'` (apostrophe) will be replaced with the corresponding XML escape 543 /// code. 544 /// 545 /// Params: 546 /// content = Content to write. Interpolated expression strings (istrings) are supported. 547 void pushElementText(string content) { 548 while (!content.empty) { 549 const nextMarkup = content.indexOfAny(`<>&"'`); 550 551 // No markup remains to be escaped 552 if (nextMarkup == -1) { 553 pushElementMarkup(content); 554 return; 555 } 556 557 // Escape the character 558 else { 559 pushElementMarkup(content[0 .. nextMarkup]); 560 pushElementMarkup(content[nextMarkup].escapeXML); 561 content = content[nextMarkup + 1 .. $]; 562 } 563 564 } 565 } 566 567 /// ditto 568 void pushElementText(Ts...)(Ts content) { 569 570 import std.format.write; 571 572 auto writer = EscapingElementWriter(this); 573 574 foreach (item; content) { 575 static if (is(typeof(item) : Element)) { 576 pushElementMarkup(item); 577 } 578 else { 579 formattedWrite!"%s"(writer, item); 580 } 581 } 582 583 } 584 585 /// This output range writes escapes the text it writes. 586 private struct EscapingElementWriter { 587 588 DocumentOutput output; 589 590 void put(char content) { 591 if (auto escaped = escapeXML(content)) { 592 output.pushElementMarkup(escaped); 593 } 594 else { 595 immutable(char)[1] c = content; 596 output.pushElementMarkup(c[]); 597 } 598 } 599 600 void put(string content) { 601 output.pushElementText(content); 602 } 603 604 } 605 606 } 607 608 /// Escape an ASCII character using HTML escape codes. 609 /// Returns: 610 /// A corresponding HTML escape code, or null if there isn't one. 611 private string escapeXML(char ch) { 612 switch (ch) { 613 case '<': return "<"; 614 case '>': return ">"; 615 case '&': return "&"; 616 case '"': return """; 617 case '\'': return "'"; 618 default: return null; 619 } 620 } 621 622 /// HTML tag builder. 623 /// 624 /// Params: 625 /// name = Name of the tag, for example `div`. 626 struct HTMLTag(string name) { 627 628 /// True if the tag is one of HTML's 629 /// [void elements](https://developer.mozilla.org/en-US/docs/Glossary/Void_element). 630 /// Void elements do not have an end tag, and thus, cannot have child nodes. 631 enum isVoidTag = name == "area" 632 || name == "base" 633 || name == "br" 634 || name == "col" 635 || name == "command" 636 || name == "embed" 637 || name == "hr" 638 || name == "img" 639 || name == "input" 640 || name == "keygen" 641 || name == "link" 642 || name == "meta" 643 || name == "param" 644 || name == "source" 645 || name == "track" 646 || name == "wbr"; 647 648 /// XML tag wrapped by this struct; tag this struct corresponds to. 649 Tag tag; 650 651 alias tag this; 652 653 /// Create a new instance of this tag for the specified document output. 654 /// Params: 655 /// output = Document output instance this generator should write to. 656 this(DocumentOutput output) { 657 this.tag = Tag(output, name, isVoidTag); 658 } 659 660 /// Pass any other tag as an instance of this tag. 661 /// Params: 662 /// tag = XML or HTML tag to wrap. 663 this(Tag tag) { 664 this.tag = tag; 665 } 666 667 /// Add an attribute to this element. This function wraps [Tag.attr]. 668 HTMLTag attr(Ts...)(Ts args) { 669 return HTMLTag( 670 tag.attr(args)); 671 } 672 673 /// Returns: 674 /// The same tag, but marked using [Tag.withAttributes]. This should be used to return 675 /// from attribute-adding methods. 676 HTMLTag attributed() { 677 return HTMLTag( 678 tag.attributed()); 679 } 680 681 /// Add an `id` attribute to the HTML tag. 682 /// Params: 683 /// value = Value to use for the attribute. Supports istrings. 684 /// Returns: 685 /// Tag builder, for chaining. 686 HTMLTag id(string value) @safe { 687 return attr("id", value); 688 } 689 690 static if (withInterpolation) { 691 /// ditto 692 HTMLTag id(Ts...)(InterpolationHeader header, Ts value) @safe { 693 return attr("id", header, value); 694 } 695 } 696 697 /// Add a `class` attribute to a HTML tag from an array of strings. 698 /// Params: 699 /// values = Classes to write, as an array. This array will be joined with spaces. 700 /// Returns: 701 /// Tag builder, for chaining. 702 HTMLTag classes(string[] values...) @safe { 703 beginAttributes(); 704 pushElementMarkup(` class="`); 705 foreach (i, value; values) { 706 if (i) pushElementMarkup(" "); 707 pushElementText(value); 708 } 709 pushElementMarkup(`"`); 710 return attributed; 711 } 712 713 /// Set the "href" attribute for an `<a>` element. 714 /// Params: 715 /// value = Value to use for the attribute. Supports istrings. 716 /// Returns: 717 /// A tag builder. 718 HTMLTag href(string value) @safe { 719 return attr("href", value); 720 } 721 722 static if (withInterpolation) { 723 /// ditto 724 HTMLTag href(Ts...)(InterpolationHeader header, Ts value) @safe { 725 return attr("href", header, value); 726 } 727 } 728 729 } 730 731 /// Write HTML elements to an output range. 732 /// Params: 733 /// range = Output range to write the output to. If not given, the builder will return 734 /// a string. 735 /// Returns: 736 /// A struct that accepts an element block `~ (html) { }` and writes the content to the output 737 /// range. 738 /// See_Also: 739 /// [HTML], the stream structure used to output data. 740 HTML buildHTML(T)(ref T range) 741 if (isOutputRange!(T, char)) 742 do { 743 return HTML( 744 DocumentOutput(fragment => put(range, fragment))); 745 } 746 747 /// Write HTML elements to a string or [Element]. 748 /// 749 /// Returns: 750 /// A struct that accepts an element block `~ (html) { }` and writes the content to a string. 751 /// 752 /// The resulting content will be wrapped in an [Element], making it compatible with the 753 /// [old elemi API](elemi.html.html). Note that metadata is not preserved, so adding 754 /// attributes or child nodes from `Element` will not be possible. 755 /// See_Also: 756 /// [HTML], the stream structure used to output data. 757 TextHTML buildHTML() @safe { 758 return TextHTML(); 759 } 760 761 /// If no arguments are specified, `buildHTML()` will output to a string. Under the hood, 762 /// it uses an [std.array.Appender]. 763 @safe unittest { 764 import std.array; 765 string stringOutput = buildHTML() ~ (html) { 766 html.p ~ "Hello!"; 767 }; 768 769 Appender!string rangeOutput; 770 buildHTML(rangeOutput) ~ (html) { 771 html.p ~ "Hello!"; 772 }; 773 assert(stringOutput == rangeOutput[]); 774 } 775 776 /// Supporting structure for [buildHTML]. 777 struct TextHTML { 778 import std.array; 779 780 Element opBinary(string op : "~")(void delegate(HTML) @system rhs) const @system { 781 Appender!string output; 782 if (rhs !is null) { 783 rhs( 784 HTML( 785 DocumentOutput(fragment => output ~= fragment))); 786 } 787 return elemTrusted(output[]); 788 } 789 790 Element opBinary(string op : "~")(void delegate(HTML) @safe rhs) const @safe { 791 Appender!string output; 792 if (rhs !is null) { 793 rhs( 794 HTML( 795 DocumentOutput(fragment => output ~= fragment))); 796 } 797 return elemTrusted(output[]); 798 } 799 800 } 801 802 /// A set of HTML tags to build documents with. 803 /// 804 /// This structure includes a set of methods for creating HTML tags. 805 struct HTML { 806 807 DocumentOutput documentOutput; 808 809 alias documentOutput this; 810 811 /// Write a string to the output stream. 812 /// Params: 813 /// rhs = String to write. 814 void opBinary(string op : "~", T : string)(T rhs) @safe { 815 documentOutput ~ rhs; 816 } 817 818 static if (withInterpolation) { 819 /// Write an [istring](https://dlang.org/spec/istring.html) to the output stream. 820 /// Params: 821 /// rhs = String to write. 822 void opBinary(string op : "~", Ts...)(InterpolationHeader, Ts text) { 823 pushElementText(text); 824 } 825 } 826 827 /// Pass self to a function. 828 /// Params: 829 /// rhs = The function to read the output. 830 void opBinary(string op : "~")(void delegate(HTML o) @system rhs) @system { 831 if (rhs !is null) { 832 rhs(this); 833 } 834 } 835 836 /// ditto 837 void opBinary(string op : "~")(void delegate(HTML o) @safe rhs) @safe { 838 if (rhs !is null) { 839 rhs(this); 840 } 841 } 842 843 @safe: 844 845 /// 846 HTMLTag!"a" a() { 847 return typeof(return)(this); 848 } 849 /// 850 HTMLTag!"abbr" abbr() { 851 return typeof(return)(this); 852 } 853 /// 854 HTMLTag!"acronym" acronym() { 855 return typeof(return)(this); 856 } 857 /// 858 HTMLTag!"address" address() { 859 return typeof(return)(this); 860 } 861 /// 862 HTMLTag!"area" area() { 863 return typeof(return)(this); 864 } 865 /// 866 HTMLTag!"article" article() { 867 return typeof(return)(this); 868 } 869 /// 870 HTMLTag!"aside" aside() { 871 return typeof(return)(this); 872 } 873 /// 874 HTMLTag!"audio" audio() { 875 return typeof(return)(this); 876 } 877 /// 878 HTMLTag!"b" b() { 879 return typeof(return)(this); 880 } 881 /// 882 HTMLTag!"base" base() { 883 return typeof(return)(this); 884 } 885 /// 886 HTMLTag!"bdi" bdi() { 887 return typeof(return)(this); 888 } 889 /// 890 HTMLTag!"bdo" bdo() { 891 return typeof(return)(this); 892 } 893 /// 894 HTMLTag!"big" big() { 895 return typeof(return)(this); 896 } 897 /// 898 HTMLTag!"blockquote" blockquote() { 899 return typeof(return)(this); 900 } 901 /// 902 HTMLTag!"body" body() { 903 return typeof(return)(this); 904 } 905 /// 906 HTMLTag!"br" br() { 907 return typeof(return)(this); 908 } 909 /// 910 HTMLTag!"button" button() { 911 return typeof(return)(this); 912 } 913 /// 914 HTMLTag!"canvas" canvas() { 915 return typeof(return)(this); 916 } 917 /// 918 HTMLTag!"caption" caption() { 919 return typeof(return)(this); 920 } 921 /// 922 HTMLTag!"center" center() { 923 return typeof(return)(this); 924 } 925 /// 926 HTMLTag!"cite" cite() { 927 return typeof(return)(this); 928 } 929 /// 930 HTMLTag!"code" code() { 931 return typeof(return)(this); 932 } 933 /// 934 HTMLTag!"col" col() { 935 return typeof(return)(this); 936 } 937 /// 938 HTMLTag!"colgroup" colgroup() { 939 return typeof(return)(this); 940 } 941 /// 942 HTMLTag!"command" command() { 943 return typeof(return)(this); 944 } 945 /// 946 HTMLTag!"data" data() { 947 return typeof(return)(this); 948 } 949 /// 950 HTMLTag!"datalist" datalist() { 951 return typeof(return)(this); 952 } 953 /// 954 HTMLTag!"dd" dd() { 955 return typeof(return)(this); 956 } 957 /// 958 HTMLTag!"del" del() { 959 return typeof(return)(this); 960 } 961 /// 962 HTMLTag!"details" details() { 963 return typeof(return)(this); 964 } 965 /// 966 HTMLTag!"dfn" dfn() { 967 return typeof(return)(this); 968 } 969 /// 970 HTMLTag!"dialog" dialog() { 971 return typeof(return)(this); 972 } 973 /// 974 HTMLTag!"dir" dir() { 975 return typeof(return)(this); 976 } 977 /// 978 HTMLTag!"div" div() { 979 return typeof(return)(this); 980 } 981 /// 982 HTMLTag!"dl" dl() { 983 return typeof(return)(this); 984 } 985 /// 986 HTMLTag!"dt" dt() { 987 return typeof(return)(this); 988 } 989 /// 990 HTMLTag!"em" em() { 991 return typeof(return)(this); 992 } 993 /// 994 HTMLTag!"embed" embed() { 995 return typeof(return)(this); 996 } 997 /// 998 HTMLTag!"fencedframe" fencedframe() { 999 return typeof(return)(this); 1000 } 1001 /// 1002 HTMLTag!"fieldset" fieldset() { 1003 return typeof(return)(this); 1004 } 1005 /// 1006 HTMLTag!"figcaption" figcaption() { 1007 return typeof(return)(this); 1008 } 1009 /// 1010 HTMLTag!"figure" figure() { 1011 return typeof(return)(this); 1012 } 1013 /// 1014 HTMLTag!"font" font() { 1015 return typeof(return)(this); 1016 } 1017 /// 1018 HTMLTag!"footer" footer() { 1019 return typeof(return)(this); 1020 } 1021 /// 1022 HTMLTag!"form" form() { 1023 return typeof(return)(this); 1024 } 1025 /// 1026 HTMLTag!"frame" frame() { 1027 return typeof(return)(this); 1028 } 1029 /// 1030 HTMLTag!"frameset" frameset() { 1031 return typeof(return)(this); 1032 } 1033 /// 1034 HTMLTag!"h1" h1() { 1035 return typeof(return)(this); 1036 } 1037 /// 1038 HTMLTag!"h2" h2() { 1039 return typeof(return)(this); 1040 } 1041 /// 1042 HTMLTag!"h3" h3() { 1043 return typeof(return)(this); 1044 } 1045 /// 1046 HTMLTag!"h4" h4() { 1047 return typeof(return)(this); 1048 } 1049 /// 1050 HTMLTag!"h5" h5() { 1051 return typeof(return)(this); 1052 } 1053 /// 1054 HTMLTag!"h6" h6() { 1055 return typeof(return)(this); 1056 } 1057 /// 1058 HTMLTag!"head" head() { 1059 return typeof(return)(this); 1060 } 1061 /// 1062 HTMLTag!"header" header() { 1063 return typeof(return)(this); 1064 } 1065 /// 1066 HTMLTag!"hgroup" hgroup() { 1067 return typeof(return)(this); 1068 } 1069 /// 1070 HTMLTag!"hr" hr() { 1071 return typeof(return)(this); 1072 } 1073 /// 1074 HTMLTag!"html" html() { 1075 return typeof(return)(this); 1076 } 1077 /// 1078 HTMLTag!"i" i() { 1079 return typeof(return)(this); 1080 } 1081 /// 1082 HTMLTag!"iframe" iframe() { 1083 return typeof(return)(this); 1084 } 1085 /// 1086 HTMLTag!"img" img() { 1087 return typeof(return)(this); 1088 } 1089 /// 1090 HTMLTag!"input" input() { 1091 return typeof(return)(this); 1092 } 1093 /// 1094 HTMLTag!"ins" ins() { 1095 return typeof(return)(this); 1096 } 1097 /// 1098 HTMLTag!"kbd" kbd() { 1099 return typeof(return)(this); 1100 } 1101 /// 1102 HTMLTag!"keygen" keygen() { 1103 return typeof(return)(this); 1104 } 1105 /// 1106 HTMLTag!"label" label() { 1107 return typeof(return)(this); 1108 } 1109 /// 1110 HTMLTag!"legend" legend() { 1111 return typeof(return)(this); 1112 } 1113 /// 1114 HTMLTag!"li" li() { 1115 return typeof(return)(this); 1116 } 1117 /// 1118 HTMLTag!"link" link() { 1119 return typeof(return)(this); 1120 } 1121 /// 1122 HTMLTag!"main" main() { 1123 return typeof(return)(this); 1124 } 1125 /// 1126 HTMLTag!"map" map() { 1127 return typeof(return)(this); 1128 } 1129 /// 1130 HTMLTag!"mark" mark() { 1131 return typeof(return)(this); 1132 } 1133 /// 1134 HTMLTag!"marquee" marquee() { 1135 return typeof(return)(this); 1136 } 1137 /// 1138 HTMLTag!"math" math() { 1139 return typeof(return)(this); 1140 } 1141 /// 1142 HTMLTag!"menu" menu() { 1143 return typeof(return)(this); 1144 } 1145 /// 1146 HTMLTag!"meta" meta() { 1147 return typeof(return)(this); 1148 } 1149 /// 1150 HTMLTag!"meter" meter() { 1151 return typeof(return)(this); 1152 } 1153 /// 1154 HTMLTag!"nav" nav() { 1155 return typeof(return)(this); 1156 } 1157 /// 1158 HTMLTag!"nobr" nobr() { 1159 return typeof(return)(this); 1160 } 1161 /// 1162 HTMLTag!"noembed" noembed() { 1163 return typeof(return)(this); 1164 } 1165 /// 1166 HTMLTag!"noframes" noframes() { 1167 return typeof(return)(this); 1168 } 1169 /// 1170 HTMLTag!"noscript" noscript() { 1171 return typeof(return)(this); 1172 } 1173 /// 1174 HTMLTag!"object" object() { 1175 return typeof(return)(this); 1176 } 1177 /// 1178 HTMLTag!"ol" ol() { 1179 return typeof(return)(this); 1180 } 1181 /// 1182 HTMLTag!"optgroup" optgroup() { 1183 return typeof(return)(this); 1184 } 1185 /// 1186 HTMLTag!"option" option() { 1187 return typeof(return)(this); 1188 } 1189 /// 1190 HTMLTag!"output" output() { 1191 return typeof(return)(this); 1192 } 1193 /// 1194 HTMLTag!"p" p() { 1195 return typeof(return)(this); 1196 } 1197 /// 1198 HTMLTag!"param" param() { 1199 return typeof(return)(this); 1200 } 1201 /// 1202 HTMLTag!"picture" picture() { 1203 return typeof(return)(this); 1204 } 1205 /// 1206 HTMLTag!"plaintext" plaintext() { 1207 return typeof(return)(this); 1208 } 1209 /// 1210 HTMLTag!"pre" pre() { 1211 return typeof(return)(this); 1212 } 1213 /// 1214 HTMLTag!"progress" progress() { 1215 return typeof(return)(this); 1216 } 1217 /// 1218 HTMLTag!"q" q() { 1219 return typeof(return)(this); 1220 } 1221 /// 1222 HTMLTag!"rb" rb() { 1223 return typeof(return)(this); 1224 } 1225 /// 1226 HTMLTag!"rp" rp() { 1227 return typeof(return)(this); 1228 } 1229 /// 1230 HTMLTag!"rt" rt() { 1231 return typeof(return)(this); 1232 } 1233 /// 1234 HTMLTag!"rtc" rtc() { 1235 return typeof(return)(this); 1236 } 1237 /// 1238 HTMLTag!"ruby" ruby() { 1239 return typeof(return)(this); 1240 } 1241 /// 1242 HTMLTag!"s" s() { 1243 return typeof(return)(this); 1244 } 1245 /// 1246 HTMLTag!"samp" samp() { 1247 return typeof(return)(this); 1248 } 1249 /// 1250 HTMLTag!"script" script() { 1251 return typeof(return)(this); 1252 } 1253 /// 1254 HTMLTag!"search" search() { 1255 return typeof(return)(this); 1256 } 1257 /// 1258 HTMLTag!"section" section() { 1259 return typeof(return)(this); 1260 } 1261 /// 1262 HTMLTag!"select" select() { 1263 return typeof(return)(this); 1264 } 1265 /// 1266 HTMLTag!"slot" slot() { 1267 return typeof(return)(this); 1268 } 1269 /// 1270 HTMLTag!"small" small() { 1271 return typeof(return)(this); 1272 } 1273 /// 1274 HTMLTag!"source" source() { 1275 return typeof(return)(this); 1276 } 1277 /// 1278 HTMLTag!"span" span() { 1279 return typeof(return)(this); 1280 } 1281 /// 1282 HTMLTag!"strike" strike() { 1283 return typeof(return)(this); 1284 } 1285 /// 1286 HTMLTag!"strong" strong() { 1287 return typeof(return)(this); 1288 } 1289 /// 1290 HTMLTag!"style" style() { 1291 return typeof(return)(this); 1292 } 1293 /// 1294 HTMLTag!"sub" sub() { 1295 return typeof(return)(this); 1296 } 1297 /// 1298 HTMLTag!"summary" summary() { 1299 return typeof(return)(this); 1300 } 1301 /// 1302 HTMLTag!"sup" sup() { 1303 return typeof(return)(this); 1304 } 1305 /// 1306 HTMLTag!"svg" svg() { 1307 return typeof(return)(this); 1308 } 1309 /// 1310 HTMLTag!"table" table() { 1311 return typeof(return)(this); 1312 } 1313 /// 1314 HTMLTag!"tbody" tbody() { 1315 return typeof(return)(this); 1316 } 1317 /// 1318 HTMLTag!"td" td() { 1319 return typeof(return)(this); 1320 } 1321 /// 1322 HTMLTag!"template" template_() { 1323 return typeof(return)(this); 1324 } 1325 /// 1326 HTMLTag!"textarea" textarea() { 1327 return typeof(return)(this); 1328 } 1329 /// 1330 HTMLTag!"tfoot" tfoot() { 1331 return typeof(return)(this); 1332 } 1333 /// 1334 HTMLTag!"th" th() { 1335 return typeof(return)(this); 1336 } 1337 /// 1338 HTMLTag!"thead" thead() { 1339 return typeof(return)(this); 1340 } 1341 /// 1342 HTMLTag!"time" time() { 1343 return typeof(return)(this); 1344 } 1345 /// 1346 HTMLTag!"title" title() { 1347 return typeof(return)(this); 1348 } 1349 /// 1350 HTMLTag!"tr" tr() { 1351 return typeof(return)(this); 1352 } 1353 /// 1354 HTMLTag!"track" track() { 1355 return typeof(return)(this); 1356 } 1357 /// 1358 HTMLTag!"tt" tt() { 1359 return typeof(return)(this); 1360 } 1361 /// 1362 HTMLTag!"u" u() { 1363 return typeof(return)(this); 1364 } 1365 /// 1366 HTMLTag!"ul" ul() { 1367 return typeof(return)(this); 1368 } 1369 /// 1370 HTMLTag!"var" var() { 1371 return typeof(return)(this); 1372 } 1373 /// 1374 HTMLTag!"video" video() { 1375 return typeof(return)(this); 1376 } 1377 /// 1378 HTMLTag!"wbr" wbr() { 1379 return typeof(return)(this); 1380 } 1381 /// 1382 HTMLTag!"xmp" xmp() { 1383 return typeof(return)(this); 1384 } 1385 1386 }