1 /// This module defines the old HTML syntax for Elemi. 2 /// 3 /// For the new syntax, see [elemi.generator](elemi.generator.html). 4 /// 5 /// HTML generation in Elemi is based on Elemi's [XML generator](elemi.xml.html). Practically 6 /// speaking, the HTML layer just auto-detects 7 /// [void elements](https://developer.mozilla.org/en-US/docs/Glossary/Void_element). 8 /// As a consequence, Elemi-generated HTML documents are also valid XHTML or XML. 9 /// 10 /// $(B Elemi can be learned by [#examples|example]!) 11 module elemi.html; 12 13 /// The `elem` template is your key to Elemi's HTML generation. It can be used to create elements, 14 /// specify attributes, and add contents. 15 pure @safe unittest { 16 auto e = elem!"p"("Hello, World!"); 17 // The template argument ("p" above) specifies element type 18 19 assert(e == "<p>Hello, World!</p>"); 20 } 21 22 /// You can nest other elements, text — mix and match. 23 pure @safe unittest { 24 auto e = elem!"p"( 25 "Hello, ", 26 elem!"strong"("World"), 27 "!", 28 ); 29 30 assert(e == "<p>Hello, <strong>World</strong>!</p>"); 31 } 32 33 /// Add attributes with `attr`: 34 pure @safe unittest { 35 auto e = elem!"input"( 36 attr("type") = "text", 37 attr("name") = "name", 38 attr("placeholder") = "Your name…", 39 ); 40 41 assert(e == `<input type="text" name="name" placeholder="Your name…"/>`); 42 } 43 44 /// Elemi is designed to work with dynamic content, so it escapes all input automatically. 45 /// 46 /// Warning: [javascript](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/javascript) 47 /// schema URLs are $(B NOT) escaped! If you want your users to specify link targets, make sure 48 /// to block all `javascript:` links. 49 pure @safe unittest { 50 auto e = elem!"div"( 51 attr("data-text") = `No jail">break`, 52 "<script>alert('Fooled!')</script>", 53 ); 54 55 assert(e == `<div data-text="No jail">break">` 56 ~ `<script>` 57 ~ `alert('Fooled!')` 58 ~ `</script>` 59 ~ `</div>`); 60 } 61 62 /// Append attributes, children, text, at runtime. 63 pure @safe unittest { 64 auto e = elem!"div"(); 65 e ~= attr("class") = "one"; 66 e ~= elem!"span"("Two"); 67 e ~= "Three"; 68 69 assert(e == `<div class="one">` 70 ~ `<span>Two</span>` 71 ~ `Three` 72 ~ `</div>`); 73 } 74 75 /// If you need to group a few elements together, you can do it with `elems`. 76 pure @safe unittest { 77 auto child = elems( 78 "Hello, ", 79 elemX!"strong"("World"), 80 ); 81 auto parent = elem!"div"( 82 child, 83 ); 84 85 assert(child == "Hello, <strong>World</strong>"); 86 assert(parent == "<div>Hello, <strong>World</strong></div>"); 87 } 88 89 /// If combined with a fresh compiler, Elemi also supports interpolated strings. 90 static if (withInterpolation) 91 pure @safe unittest{ 92 auto e = elem!"div"( 93 attr("data-expression") = i"1+2 = $(1+2)", 94 i"1+2 is $(1+2)", 95 ); 96 97 assert(e == `<div data-expression="1+2 = 3">1+2 is 3</div>`); 98 } 99 100 import std.meta; 101 import std.range; 102 import std.traits; 103 104 import elemi.xml; 105 import elemi.internal; 106 import elemi.generator; 107 108 public { 109 110 import elemi.attribute; 111 import elemi.element; 112 113 } 114 115 // Magic elem alias 116 alias elem = elemH; 117 alias add = addH; 118 119 /// Create a HTML element. This template wraps `elemX`, implementing support for HTML void tags. 120 /// 121 /// Examples for this module [elemi.html](elemi.html.html) cover its most important features. 122 /// 123 /// Params: 124 /// name = Name of the element. 125 /// Ts = Compile-time arguments to pass to `elemX`. Discouraged. 126 /// args = Arguments to pass to `elemX`. Mix and match any from this list: $(LIST 127 /// * Pass an [Element] created by this function to add as a child. 128 /// * Any [Attribute] given will be added to the element's attribute list. 129 /// * Text content can be specified with a [string]. 130 /// ) 131 /// Returns: 132 /// An [Element] instance, which can implicitly be casted to string. 133 template elemH(string name, Ts...) { 134 135 Element elemH(Args...)(Args args) { 136 137 enum tag = makeHTMLTag(name); 138 139 return elemX!(tag, Ts)(args); 140 141 } 142 143 } 144 145 /// Add a new node as a child of this node. 146 /// Returns: This node, to allow chaining. 147 Element addH(Ts...)(ref Element parent, Ts args) 148 if (allSatisfy!(isType, Ts)) { 149 150 parent ~= args; 151 return parent; 152 153 } 154 155 /// ditto 156 Element addH(Ts...)(Element parent, Ts args) 157 if (allSatisfy!(isType, Ts)) { 158 159 parent ~= args; 160 return parent; 161 162 } 163 164 /// ditto 165 template addH(Ts...) 166 if (Ts.length != 0) { 167 168 Element addH(Args...)(ref Element parent, Args args) { 169 170 parent ~= elemH!Ts(args); 171 return parent; 172 173 } 174 175 Element addH(Args...)(Element parent, Args args) { 176 177 parent ~= elemH!Ts(args); 178 return parent; 179 180 } 181 182 } 183 184 /// A [void element](https://developer.mozilla.org/en-US/docs/Glossary/Void_element) in HTML 185 /// accepts no children, and has no closing tag. Contrast for example, the self-closing `<input/>` 186 /// with the usual `<div></div>` — no `</input>` tag follows. 187 /// 188 /// Correctly generating or omitting end tags is obligatory for HTML5. 189 /// 190 /// Returns: 191 /// True, if the given tag name represents a HTML5 self-closing tag. 192 bool isVoidTag(string tag) pure @safe { 193 194 switch (tag) { 195 196 // Void element 197 case "area", "base", "br", "col", "command", "embed", "hr", "img", "input": 198 case "keygen", "link", "meta", "param", "source", "track", "wbr": 199 200 return true; 201 202 // Containers 203 default: 204 205 return false; 206 207 } 208 209 } 210 211 /// If the given tag is a void tag, make it a self-closing. 212 private string makeHTMLTag(string tag) pure @safe { 213 214 assert(tag.length && tag[$-1] != '/', "Self-closing tags are applied automatically when working with html, please" 215 ~ " remove the slash."); 216 217 return isVoidTag(tag) ? tag ~ "/" : tag; 218 219 }