1 /// This module defines the old Elemi syntax for generating XML code. 2 /// 3 /// See [#examples|examples] for more information. 4 module elemi.xml; 5 6 /// Elemi allows creating XML elements using the `elemX` template. 7 pure @safe unittest { 8 auto element = elemX!"paragraph"("Hello, World!"); 9 10 assert(element == "<paragraph>Hello, World!</paragraph>"); 11 } 12 13 /// Elements can be nested. 14 pure @safe unittest { 15 auto element = elemX!"book"( 16 elemX!"title"("Programming in D"), 17 elemX!"author"("Ali Çehreli"), 18 elemX!"link"("https://www.ddili.org/ders/d.en/index.html"), 19 ); 20 21 assert(element == `<book>` 22 ~ `<title>Programming in D</title>` 23 ~ `<author>Ali Çehreli</author>` 24 ~ `<link>https://www.ddili.org/ders/d.en/index.html</link>` 25 ~ `</book>`); 26 } 27 28 /// Text and XML content can be mixed together. Special XML characters are always escaped. 29 pure @safe unittest { 30 auto element = elemX!"paragraph"( 31 "<injection />", 32 elemX!"bold"("Can't get through me!"), 33 ); 34 35 assert(element == `<paragraph>` 36 ~ `<injection />` 37 ~ `<bold>Can't get through me!</bold>` 38 ~ `</paragraph>`); 39 } 40 41 /// Attributes can be added using `attr` at the beginning of your element. 42 pure @safe unittest { 43 auto book = elemX!"website"( 44 attr("title") = "D Programming Language", 45 attr("url") = "https://dlang.org", 46 "D is a general-purpose programming language with static typing, ", 47 "systems-level access, and C-like syntax. With the D Programming ", 48 "Language, write fast, read fast, and run fast." 49 ); 50 51 assert(book == `<website ` 52 ~ `title="D Programming Language" ` 53 ~ `url="https://dlang.org">` 54 ~ `D is a general-purpose programming language with static typing, ` 55 ~ `systems-level access, and C-like syntax. With the D Programming ` 56 ~ `Language, write fast, read fast, and run fast.` 57 ~ `</website>`); 58 } 59 60 /// You can create self-closing tags if you follow the name with a slash. 61 pure @safe unittest { 62 auto element = elemX!"void/"(); 63 assert(element == "<void/>"); 64 } 65 66 /// The `elem` shorthand defaults to HTML mode, but if you import `elemi.xml` directly, 67 /// XML mode will be used. 68 pure @safe unittest { 69 { 70 import elemi : elem; 71 auto e = elem!"input"(); 72 assert(e == "<input/>"); 73 // <input> is a self-closing void tag 74 } 75 { 76 import elemi.xml : elem; 77 auto e = elem!"input"(); 78 assert(e == "<input></input>"); 79 } 80 } 81 82 /// If you need to add children dynamically, you can append them. 83 pure @safe unittest { 84 Element element = elemX!"parent"(); 85 86 // Append elements 87 foreach (content; ["one", "two", "three"]) { 88 element ~= elemX!"child"(content); 89 } 90 91 // Append attributes 92 element ~= attr("id") = "example"; 93 94 assert(element == `<parent id="example">` 95 ~ `<child>one</child>` 96 ~ `<child>two</child>` 97 ~ `<child>three</child>` 98 ~ `</parent>`); 99 } 100 101 /// If you need to group a few elements together, you can do it with `elems`. 102 pure @safe unittest { 103 auto child = elems( 104 "Hello, ", 105 elemX!"strong"("World"), 106 ); 107 auto parent = elemX!"div"( 108 child, 109 ); 110 111 assert(child == "Hello, <strong>World</strong>"); 112 assert(parent == "<div>Hello, <strong>World</strong></div>"); 113 } 114 115 static if (imported!"elemi.internal".withInterpolation) { 116 /// If combined with a fresh compiler, Elemi also supports interpolated strings. 117 pure @safe unittest{ 118 auto e = elemX!"item"( 119 attr("expression") = i"1+2 = $(1+2)", 120 i"1+2 is $(1+2)", 121 ); 122 123 assert(e == `<item expression="1+2 = 3">1+2 is 3</item>`); 124 } 125 } 126 127 /// `elemX` also supports DTD declarations like DOCTYPE, XML declarations, and even preprocessor 128 /// tags. 129 pure @safe unittest { 130 assert(elemX!"!DOCTYPE"("html") == `<!DOCTYPE html>`); 131 assert(elemX!"?xml"( 132 attr("version") = "1.0", 133 attr("encoding") = "UTF-8") == `<?xml version="1.0" encoding="UTF-8" ?>`); 134 assert(elemX!"?php"(`$var = "<div></div>";`) == `<?php $var = "<div></div>"; ?>`); 135 assert(elemX!"?="(`$var`) == `<?= $var ?>`); 136 } 137 138 import std.meta; 139 import std.traits; 140 141 import elemi.internal; 142 143 public { 144 145 import elemi.attribute; 146 import elemi.element; 147 148 } 149 150 /// A shorthand for `elemX`. Available if importing `elemi.xml`. 151 /// 152 /// Note that importing both `elemi` and `elemi.xml`, or `elemi.html` and `elemi.xml` may raise 153 /// conflicts. Import whichever is more suitable. 154 /// 155 /// See [elemX] for more information. 156 alias elem = elemX; 157 alias add = addX; 158 159 /// Create an XML element. 160 /// 161 /// `elemX` builds the element with attributes and child content given as arguments. Their kind 162 /// is distinguished by their type — child element should be passed as [Element], attributes 163 /// as [Attribute] and text content as [string]. 164 /// 165 /// * You can pass the result of `elemX` to another `elemX` call to create child nodes: 166 /// `elemX!"parent"(elemX!"child"())`. 167 /// * Use [attr] to specify attributes: `elemX!"element"(attr("key") = "value")`. 168 /// * Pass a string to specify text content: `elemX!"text"("Hello")`. 169 /// 170 /// `elemX` also provides the option to specify attributes from an associative array 171 /// or source: `elemX!"element"(["key": "value"])` or `elemX!("element", `key="value"`)` 172 /// but these are considered legacy and should be avoided in new code. 173 /// 174 /// Params: 175 /// name = Name of the element. 176 /// attrXML = Optional, legacy; unsanitized attributes to insert at compile-time. 177 /// attributes = Optional, legacy; attributes for the element as an associative array mapping 178 /// attribute names to their values. 179 /// content = Attributes ([Attribute]), 180 /// children elements ([Element]) 181 /// and text of the element, as a string. 182 /// Returns: 183 /// An [Element] type, implicitly castable to string. 184 /// Instances of Element can be safely placed 185 Element elemX(string name, string[string] attributes, Ts...)(Ts content) { 186 187 // Overload 1: attributes from a CTFE hash map 188 189 enum attrHTML = attributes.serializeAttributes; 190 191 auto element = Element.make!name; 192 element.attributes = attrHTML; 193 element ~= content; 194 195 return element; 196 197 } 198 199 /// ditto 200 Element elemX(string name, string attrXML = null, T...)(string[string] attributes, T content) { 201 202 // Overload 2: attributes from a CTFE attribute string and from a runtime hash map 203 204 enum attrXML = minifyAttributes(attrXML); 205 206 auto element = Element.make!name; 207 element.attributes = attrXML 208 ~ attributes.serializeAttributes; 209 element ~= content; 210 211 return element; 212 213 } 214 215 /// ditto 216 Element elemX(string name, string attrXML = null, T...)(T content) 217 if (!T.length || (!is(T[0] == typeof(null)) && !is(T[0] == string[string]))) { 218 219 // Overload 3: attributes from a CTFE attribute string 220 221 enum attrXML = minifyAttributes(attrXML); 222 223 auto element = Element.make!name; 224 element.attributes = attrXML; 225 element ~= content; 226 227 return element; 228 229 } 230 231 /// 232 pure @safe unittest { 233 234 enum xml = elemX!"xml"( 235 elemX!"heading"("This is my sample document!"), 236 elemX!("spacing /", q{ height="1em" }), 237 elemX!"spacing /"(["height": "1em"]), 238 elemX!"empty", 239 elemX!"br", 240 elemX!"container" 241 .addX!"paragraph"("Foo") 242 .addX!"paragraph"("Bar"), 243 ); 244 245 assert(xml == "<xml>" ~ ( 246 "<heading>This is my sample document!</heading>" 247 ~ `<spacing height="1em"/>` 248 ~ `<spacing height="1em"/>` 249 ~ `<empty></empty>` 250 ~ `<br></br>` 251 ~ "<container>" ~ ( 252 "<paragraph>Foo</paragraph>" 253 ~ "<paragraph>Bar</paragraph>" 254 ) ~ "</container>" 255 ) ~ "</xml>"); 256 257 } 258 259 260 /// Add a new node as a child. 261 /// 262 /// This overload is considered legacy; you should use the apprend `~=` operator instead. 263 /// 264 /// Params: 265 /// parent = Parent node. 266 /// Returns: 267 /// This node, to allow chaining. 268 Element addX(Ts...)(ref Element parent, Ts args) 269 if (allSatisfy!(isType, Ts)) { 270 271 parent ~= args; 272 return parent; 273 274 } 275 276 /// ditto 277 Element addX(Ts...)(Element parent, Ts args) 278 if (allSatisfy!(isType, Ts)) { 279 280 parent ~= args; 281 return parent; 282 283 } 284 285 /// ditto 286 template addX(Ts...) 287 if (Ts.length != 0) { 288 289 Element addX(Args...)(ref Element parent, Args args) { 290 291 parent ~= elemX!Ts(args); 292 return parent; 293 294 } 295 296 Element addX(Args...)(Element parent, Args args) { 297 298 parent ~= elemX!Ts(args); 299 return parent; 300 301 } 302 303 } 304 305 /// 306 pure @safe unittest { 307 308 auto document = elem!"xml" 309 .addX!"text"("Hello") 310 .addX!"text"("World!"); 311 312 assert(document == "<xml><text>Hello</text><text>World!</text></xml>"); 313 314 } 315 316 pure @safe unittest { 317 318 assert(elem!"xml".add("<XSS>") == "<xml><XSS></xml>"); 319 assert(elem!"xml".addX!"span"("<XSS>") == "<xml><span><XSS></span></xml>"); 320 321 } 322 323 pure @safe unittest { 324 325 assert(elemX!"br" == "<br></br>"); 326 assert(elemX!"br " == "<br ></br >"); 327 assert(elemX!"br /" == "<br/>"); 328 assert(elemX!"br/" == "<br/>"); 329 330 assert(elemX!"myFancyTag" == "<myFancyTag></myFancyTag>"); 331 assert(elemX!"myFancyTag /" == "<myFancyTag/>"); 332 assert(elemX!"myFancyTäg /" == "<myFancyTäg/>"); 333 334 assert(elemX!"?br" == "<?br ?>"); 335 assert(elemX!"!br" == "<!br>"); 336 337 assert(elemX!"?br" == "<?br ?>"); 338 assert(elemX!("?br", "foo") == "<?br foo ?>"); 339 assert(elemX!"?br"(attr("foo", "bar")) == `<?br foo="bar" ?>`); 340 341 } 342 343 // Issue #1 344 pure @safe unittest { 345 346 enum Foo = elem!("p")("<unsafe>code</unsafe>"); 347 348 } 349 350 pure @safe unittest { 351 352 assert(elemX!"p" == "<p></p>"); 353 assert(elemX!"p /" == "<p/>"); 354 assert(elemX!("!DOCTYPE", "html") == "<!DOCTYPE html>"); 355 assert(Element.HTMLDoctype == "<!DOCTYPE html>"); 356 assert(elemX!("!ATTLIST", "pre (preserve) #FIXED 'preserve'") == "<!ATTLIST pre (preserve) #FIXED 'preserve'>"); 357 assert(elemX!"!ATTLIST"("pre (preserve) #FIXED 'preserve'") == "<!ATTLIST pre (preserve) #FIXED 'preserve'>"); 358 assert(elemX!"!ATTLIST".add("pre (preserve) #FIXED 'preserve'") == "<!ATTLIST pre (preserve) #FIXED 'preserve'>"); 359 assert(elemX!"?xml" == "<?xml ?>"); 360 assert(elemX!("?xml", q{ version="1.1" encoding="UTF-8" }) == `<?xml version="1.1" encoding="UTF-8" ?>`); 361 assert(elemX!"?xml"(`version="1.1" encoding="UTF-8"`) == `<?xml version="1.1" encoding="UTF-8" ?>`); 362 assert(elemX!"?xml".add(`version="1.1" encoding="UTF-8"`) == `<?xml version="1.1" encoding="UTF-8" ?>`); 363 assert(Element.XMLDeclaration == `<?xml version="1.1" encoding="UTF-8" ?>`); 364 assert(elemX!"?xml"(["version": "1.1"]).addTrusted(`encoding="UTF-8"`) 365 == `<?xml version="1.1" encoding="UTF-8" ?>`); 366 assert(elemX!"?php" == "<?php ?>"); 367 assert(Element.XMLDeclaration1_0 == `<?xml version="1.0" encoding="UTF-8" ?>`); 368 assert(Element.XMLDeclaration1_1 == `<?xml version="1.1" encoding="UTF-8" ?>`); 369 assert(elemX!"?php"(`echo "Hello, World!";`) == `<?php echo "Hello, World!"; ?>`); 370 assert(elemX!"?="(`"Hello, World!"`) == `<?= "Hello, World!" ?>`); 371 // ↑ I will not special-case this to remove spaces. 372 373 auto php = elemX!"?php"; 374 php.add(`$target = "World!";`); 375 php.add(`echo "Hello, " . $target;`); 376 assert(php == `<?php $target = "World!";echo "Hello, " . $target; ?>`); 377 378 assert(elemX!("?xml", "test").add("foo") == "<?xml test foo ?>"); 379 assert(elemX!("!XML", "test").add("foo") == "<!XML test foo>"); 380 381 }