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 }