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>-> 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=""XSS!""/>` 66 ); 67 68 assert( 69 70 elem!"input"(["type": "text", "value": `"XSS!"`]) 71 == `<input type="text" value=""XSS!""/>` 72 73 ); 74 assert( 75 elem!("input", q{ type="text" })(["value": `"XSS!"`]) 76 == `<input type="text" value=""XSS!""/>` 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>-> 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 }