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 // Use mixin template to work around template symbol issue with 109 // `FindChildrenRange(T)` instantiating itself with `const(T)`. 110 template FindChildrenRangeImpl(T) 111 { 112 private T[] children; 113 114 private string tag; 115 116 @disable this(); 117 118 public this(T[] children, string tag) 119 { 120 this.children = children; 121 this.tag = tag; 122 prime; 123 } 124 125 public @property bool empty() const @nogc nothrow pure @safe 126 { 127 return this.children.empty; 128 } 129 130 public void popFront() @nogc nothrow pure @safe 131 { 132 this.children.popFront; 133 prime; 134 } 135 136 public @property T front() @nogc nothrow pure @safe 137 in (!empty) 138 { 139 return this.children.front; 140 } 141 142 public @property auto save() const @nogc nothrow pure @safe 143 { 144 return ConstFindChildrenRange(children, tag); 145 } 146 147 private void prime() @nogc nothrow pure @safe 148 { 149 while (!empty && (front.type_ != Type.element || front.tag_ != this.tag)) 150 { 151 this.children.popFront; 152 } 153 } 154 } 155 156 static struct ConstFindChildrenRange 157 { 158 mixin FindChildrenRangeImpl!(const XmlNode); 159 } 160 161 static struct FindChildrenRange 162 { 163 mixin FindChildrenRangeImpl!XmlNode; 164 } 165 166 return FindChildrenRange(this.children, tag); 167 } 168 169 public Nullable!XmlNode findChild(string tag) @nogc pure @safe 170 { 171 auto result = findChildren(tag); 172 173 return result.empty ? typeof(return)() : nullable(result.front); 174 } 175 176 public @property string text() const pure @safe 177 { 178 if (this.type == Type.text) 179 { 180 return this.tag; 181 } 182 return this.children.map!(child => child.text).join; 183 } 184 185 public string toString() const 186 { 187 auto sink = appender!string(); 188 auto writer = customXmlWriter!(No.pretty)(sink); 189 190 writer.write(this); 191 return sink.data; 192 } 193 194 public void toString(scope void delegate(const(char)[]) sink) const 195 { 196 auto writer = customXmlWriter!(No.pretty)(sink); 197 198 writer.write(this); 199 } 200 201 public XmlNode addAttribute(string name, string value) 202 { 203 this.attributes.attributes ~= Attribute(name, value); 204 return this; 205 } 206 207 public void free() 208 { 209 import core.memory : GC; 210 211 this.children.each!"a.free"; 212 GC.free(this.children.ptr); 213 this.attributes.free; 214 } 215 }