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 <vladimir@thecybershadow.net> 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 final void writeTo(XmlWriter)(ref XmlWriter output) const 83 { 84 with (_xmlbuild_info) 85 { 86 output.startTagWithAttributes(tag); 87 foreach (ref attribute; attributes) 88 output.addAttribute(attribute.key, attribute.value); 89 if (!children.length && !text) 90 { 91 output.endAttributesAndTag(); 92 return; 93 } 94 output.endAttributes(); 95 96 foreach (child; children) 97 child.writeTo(output); 98 output.text(text); 99 100 output.endTag(tag); 101 } 102 } 103 104 // Use a unique name, unlikely to occur in an XML file as a field or attribute. 105 private XmlBuildInfo _xmlbuild_info; 106 } 107 108 private: 109 110 struct StringPair { string key, value; } 111 112 struct XmlBuildInfo 113 { 114 string tag, text; 115 StringPair[] attributes; 116 XmlBuildNode[] children; 117 } 118 119 version(unittest) import std.array : split; 120 version(unittest) import std.algorithm.sorting : sort; 121 122 unittest 123 { 124 auto svg = newXml().svg(); 125 svg.xmlns = "http://www.w3.org/2000/svg"; 126 svg["version"] = "1.1"; 127 auto text = svg.text(["x" : "0", "y" : "15", "fill" : "red"]); 128 text[] = "I love SVG"; 129 130 auto s = svg.toString(); 131 132 enum start = `<svg xmlns="http://www.w3.org/2000/svg" version="1.1"><text `; 133 assert(s[0..start.length] == start); 134 s = s[start.length..$]; 135 136 enum end = `>I love SVG</text></svg>`; 137 assert(s[$-end.length..$] == end); 138 s = s[0..$-end.length]; 139 140 assert(s.split.sort == `x="0" y="15" fill="red"`.split.sort); 141 }