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>&lt;XSS&gt;</xml>");
153     assert(elem!"xml".addX!"span"("<XSS>") == "<xml><span>&lt;XSS&gt;</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 }