1 module text.xml.Tree;
2 
3 import boilerplate;
4 import dxml.parser;
5 import std.algorithm;
6 import std.range;
7 import std.typecons;
8 import text.xml.Writer;
9 
10 alias Attribute = Tuple!(string, "name", string, "value");
11 
12 /**
13  * This struct holds a list of XML attributes as name/value pairs.
14  */
15 struct Attributes
16 {
17 
18     private Attribute[] attributes;
19 
20     private alias lookup = (Attribute attr, string name) => attr.name == name;
21 
22     public this(R)(R range)
23     if (isInputRange!R && is(ElementType!R == Attribute))
24     {
25         this = range;
26     }
27 
28     public typeof(this) opAssign(R)(R range)
29     if (isInputRange!R && is(ElementType!R == Attribute))
30     {
31         this.attributes = range.array;
32         return this;
33     }
34 
35     public inout(Attribute)[] opIndex() inout @nogc nothrow pure @safe
36     {
37         return this.attributes;
38     }
39 
40     public string opIndex(string name) nothrow pure @safe
41     in (!this.attributes.find!lookup(name).empty, "Attribute not found")
42     {
43         return this.attributes.find!lookup(name).front.value;
44     }
45 
46     public ref string opIndexAssign(string value, string name) pure @safe
47     {
48         auto result = this.attributes.find!lookup(name);
49 
50         if (result.empty)
51         {
52             this.attributes ~= Attribute(name, value);
53             return this.attributes.back.value;
54         }
55         return result.front.value = value;
56     }
57 
58     public bool opBinaryRight(string op : "in")(string name) const
59     {
60         return this.attributes.canFind!lookup(name);
61     }
62 
63     public void free()
64     {
65         import core.memory : GC;
66 
67         GC.free(this.attributes.ptr);
68     }
69 }
70 
71 /**
72  * This struct represents an XML node.
73  */
74 struct XmlNode
75 {
76 
77     enum Type : EntityType
78     {
79         cdata = EntityType.cdata,
80         comment = EntityType.comment,
81         text = EntityType.text,
82         pi = EntityType.pi,
83         element = EntityType.elementStart,
84     }
85 
86     private Type type_;
87 
88     private string tag_;
89 
90     public XmlNode[] children;
91 
92     public Attributes attributes;
93 
94     public @property string tag() const @nogc nothrow pure @safe
95     {
96         return this.tag_;
97     }
98 
99     public @property Type type() const @nogc nothrow pure @safe
100     {
101         return this.type_;
102     }
103 
104     public auto findChildren(string tag) @nogc pure @safe
105     {
106         import std.traits : CopyConstness, Unqual;
107 
108         static struct FindChildrenRange(T)
109         if (is(Unqual!T == XmlNode))
110         {
111             private T[] children;
112 
113             private string tag;
114 
115             @disable this();
116 
117             public this(T[] children, string tag)
118             {
119                 this.children = children;
120                 this.tag = tag;
121                 prime;
122             }
123 
124             public @property bool empty() const @nogc nothrow pure @safe
125             {
126                 return this.children.empty;
127             }
128 
129             public void popFront() @nogc nothrow pure @safe
130             {
131                 this.children.popFront;
132                 prime;
133             }
134 
135             public @property T front() @nogc nothrow pure @safe
136             in (!empty)
137             {
138                 return this.children.front;
139             }
140 
141             public @property auto save() const @nogc nothrow pure @safe
142             {
143                 return FindChildrenRange!(CopyConstness!(typeof(this), T))(children, tag);
144             }
145 
146             private void prime() @nogc nothrow pure @safe
147             {
148                 while (!empty && (front.type_ != Type.element || front.tag_ != this.tag))
149                 {
150                     this.children.popFront;
151                 }
152             }
153         }
154 
155         return FindChildrenRange!XmlNode(this.children, tag);
156     }
157 
158     public Nullable!XmlNode findChild(string tag) @nogc pure @safe
159     {
160         auto result = findChildren(tag);
161 
162         return result.empty ? typeof(return)() : nullable(result.front);
163     }
164 
165     public @property string text() const pure @safe
166     {
167         if (this.type == Type.text)
168         {
169             return this.tag;
170         }
171         return this.children.map!(child => child.text).join;
172     }
173 
174     public string toString() const
175     {
176         auto sink = appender!string();
177         auto writer = customXmlWriter!(No.pretty)(sink);
178 
179         writer.write(this);
180         return sink.data;
181     }
182 
183     public void toString(scope void delegate(const(char)[]) sink) const
184     {
185         auto writer = customXmlWriter!(No.pretty)(sink);
186 
187         writer.write(this);
188     }
189 
190     public XmlNode addAttribute(string name, string value)
191     {
192         this.attributes.attributes ~= Attribute(name, value);
193         return this;
194     }
195 
196     public void free()
197     {
198         import core.memory : GC;
199 
200         this.children.each!"a.free";
201         GC.free(this.children.ptr);
202         this.attributes.free;
203     }
204 }