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 	/// Set inner text via assigning a string
56 	void opAssign(string text)
57 	{
58 		_xmlbuild_info.text = text;
59 	}
60 
61 	override string toString() const
62 	{
63 		XmlWriter writer;
64 		writeTo(writer);
65 		return writer.output.get();
66 	}
67 
68 	string toPrettyString() const
69 	{
70 		PrettyXmlWriter writer;
71 		writeTo(writer);
72 		return writer.output.get();
73 	}
74 
75 	final void writeTo(XmlWriter)(ref XmlWriter output) const
76 	{
77 		with (_xmlbuild_info)
78 		{
79 			output.startTagWithAttributes(tag);
80 			foreach (ref attribute; attributes)
81 				output.addAttribute(attribute.key, attribute.value);
82 			if (!children.length && !text)
83 			{
84 				output.endAttributesAndTag();
85 				return;
86 			}
87 			output.endAttributes();
88 
89 			foreach (child; children)
90 				child.writeTo(output);
91 			output.text(text);
92 
93 			output.endTag(tag);
94 		}
95 	}
96 
97 	// Use a unique name, unlikely to occur in an XML file as a field or attribute.
98 	private XmlBuildInfo _xmlbuild_info;
99 }
100 
101 private:
102 
103 struct StringPair { string key, value; }
104 
105 struct XmlBuildInfo
106 {
107 	string tag, text;
108 	StringPair[] attributes;
109 	XmlBuildNode[] children;
110 }
111 
112 unittest
113 {
114 	auto svg = newXml().svg();
115 	svg.xmlns = "http://www.w3.org/2000/svg";
116 	svg["version"] = "1.1";
117 	auto text = svg.text(["x" : "0", "y" : "15", "fill" : "red"]);
118 	text = "I love SVG";
119 
120 	auto s = svg.toString();
121 	string s1 = `<svg xmlns="http://www.w3.org/2000/svg" version="1.1"><text fill="red" x="0" y="15">I love SVG</text></svg>`;
122 	string s2 = `<svg xmlns="http://www.w3.org/2000/svg" version="1.1"><text x="0" y="15" fill="red">I love SVG</text></svg>`;
123 	assert(s == s1 || s == s2, s);
124 }