1 /** 2 * Very simple write-only API for building XML documents. 3 * Abuses operator overloading to allow a very terse syntax. 4 * 5 * License: 6 * This Source Code Form is subject to the terms of 7 * the Mozilla Public License, v. 2.0. If a copy of 8 * the MPL was not distributed with this file, You 9 * can obtain one at http://mozilla.org/MPL/2.0/. 10 * 11 * Authors: 12 * Vladimir Panteleev <ae@cy.md> 13 */ 14 15 module ae.utils.xmlbuild; 16 17 import ae.utils.xmlwriter; 18 19 /// Create an XML node. Entry point. 20 XmlBuildNode newXml() 21 { 22 return new XmlBuildNode(); 23 } 24 25 /// The node type. Avoid using directly. 26 // Can't use struct pointers, because node["attr"] is 27 // interpreted as indexing the pointer. 28 final class XmlBuildNode 29 { 30 /// Create a child node by calling a "method" on the node 31 XmlBuildNode opDispatch(string name)(string[string] attributes = null) 32 { 33 auto result = new XmlBuildNode(); 34 result._xmlbuild_info.tag = name; 35 foreach (key, value; attributes) 36 result._xmlbuild_info.attributes ~= StringPair(key, value); 37 _xmlbuild_info.children ~= result; 38 return result; 39 } 40 41 /// Add attribute by assigning a "field" on the node 42 @property string opDispatch(string name)(string value) 43 { 44 _xmlbuild_info.attributes ~= StringPair(name, value); 45 return value; 46 } 47 48 /// Add attribute via index 49 string opIndexAssign(string value, string name) 50 { 51 _xmlbuild_info.attributes ~= StringPair(name, value); 52 return value; 53 } 54 55 /// Get/set inner text via node[] 56 ref string opSlice() 57 { 58 return _xmlbuild_info.text; 59 } 60 61 /// Set inner text via assigning a string 62 deprecated ("Use `node[] = str` instead of node = str") 63 void opAssign(string text) 64 { 65 _xmlbuild_info.text = text; 66 } 67 68 override string toString() const 69 { 70 XmlWriter writer; 71 writeTo(writer); 72 return writer.output.get(); 73 } /// 74 75 string toPrettyString() const 76 { 77 PrettyXmlWriter writer; 78 writeTo(writer); 79 return writer.output.get(); 80 } /// 81 82 /// Write to an `XmlWriter`. 83 final void writeTo(XmlWriter)(ref XmlWriter output) const 84 { 85 with (_xmlbuild_info) 86 { 87 output.startTagWithAttributes(tag); 88 foreach (ref attribute; attributes) 89 output.addAttribute(attribute.key, attribute.value); 90 if (!children.length && !text) 91 { 92 output.endAttributesAndTag(); 93 return; 94 } 95 output.endAttributes(); 96 97 foreach (child; children) 98 child.writeTo(output); 99 output.text(text); 100 101 output.endTag(tag); 102 } 103 } 104 105 // Use a unique name, unlikely to occur in an XML file as a field or attribute. 106 private XmlBuildInfo _xmlbuild_info; 107 } 108 109 private: 110 111 struct StringPair { string key, value; } 112 113 struct XmlBuildInfo 114 { 115 string tag, text; 116 StringPair[] attributes; 117 XmlBuildNode[] children; 118 } 119 120 version(unittest) import std.array : split; 121 version(unittest) import std.algorithm.sorting : sort; 122 123 unittest 124 { 125 auto svg = newXml().svg(); 126 svg.xmlns = "http://www.w3.org/2000/svg"; 127 svg["version"] = "1.1"; 128 auto text = svg.text(["x" : "0", "y" : "15", "fill" : "red"]); 129 text[] = "I love SVG"; 130 131 auto s = svg.toString(); 132 133 enum start = `<svg xmlns="http://www.w3.org/2000/svg" version="1.1"><text `; 134 assert(s[0..start.length] == start); 135 s = s[start.length..$]; 136 137 enum end = `>I love SVG</text></svg>`; 138 assert(s[$-end.length..$] == end); 139 s = s[0..$-end.length]; 140 141 assert(s.split.sort == `x="0" y="15" fill="red"`.split.sort); 142 }