1 module elemi.xml; 2 3 import std.meta; 4 import std.traits; 5 6 import elemi.internal; 7 8 public { 9 10 import elemi.attribute; 11 import elemi.element; 12 13 } 14 15 // Magic elem alias 16 alias elem = elemX; 17 alias add = addX; 18 19 /// Create an XML element. 20 /// 21 /// Params: 22 /// name = Name of the element. 23 /// attrHTML = Unsanitized attributes to insert at compile-time. 24 /// attributes = Attributes for the element as an associative array mapping attribute names to their values. 25 /// content = Attributes (via `Attribute` and `attr`), children and text of the element. 26 /// Returns: a Element type, implictly castable to string. 27 Element elemX(string name, string[string] attributes, Ts...)(Ts content) { 28 29 // Overload 1: attributes from a CTFE hash map 30 31 enum attrHTML = attributes.serializeAttributes; 32 33 auto element = Element.make!name; 34 element.attributes = attrHTML; 35 element ~= content; 36 37 return element; 38 39 } 40 41 /// ditto 42 Element elemX(string name, string attrHTML = null, T...)(string[string] attributes, T content) { 43 44 // Overload 2: attributes from a CTFE attribute string and from a runtime hash map 45 46 enum attrHTML = minifyAttributes(attrHTML); 47 48 auto element = Element.make!name; 49 element.attributes = attrHTML 50 ~ attributes.serializeAttributes; 51 element ~= content; 52 53 return element; 54 55 } 56 57 /// ditto 58 Element elemX(string name, string attrHTML = null, T...)(T content) 59 if (!T.length || (!is(T[0] == typeof(null)) && !is(T[0] == string[string]))) { 60 61 // Overload 3: attributes from a CTFE attribute string 62 63 enum attrHTML = minifyAttributes(attrHTML); 64 65 auto element = Element.make!name; 66 element.attributes = attrHTML; 67 element ~= content; 68 69 return element; 70 71 } 72 73 /// 74 pure @safe unittest { 75 76 enum xml = elemX!"xml"( 77 elemX!"heading"("This is my sample document!"), 78 elemX!("spacing /", q{ height="1em" }), 79 elemX!"spacing /"(["height": "1em"]), 80 elemX!"empty", 81 elemX!"br", 82 elemX!"container" 83 .addX!"paragraph"("Foo") 84 .addX!"paragraph"("Bar"), 85 ); 86 87 assert(xml == "<xml>" ~ ( 88 "<heading>This is my sample document!</heading>" 89 ~ `<spacing height="1em"/>` 90 ~ `<spacing height="1em"/>` 91 ~ `<empty></empty>` 92 ~ `<br></br>` 93 ~ "<container>" ~ ( 94 "<paragraph>Foo</paragraph>" 95 ~ "<paragraph>Bar</paragraph>" 96 ) ~ "</container>" 97 ) ~ "</xml>"); 98 99 } 100 101 102 /// Add a new node as a child of this node. 103 /// Returns: This node, to allow chaining. 104 Element addX(Ts...)(ref Element parent, Ts args) 105 if (allSatisfy!(isType, Ts)) { 106 107 parent ~= args; 108 return parent; 109 110 } 111 112 Element addX(Ts...)(Element parent, Ts args) 113 if (allSatisfy!(isType, Ts)) { 114 115 parent ~= args; 116 return parent; 117 118 } 119 120 template addX(Ts...) 121 if (Ts.length != 0) { 122 123 Element addX(Args...)(ref Element parent, Args args) { 124 125 parent ~= elemX!Ts(args); 126 return parent; 127 128 } 129 130 Element addX(Args...)(Element parent, Args args) { 131 132 parent ~= elemX!Ts(args); 133 return parent; 134 135 } 136 137 } 138 139 /// 140 pure @safe unittest { 141 142 auto document = elem!"xml" 143 .addX!"text"("Hello") 144 .addX!"text"("World!"); 145 146 assert(document == "<xml><text>Hello</text><text>World!</text></xml>"); 147 148 } 149 150 pure @safe unittest { 151 152 assert(elem!"xml".add("<XSS>") == "<xml><XSS></xml>"); 153 assert(elem!"xml".addX!"span"("<XSS>") == "<xml><span><XSS></span></xml>"); 154 155 } 156 157 pure @safe unittest { 158 159 assert(elemX!"br" == "<br></br>"); 160 assert(elemX!"br " == "<br ></br >"); 161 assert(elemX!"br /" == "<br/>"); 162 assert(elemX!"br/" == "<br/>"); 163 164 assert(elemX!"myFancyTag" == "<myFancyTag></myFancyTag>"); 165 assert(elemX!"myFancyTag /" == "<myFancyTag/>"); 166 assert(elemX!"myFancyTäg /" == "<myFancyTäg/>"); 167 168 assert(elemX!"?br" == "<?br ?>"); 169 assert(elemX!"!br" == "<!br>"); 170 171 assert(elemX!"?br" == "<?br ?>"); 172 assert(elemX!("?br", "foo") == "<?br foo ?>"); 173 assert(elemX!"?br"(attr("foo", "bar")) == `<?br foo="bar" ?>`); 174 175 } 176 177 // Issue #1 178 pure @safe unittest { 179 180 enum Foo = elem!("p")("<unsafe>code</unsafe>"); 181 182 } 183 184 pure @safe unittest { 185 186 assert(elemX!"p" == "<p></p>"); 187 assert(elemX!"p /" == "<p/>"); 188 assert(elemX!("!DOCTYPE", "html") == "<!DOCTYPE html>"); 189 assert(Element.HTMLDoctype == "<!DOCTYPE html>"); 190 assert(elemX!("!ATTLIST", "pre (preserve) #FIXED 'preserve'") == "<!ATTLIST pre (preserve) #FIXED 'preserve'>"); 191 assert(elemX!"!ATTLIST"("pre (preserve) #FIXED 'preserve'") == "<!ATTLIST pre (preserve) #FIXED 'preserve'>"); 192 assert(elemX!"!ATTLIST".add("pre (preserve) #FIXED 'preserve'") == "<!ATTLIST pre (preserve) #FIXED 'preserve'>"); 193 assert(elemX!"?xml" == "<?xml ?>"); 194 assert(elemX!("?xml", q{ version="1.1" encoding="UTF-8" }) == `<?xml version="1.1" encoding="UTF-8" ?>`); 195 assert(elemX!"?xml"(`version="1.1" encoding="UTF-8"`) == `<?xml version="1.1" encoding="UTF-8" ?>`); 196 assert(elemX!"?xml".add(`version="1.1" encoding="UTF-8"`) == `<?xml version="1.1" encoding="UTF-8" ?>`); 197 assert(Element.XMLDeclaration == `<?xml version="1.1" encoding="UTF-8" ?>`); 198 assert(elemX!"?xml"(["version": "1.1"]).addTrusted(`encoding="UTF-8"`) 199 == `<?xml version="1.1" encoding="UTF-8" ?>`); 200 assert(elemX!"?php" == "<?php ?>"); 201 assert(Element.XMLDeclaration1_0 == `<?xml version="1.0" encoding="UTF-8" ?>`); 202 assert(Element.XMLDeclaration1_1 == `<?xml version="1.1" encoding="UTF-8" ?>`); 203 assert(elemX!"?php"(`echo "Hello, World!";`) == `<?php echo "Hello, World!"; ?>`); 204 assert(elemX!"?="(`"Hello, World!"`) == `<?= "Hello, World!" ?>`); 205 // ↑ I will not special-case this to remove spaces. 206 207 auto php = elemX!"?php"; 208 php.add(`$target = "World!";`); 209 php.add(`echo "Hello, " . $target;`); 210 assert(php == `<?php $target = "World!";echo "Hello, " . $target; ?>`); 211 212 assert(elemX!("?xml", "test").add("foo") == "<?xml test foo ?>"); 213 assert(elemX!("!XML", "test").add("foo") == "<!XML test foo>"); 214 215 }