1 module elemi;
2 
3 public {
4 
5     import elemi.xml;
6     import elemi.html;
7     import elemi.element;
8     import elemi.attribute;
9 
10 }
11 
12 alias elem = elemi.html.elem;
13 alias add = elemi.html.add;
14 
15 
16 ///
17 pure @safe unittest {
18 
19     // Compile-time empty type detection
20     assert(elem!"input" == "<input/>");
21     assert(elem!"hr" == "<hr/>");
22     assert(elem!"p" == "<p></p>");
23 
24     // Content
25     assert(elem!"p"("Hello, World!") == "<p>Hello, World!</p>");
26 
27     // Compile-time attributes — variant A
28     assert(
29 
30         elem!("a", [ "href": "about:blank", "title": "Destroy this page" ])("Hello, World!")
31 
32         == `<a href="about:blank" title="Destroy this page">Hello, World!</a>`
33 
34     );
35 
36     // Compile-time attributes — variant B
37     assert(
38 
39         elem!("a", q{
40             href="about:blank"
41             title="Destroy this page" })(
42             "Hello, World!"
43         )
44         == `<a href="about:blank" title="Destroy this page">Hello, World!</a>`
45 
46     );
47 
48     // Nesting and input sanitization
49     assert(
50 
51         elem!"div"(
52             elem!"p"("Hello, World!"),
53             "-> Sanitized"
54         )
55 
56         == "<div><p>Hello, World!</p>-&gt; Sanitized</div>"
57 
58     );
59 
60     // Sanitized user input in attributes
61     assert(
62         elem!"input"(
63             attr("type") = "text",
64             attr("value") = `"XSS!"`
65         ) == `<input type="text" value="&quot;XSS!&quot;"/>`
66     );
67 
68     assert(
69 
70         elem!"input"(["type": "text", "value": `"XSS!"`])
71         == `<input type="text" value="&quot;XSS!&quot;"/>`
72 
73     );
74     assert(
75         elem!("input", q{ type="text" })(["value": `"XSS!"`])
76         == `<input type="text" value="&quot;XSS!&quot;"/>`
77     );
78 
79     // Alternative method of nesting
80     assert(
81 
82         elem!("div", q{ style="background:#500" })
83             .add!"p"("Hello, World!")
84             .add("-> Sanitized")
85             .add(
86                 " and",
87                 " clear"
88             )
89 
90         == `<div style="background:#500"><p>Hello, World!</p>-&gt; Sanitized and clear</div>`
91 
92     );
93 
94     import std.range : repeat;
95 
96     // Adding elements by ranges
97     assert(
98         elem!"ul"(
99             "element".elem!"li".repeat(3)
100         )
101         == "<ul><li>element</li><li>element</li><li>element</li></ul>"
102 
103     );
104 
105     // Significant whitespace
106     assert(elem!"span"(" Foo ") == "<span> Foo </span>");
107 
108     // Also with tilde
109     auto myElem = elem!"div";
110     myElem ~= elem!"span"("Sample");
111     myElem ~= " ";
112     myElem ~= elem!"span"("Text");
113     myElem ~= attr("class") = "test";
114 
115     assert(
116         myElem == `<div class="test"><span>Sample</span> <span>Text</span></div>`
117     );
118 
119 }
120 
121 /// A general example page
122 pure @system unittest {
123 
124     import std.stdio : writeln;
125     import std.base64 : Base64;
126     import std.array : split, join;
127     import std.algorithm : map, filter;
128 
129     enum page = Element.HTMLDoctype ~ elem!"html"(
130 
131         elem!"head"(
132 
133             elem!("title")("An example document"),
134 
135             // Metadata
136             Element.MobileViewport,
137             Element.EncodingUTF8,
138 
139             elem!("style")(`
140 
141                 html, body {
142                     height: 100%;
143                    font-family: sans-serif;
144                     padding: 0;
145                     margin: 0;
146                 }
147                 .header {
148                     background: #f7a;
149                     font-size: 1.5em;
150                     margin: 0;
151                     padding: 5px;
152                 }
153                 .article {
154                     padding-left: 2em;
155                 }
156 
157             `.split("\n").map!"a.strip".filter!"a.length".join),
158 
159         ),
160 
161         elem!"body"(
162 
163             elem!("header", q{ class="header" })(
164                 elem!"h1"("Example website")
165             ),
166 
167             elem!"h1"("Welcome to my website!"),
168             elem!"p"("Hello there,", elem!"br", "may you want to read some of my articles?"),
169 
170             elem!("div", q{ class="article" })(
171                 elem!"h2"("Stuff"),
172                 elem!"p"("Description")
173             )
174 
175         )
176 
177     );
178 
179     enum target = cast(string) Base64.decode([
180         `PCFET0NUWVBFIGh0bWw+PGh0bWw+PGhlYWQ+PHRpdGxlPkFuIGV4YW1wbGUgZG9jdW1lbnQ8L3Rp`,
181         `dGxlPjxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGlu`,
182         `aXRpYWwtc2NhbGU9MSIvPjxtZXRhIGNoYXJzZXQ9InV0Zi04Ii8+PHN0eWxlPmh0bWwsIGJvZHkg`,
183         `e2hlaWdodDogMTAwJTtmb250LWZhbWlseTogc2Fucy1zZXJpZjtwYWRkaW5nOiAwO21hcmdpbjog`,
184         `MDt9LmhlYWRlciB7YmFja2dyb3VuZDogI2Y3YTtmb250LXNpemU6IDEuNWVtO21hcmdpbjogMDtw`,
185         `YWRkaW5nOiA1cHg7fS5hcnRpY2xlIHtwYWRkaW5nLWxlZnQ6IDJlbTt9PC9zdHlsZT48L2hlYWQ+`,
186         `PGJvZHk+PGhlYWRlciBjbGFzcz0iaGVhZGVyIj48aDE+RXhhbXBsZSB3ZWJzaXRlPC9oMT48L2hl`,
187         `YWRlcj48aDE+V2VsY29tZSB0byBteSB3ZWJzaXRlITwvaDE+PHA+SGVsbG8gdGhlcmUsPGJyLz5t`,
188         `YXkgeW91IHdhbnQgdG8gcmVhZCBzb21lIG9mIG15IGFydGljbGVzPzwvcD48ZGl2IGNsYXNzPSJh`,
189         `cnRpY2xlIj48aDI+U3R1ZmY8L2gyPjxwPkRlc2NyaXB0aW9uPC9wPjwvZGl2PjwvYm9keT48L2h0`,
190         `bWw+`,
191     ].join);
192 
193     assert(page == target);
194 
195 }
196 
197 // README example
198 pure @safe unittest {
199 
200     import elemi;
201     import std.conv;
202 
203     // HTML document
204     auto document = text(
205         Element.HTMLDoctype,
206         elem!"html"(
207 
208             elem!"head"(
209                 elem!"title"("Hello, World!"),
210                 Element.MobileViewport,
211                 Element.EncodingUTF8,
212             ),
213 
214             elem!"body"(
215                 attr("class") = ["home", "logged-in"],
216 
217                 elem!"main"(
218 
219                     elem!"img"(
220                         attr("src") = "/logo.png",
221                         attr("alt") = "Website logo"
222                     ),
223 
224                     // All input is sanitized.
225                     "<Welcome to my website!>"
226 
227                 )
228 
229             ),
230 
231         ),
232 
233     );
234 
235     // XML document
236     // You may `import elemi.xml` if you prefer to type `elem` over `elemX`
237     auto xml = text(
238         Element.XMLDeclaration1_0,
239         elemX!"feed"(
240 
241             attr("xmlns") = "http://www.w3.org/2005/Atom",
242 
243             elemX!"title"("Example feed"),
244             elemX!"subtitle"("Showcasing using elemi for generating XML"),
245             elemX!"updated"("2021-10-30T20:30:00Z"),
246 
247             elemX!"entry"(
248                 elemX!"title"("Elemi home page"),
249                 elemX!"link"(
250                     attr("href") = "https://git.samerion.com/Artha/Elemi",
251                 ),
252                 elemX!"updated"("2021-10-30T20:30:00Z"),
253                 elemX!"summary"("Elemi repository on GitHub"),
254                 elemX!"author"(
255                      elemX!"Artha",
256                      elemX!"artha@samerion.com"
257                 )
258             )
259 
260         )
261 
262     );
263 
264 }
265 
266 // UTF-32 test: generally `string` is preferred and in most cases, is required. There's one exception, content, and it
267 // must preserve the support.
268 //
269 // In the future, it might be preferrable to introduce support for any UTF encoding.
270 pure @safe unittest {
271 
272     import elemi;
273 
274     auto data = "Foo bar"d;
275     dchar[] dataArr = "Foo bar"d.dup;
276 
277     assert(elem!"div"("Hello, World!"d) == "<div>Hello, World!</div>");
278     assert(elem!"div"(elem!"span"("Hello, World!"d)) == "<div><span>Hello, World!</span></div>");
279     assert(elem!"div"(["class": "foo bar"], "Hello, World!"d) == `<div class="foo bar">Hello, World!</div>`);
280     assert(elem!"p"(data) == `<p>Foo bar</p>`);
281     assert(elem!"p"(dataArr) == `<p>Foo bar</p>`);
282 
283 }