1 /*
2 Building XML with RAII techniques.
3 */
4 module mage.util.xml;
5 import mage;
6 import mage.util.stream;
7 import mage.util.mem;
8 
9 import std.format : format;
10 
11 template isSomeParent(T)
12 {
13   import mage.util.reflection;
14   alias isSomeParent = Resolve!(is(T == Doc) || is(T == Element));
15 }
16 
17 struct Text
18 {
19   string str;
20 }
21 
22 struct Attribute
23 {
24   string key;
25   string value;
26 }
27 
28 struct Element
29 {
30   Doc* doc;
31   Element* parent;
32   string name;
33   Element*[] children;
34   Attribute*[] attributes;
35   Text* textContent;
36 
37   Element* child(string name)
38   {
39     //auto c = doc.mem.allocate!Element(doc, &this, name);
40     auto c = doc.mem.allocate!Element(doc, &this, name);
41     children ~= c;
42     return c;
43   }
44 
45   Attribute* attr(in string key, in string value)
46   {
47     foreach(a; attributes) {
48       if(a.key == key) {
49         a.value = value;
50         return a;
51       }
52     }
53     auto a = doc.mem.allocate!Attribute(key, value);
54     attributes ~= a;
55     return a;
56   }
57 
58   Text* text(in string content)
59   {
60     textContent = doc.mem.allocate!Text(content);
61     return textContent;
62   }
63 }
64 
65 struct Doc
66 {
67   BlockArray!(4.KiB) mem;
68 
69   string xmlVersion = "1.0";
70   string xmlEncoding = "utf-8";
71   Element*[] children;
72 
73   Element* child(string name) {
74     auto n = mem.allocate!Element(&this, null, name);
75     children ~= n;
76     return n;
77   }
78 }
79 
80 
81 unittest {
82   Doc doc;
83   with(doc) {
84     with(child("Project")) {
85       attr("DefaultTargets", "Build");
86       attr("ToolsVersion", "12.0");
87       attr("xmlns", "http://schemas.microsoft.com/developer/msbuild/2003");
88       with(child("ItemGroup")) {
89         attr("Label", "ProjectConfigurations");
90         with(child("ProjectConfiguration")) {
91           attr("Include", "Debug|Win32");
92           with(child("Configuration")) {
93             text("Debug");
94           }
95           with(child("Platform")) {
96             text("Win32");
97           }
98         }
99       }
100     }
101   }
102 }
103 
104 void serialize(S)(ref S stream, ref Doc doc)
105 {
106   stream.write(`<?xml version="%s" encoding="%s"?>`.format(doc.xmlVersion, doc.xmlEncoding));
107   foreach(c; doc.children) {
108     stream.serialize(*c);
109   }
110   stream.writeln();
111 }
112 
113 void serialize(S)(ref S stream, ref Element n)
114 {
115   import std.stdio : stdout = write;
116   
117   stream.writeln();
118   stream.write("<%s".format(n.name));
119   foreach(a; n.attributes) {
120     stream.write(" ");
121     stream.serialize(*a);
122   }
123   if(n.children.length > 0 || n.textContent !is null)
124   {
125     stream.write(">");
126     stream.indent();
127     foreach(c; n.children) {
128       stream.serialize(*c);
129     }
130     stream.dedent();
131     if(n.textContent) stream.write(n.textContent.str);
132     else stream.writeln();
133     stream.write("</%s>".format(n.name));
134   }
135   else {
136     stream.write("/>");
137   }
138 }
139 
140 void serialize(S)(ref S stream, ref Attribute a)
141 {
142   stream.write(`%s="%s"`.format(a.key, a.value));
143 }
144 
145 unittest {
146   Doc doc;
147   with(doc) {
148     foreach(i; 0..3)
149     {
150       with(child("A")) {
151         attr("id", "a%s".format(i));
152         attr("name", "the-a");
153         with(child("B")) {
154           attr("name", "the-b");
155           with(child("C1")) {
156             attr("name", "the-c");
157           }
158           with(child("C2")) {
159             attr("name", "the-other-c");
160           }
161         }
162       }
163     }
164   }
165   //StringStream s;
166   //s.writeln();
167   StdoutStream l;
168   //l.newLine = "\\n\n";
169   //l.indentString = "=>";
170   l.serialize(doc);
171   //log(s.content);
172 }