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&quot;&gt;break">`
56         ~ `&lt;script&gt;`
57         ~ `alert(&#39;Fooled!&#39;)`
58         ~ `&lt;/script&gt;`
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 }