aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/sdlang/ast.d
diff options
context:
space:
mode:
Diffstat (limited to 'src/sdlang/ast.d')
-rw-r--r--src/sdlang/ast.d2945
1 files changed, 0 insertions, 2945 deletions
diff --git a/src/sdlang/ast.d b/src/sdlang/ast.d
deleted file mode 100644
index 87dd0bd..0000000
--- a/src/sdlang/ast.d
+++ /dev/null
@@ -1,2945 +0,0 @@
-// SDLang-D
-// Written in the D programming language.
-
-module sdlang.ast;
-
-import std.algorithm;
-import std.array;
-import std.conv;
-import std.range;
-import std.string;
-
-import sdlang.exception;
-import sdlang.token;
-import sdlang.util;
-
-class Attribute
-{
- Value value;
- Location location;
-
- private Tag _parent;
- /// Get parent tag. To set a parent, attach this Attribute to its intended
- /// parent tag by calling `Tag.add(...)`, or by passing it to
- /// the parent tag's constructor.
- @property Tag parent()
- {
- return _parent;
- }
-
- private string _namespace;
- /++
- This tag's namespace. Empty string if no namespace.
-
- Note that setting this value is O(n) because internal lookup structures
- need to be updated.
-
- Note also, that setting this may change where this tag is ordered among
- its parent's list of tags.
- +/
- @property string namespace()
- {
- return _namespace;
- }
- ///ditto
- @property void namespace(string value)
- {
- if(_parent && _namespace != value)
- {
- // Remove
- auto saveParent = _parent;
- if(_parent)
- this.remove();
-
- // Change namespace
- _namespace = value;
-
- // Re-add
- if(saveParent)
- saveParent.add(this);
- }
- else
- _namespace = value;
- }
-
- private string _name;
- /++
- This attribute's name, not including namespace.
-
- Use `getFullName().toString` if you want the namespace included.
-
- Note that setting this value is O(n) because internal lookup structures
- need to be updated.
-
- Note also, that setting this may change where this attribute is ordered
- among its parent's list of tags.
- +/
- @property string name()
- {
- return _name;
- }
- ///ditto
- @property void name(string value)
- {
- if(_parent && _name != value)
- {
- _parent.updateId++;
-
- void removeFromGroupedLookup(string ns)
- {
- // Remove from _parent._attributes[ns]
- auto sameNameAttrs = _parent._attributes[ns][_name];
- auto targetIndex = sameNameAttrs.countUntil(this);
- _parent._attributes[ns][_name].removeIndex(targetIndex);
- }
-
- // Remove from _parent._tags
- removeFromGroupedLookup(_namespace);
- removeFromGroupedLookup("*");
-
- // Change name
- _name = value;
-
- // Add to new locations in _parent._attributes
- _parent._attributes[_namespace][_name] ~= this;
- _parent._attributes["*"][_name] ~= this;
- }
- else
- _name = value;
- }
-
- /// This tag's name, including namespace if one exists.
- deprecated("Use 'getFullName().toString()'")
- @property string fullName()
- {
- return getFullName().toString();
- }
-
- /// This tag's name, including namespace if one exists.
- FullName getFullName()
- {
- return FullName(_namespace, _name);
- }
-
- this(string namespace, string name, Value value, Location location = Location(0, 0, 0))
- {
- this._namespace = namespace;
- this._name = name;
- this.location = location;
- this.value = value;
- }
-
- this(string name, Value value, Location location = Location(0, 0, 0))
- {
- this._namespace = "";
- this._name = name;
- this.location = location;
- this.value = value;
- }
-
- /// Copy this Attribute.
- /// The clone does $(B $(I not)) have a parent, even if the original does.
- Attribute clone()
- {
- return new Attribute(_namespace, _name, value, location);
- }
-
- /// Removes `this` from its parent, if any. Returns `this` for chaining.
- /// Inefficient ATM, but it works.
- Attribute remove()
- {
- if(!_parent)
- return this;
-
- void removeFromGroupedLookup(string ns)
- {
- // Remove from _parent._attributes[ns]
- auto sameNameAttrs = _parent._attributes[ns][_name];
- auto targetIndex = sameNameAttrs.countUntil(this);
- _parent._attributes[ns][_name].removeIndex(targetIndex);
- }
-
- // Remove from _parent._attributes
- removeFromGroupedLookup(_namespace);
- removeFromGroupedLookup("*");
-
- // Remove from _parent.allAttributes
- auto allAttrsIndex = _parent.allAttributes.countUntil(this);
- _parent.allAttributes.removeIndex(allAttrsIndex);
-
- // Remove from _parent.attributeIndicies
- auto sameNamespaceAttrs = _parent.attributeIndicies[_namespace];
- auto attrIndiciesIndex = sameNamespaceAttrs.countUntil(allAttrsIndex);
- _parent.attributeIndicies[_namespace].removeIndex(attrIndiciesIndex);
-
- // Fixup other indicies
- foreach(ns, ref nsAttrIndicies; _parent.attributeIndicies)
- foreach(k, ref v; nsAttrIndicies)
- if(v > allAttrsIndex)
- v--;
-
- _parent.removeNamespaceIfEmpty(_namespace);
- _parent.updateId++;
- _parent = null;
- return this;
- }
-
- override bool opEquals(Object o)
- {
- auto a = cast(Attribute)o;
- if(!a)
- return false;
-
- return
- _namespace == a._namespace &&
- _name == a._name &&
- value == a.value;
- }
-
- string toSDLString()()
- {
- Appender!string sink;
- this.toSDLString(sink);
- return sink.data;
- }
-
- void toSDLString(Sink)(ref Sink sink) if(isOutputRange!(Sink,char))
- {
- if(_namespace != "")
- {
- sink.put(_namespace);
- sink.put(':');
- }
-
- sink.put(_name);
- sink.put('=');
- value.toSDLString(sink);
- }
-}
-
-/// Deep-copy an array of Tag or Attribute.
-/// The top-level clones are $(B $(I not)) attached to any parent, even if the originals are.
-T[] clone(T)(T[] arr) if(is(T==Tag) || is(T==Attribute))
-{
- T[] newArr;
- newArr.length = arr.length;
-
- foreach(i; 0..arr.length)
- newArr[i] = arr[i].clone();
-
- return newArr;
-}
-
-class Tag
-{
- /// File/Line/Column/Index information for where this tag was located in
- /// its original SDLang file.
- Location location;
-
- /// Access all this tag's values, as an array of type `sdlang.token.Value`.
- Value[] values;
-
- private Tag _parent;
- /// Get parent tag. To set a parent, attach this Tag to its intended
- /// parent tag by calling `Tag.add(...)`, or by passing it to
- /// the parent tag's constructor.
- @property Tag parent()
- {
- return _parent;
- }
-
- private string _namespace;
- /++
- This tag's namespace. Empty string if no namespace.
-
- Note that setting this value is O(n) because internal lookup structures
- need to be updated.
-
- Note also, that setting this may change where this tag is ordered among
- its parent's list of tags.
- +/
- @property string namespace()
- {
- return _namespace;
- }
- ///ditto
- @property void namespace(string value)
- {
- //TODO: Can we do this in-place, without removing/adding and thus
- // modyfying the internal order?
- if(_parent && _namespace != value)
- {
- // Remove
- auto saveParent = _parent;
- if(_parent)
- this.remove();
-
- // Change namespace
- _namespace = value;
-
- // Re-add
- if(saveParent)
- saveParent.add(this);
- }
- else
- _namespace = value;
- }
-
- private string _name;
- /++
- This tag's name, not including namespace.
-
- Use `getFullName().toString` if you want the namespace included.
-
- Note that setting this value is O(n) because internal lookup structures
- need to be updated.
-
- Note also, that setting this may change where this tag is ordered among
- its parent's list of tags.
- +/
- @property string name()
- {
- return _name;
- }
- ///ditto
- @property void name(string value)
- {
- //TODO: Seriously? Can't we at least do the "*" modification *in-place*?
-
- if(_parent && _name != value)
- {
- _parent.updateId++;
-
- // Not the most efficient, but it works.
- void removeFromGroupedLookup(string ns)
- {
- // Remove from _parent._tags[ns]
- auto sameNameTags = _parent._tags[ns][_name];
- auto targetIndex = sameNameTags.countUntil(this);
- _parent._tags[ns][_name].removeIndex(targetIndex);
- }
-
- // Remove from _parent._tags
- removeFromGroupedLookup(_namespace);
- removeFromGroupedLookup("*");
-
- // Change name
- _name = value;
-
- // Add to new locations in _parent._tags
- //TODO: Can we re-insert while preserving the original order?
- _parent._tags[_namespace][_name] ~= this;
- _parent._tags["*"][_name] ~= this;
- }
- else
- _name = value;
- }
-
- /// This tag's name, including namespace if one exists.
- deprecated("Use 'getFullName().toString()'")
- @property string fullName()
- {
- return getFullName().toString();
- }
-
- /// This tag's name, including namespace if one exists.
- FullName getFullName()
- {
- return FullName(_namespace, _name);
- }
-
- // Tracks dirtiness. This is incremented every time a change is made which
- // could invalidate existing ranges. This way, the ranges can detect when
- // they've been invalidated.
- private size_t updateId=0;
-
- this(Tag parent = null)
- {
- if(parent)
- parent.add(this);
- }
-
- this(
- string namespace, string name,
- Value[] values=null, Attribute[] attributes=null, Tag[] children=null
- )
- {
- this(null, namespace, name, values, attributes, children);
- }
-
- this(
- Tag parent, string namespace, string name,
- Value[] values=null, Attribute[] attributes=null, Tag[] children=null
- )
- {
- this._namespace = namespace;
- this._name = name;
-
- if(parent)
- parent.add(this);
-
- this.values = values;
- this.add(attributes);
- this.add(children);
- }
-
- /// Deep-copy this Tag.
- /// The clone does $(B $(I not)) have a parent, even if the original does.
- Tag clone()
- {
- auto newTag = new Tag(_namespace, _name, values.dup, allAttributes.clone(), allTags.clone());
- newTag.location = location;
- return newTag;
- }
-
- private Attribute[] allAttributes; // In same order as specified in SDL file.
- private Tag[] allTags; // In same order as specified in SDL file.
- private string[] allNamespaces; // In same order as specified in SDL file.
-
- private size_t[][string] attributeIndicies; // allAttributes[ attributes[namespace][i] ]
- private size_t[][string] tagIndicies; // allTags[ tags[namespace][i] ]
-
- private Attribute[][string][string] _attributes; // attributes[namespace or "*"][name][i]
- private Tag[][string][string] _tags; // tags[namespace or "*"][name][i]
-
- /// Adds a Value, Attribute, Tag (or array of such) as a member/child of this Tag.
- /// Returns `this` for chaining.
- /// Throws `ValidationException` if trying to add an Attribute or Tag
- /// that already has a parent.
- Tag add(Value val)
- {
- values ~= val;
- updateId++;
- return this;
- }
-
- ///ditto
- Tag add(Value[] vals)
- {
- foreach(val; vals)
- add(val);
-
- return this;
- }
-
- ///ditto
- Tag add(Attribute attr)
- {
- if(attr._parent)
- {
- throw new ValidationException(
- "Attribute is already attached to a parent tag. "~
- "Use Attribute.remove() before adding it to another tag."
- );
- }
-
- if(!allNamespaces.canFind(attr._namespace))
- allNamespaces ~= attr._namespace;
-
- attr._parent = this;
-
- allAttributes ~= attr;
- attributeIndicies[attr._namespace] ~= allAttributes.length-1;
- _attributes[attr._namespace][attr._name] ~= attr;
- _attributes["*"] [attr._name] ~= attr;
-
- updateId++;
- return this;
- }
-
- ///ditto
- Tag add(Attribute[] attrs)
- {
- foreach(attr; attrs)
- add(attr);
-
- return this;
- }
-
- ///ditto
- Tag add(Tag tag)
- {
- if(tag._parent)
- {
- throw new ValidationException(
- "Tag is already attached to a parent tag. "~
- "Use Tag.remove() before adding it to another tag."
- );
- }
-
- if(!allNamespaces.canFind(tag._namespace))
- allNamespaces ~= tag._namespace;
-
- tag._parent = this;
-
- allTags ~= tag;
- tagIndicies[tag._namespace] ~= allTags.length-1;
- _tags[tag._namespace][tag._name] ~= tag;
- _tags["*"] [tag._name] ~= tag;
-
- updateId++;
- return this;
- }
-
- ///ditto
- Tag add(Tag[] tags)
- {
- foreach(tag; tags)
- add(tag);
-
- return this;
- }
-
- /// Removes `this` from its parent, if any. Returns `this` for chaining.
- /// Inefficient ATM, but it works.
- Tag remove()
- {
- if(!_parent)
- return this;
-
- void removeFromGroupedLookup(string ns)
- {
- // Remove from _parent._tags[ns]
- auto sameNameTags = _parent._tags[ns][_name];
- auto targetIndex = sameNameTags.countUntil(this);
- _parent._tags[ns][_name].removeIndex(targetIndex);
- }
-
- // Remove from _parent._tags
- removeFromGroupedLookup(_namespace);
- removeFromGroupedLookup("*");
-
- // Remove from _parent.allTags
- auto allTagsIndex = _parent.allTags.countUntil(this);
- _parent.allTags.removeIndex(allTagsIndex);
-
- // Remove from _parent.tagIndicies
- auto sameNamespaceTags = _parent.tagIndicies[_namespace];
- auto tagIndiciesIndex = sameNamespaceTags.countUntil(allTagsIndex);
- _parent.tagIndicies[_namespace].removeIndex(tagIndiciesIndex);
-
- // Fixup other indicies
- foreach(ns, ref nsTagIndicies; _parent.tagIndicies)
- foreach(k, ref v; nsTagIndicies)
- if(v > allTagsIndex)
- v--;
-
- _parent.removeNamespaceIfEmpty(_namespace);
- _parent.updateId++;
- _parent = null;
- return this;
- }
-
- private void removeNamespaceIfEmpty(string namespace)
- {
- // If namespace has no attributes, remove it from attributeIndicies/_attributes
- if(namespace in attributeIndicies && attributeIndicies[namespace].length == 0)
- {
- attributeIndicies.remove(namespace);
- _attributes.remove(namespace);
- }
-
- // If namespace has no tags, remove it from tagIndicies/_tags
- if(namespace in tagIndicies && tagIndicies[namespace].length == 0)
- {
- tagIndicies.remove(namespace);
- _tags.remove(namespace);
- }
-
- // If namespace is now empty, remove it from allNamespaces
- if(
- namespace !in tagIndicies &&
- namespace !in attributeIndicies
- )
- {
- auto allNamespacesIndex = allNamespaces.length - allNamespaces.find(namespace).length;
- allNamespaces = allNamespaces[0..allNamespacesIndex] ~ allNamespaces[allNamespacesIndex+1..$];
- }
- }
-
- struct NamedMemberRange(T, string membersGrouped)
- {
- private Tag tag;
- private string namespace; // "*" indicates "all namespaces" (ok since it's not a valid namespace name)
- private string name;
- private size_t updateId; // Tag's updateId when this range was created.
-
- this(Tag tag, string namespace, string name, size_t updateId)
- {
- this.tag = tag;
- this.namespace = namespace;
- this.name = name;
- this.updateId = updateId;
- frontIndex = 0;
-
- if(
- tag !is null &&
- namespace in mixin("tag."~membersGrouped) &&
- name in mixin("tag."~membersGrouped~"[namespace]")
- )
- endIndex = mixin("tag."~membersGrouped~"[namespace][name].length");
- else
- endIndex = 0;
- }
-
- invariant()
- {
- assert(
- this.updateId == tag.updateId,
- "This range has been invalidated by a change to the tag."
- );
- }
-
- @property bool empty()
- {
- return tag is null || frontIndex == endIndex;
- }
-
- private size_t frontIndex;
- @property T front()
- {
- return this[0];
- }
- void popFront()
- {
- if(empty)
- throw new DOMRangeException(tag, "Range is empty");
-
- frontIndex++;
- }
-
- private size_t endIndex; // One past the last element
- @property T back()
- {
- return this[$-1];
- }
- void popBack()
- {
- if(empty)
- throw new DOMRangeException(tag, "Range is empty");
-
- endIndex--;
- }
-
- alias length opDollar;
- @property size_t length()
- {
- return endIndex - frontIndex;
- }
-
- @property typeof(this) save()
- {
- auto r = typeof(this)(this.tag, this.namespace, this.name, this.updateId);
- r.frontIndex = this.frontIndex;
- r.endIndex = this.endIndex;
- return r;
- }
-
- typeof(this) opSlice()
- {
- return save();
- }
-
- typeof(this) opSlice(size_t start, size_t end)
- {
- auto r = save();
- r.frontIndex = this.frontIndex + start;
- r.endIndex = this.frontIndex + end;
-
- if(
- r.frontIndex > this.endIndex ||
- r.endIndex > this.endIndex ||
- r.frontIndex > r.endIndex
- )
- throw new DOMRangeException(tag, "Slice out of range");
-
- return r;
- }
-
- T opIndex(size_t index)
- {
- if(empty)
- throw new DOMRangeException(tag, "Range is empty");
-
- return mixin("tag."~membersGrouped~"[namespace][name][frontIndex+index]");
- }
- }
-
- struct MemberRange(T, string allMembers, string memberIndicies, string membersGrouped)
- {
- private Tag tag;
- private string namespace; // "*" indicates "all namespaces" (ok since it's not a valid namespace name)
- private bool isMaybe;
- private size_t updateId; // Tag's updateId when this range was created.
- private size_t initialEndIndex;
-
- this(Tag tag, string namespace, bool isMaybe)
- {
- this.tag = tag;
- this.namespace = namespace;
- this.updateId = tag.updateId;
- this.isMaybe = isMaybe;
- frontIndex = 0;
-
- if(tag is null)
- endIndex = 0;
- else
- {
-
- if(namespace == "*")
- initialEndIndex = mixin("tag."~allMembers~".length");
- else if(namespace in mixin("tag."~memberIndicies))
- initialEndIndex = mixin("tag."~memberIndicies~"[namespace].length");
- else
- initialEndIndex = 0;
-
- endIndex = initialEndIndex;
- }
- }
-
- invariant()
- {
- assert(
- this.updateId == tag.updateId,
- "This range has been invalidated by a change to the tag."
- );
- }
-
- @property bool empty()
- {
- return tag is null || frontIndex == endIndex;
- }
-
- private size_t frontIndex;
- @property T front()
- {
- return this[0];
- }
- void popFront()
- {
- if(empty)
- throw new DOMRangeException(tag, "Range is empty");
-
- frontIndex++;
- }
-
- private size_t endIndex; // One past the last element
- @property T back()
- {
- return this[$-1];
- }
- void popBack()
- {
- if(empty)
- throw new DOMRangeException(tag, "Range is empty");
-
- endIndex--;
- }
-
- alias length opDollar;
- @property size_t length()
- {
- return endIndex - frontIndex;
- }
-
- @property typeof(this) save()
- {
- auto r = typeof(this)(this.tag, this.namespace, this.isMaybe);
- r.frontIndex = this.frontIndex;
- r.endIndex = this.endIndex;
- r.initialEndIndex = this.initialEndIndex;
- r.updateId = this.updateId;
- return r;
- }
-
- typeof(this) opSlice()
- {
- return save();
- }
-
- typeof(this) opSlice(size_t start, size_t end)
- {
- auto r = save();
- r.frontIndex = this.frontIndex + start;
- r.endIndex = this.frontIndex + end;
-
- if(
- r.frontIndex > this.endIndex ||
- r.endIndex > this.endIndex ||
- r.frontIndex > r.endIndex
- )
- throw new DOMRangeException(tag, "Slice out of range");
-
- return r;
- }
-
- T opIndex(size_t index)
- {
- if(empty)
- throw new DOMRangeException(tag, "Range is empty");
-
- if(namespace == "*")
- return mixin("tag."~allMembers~"[ frontIndex+index ]");
- else
- return mixin("tag."~allMembers~"[ tag."~memberIndicies~"[namespace][frontIndex+index] ]");
- }
-
- alias NamedMemberRange!(T,membersGrouped) ThisNamedMemberRange;
- ThisNamedMemberRange opIndex(string name)
- {
- if(frontIndex != 0 || endIndex != initialEndIndex)
- {
- throw new DOMRangeException(tag,
- "Cannot lookup tags/attributes by name on a subset of a range, "~
- "only across the entire tag. "~
- "Please make sure you haven't called popFront or popBack on this "~
- "range and that you aren't using a slice of the range."
- );
- }
-
- if(!isMaybe && empty)
- throw new DOMRangeException(tag, "Range is empty");
-
- if(!isMaybe && name !in this)
- throw new DOMRangeException(tag, `No such `~T.stringof~` named: "`~name~`"`);
-
- return ThisNamedMemberRange(tag, namespace, name, updateId);
- }
-
- bool opBinaryRight(string op)(string name) if(op=="in")
- {
- if(frontIndex != 0 || endIndex != initialEndIndex)
- {
- throw new DOMRangeException(tag,
- "Cannot lookup tags/attributes by name on a subset of a range, "~
- "only across the entire tag. "~
- "Please make sure you haven't called popFront or popBack on this "~
- "range and that you aren't using a slice of the range."
- );
- }
-
- if(tag is null)
- return false;
-
- return
- namespace in mixin("tag."~membersGrouped) &&
- name in mixin("tag."~membersGrouped~"[namespace]") &&
- mixin("tag."~membersGrouped~"[namespace][name].length") > 0;
- }
- }
-
- struct NamespaceRange
- {
- private Tag tag;
- private bool isMaybe;
- private size_t updateId; // Tag's updateId when this range was created.
-
- this(Tag tag, bool isMaybe)
- {
- this.tag = tag;
- this.isMaybe = isMaybe;
- this.updateId = tag.updateId;
- frontIndex = 0;
- endIndex = tag.allNamespaces.length;
- }
-
- invariant()
- {
- assert(
- this.updateId == tag.updateId,
- "This range has been invalidated by a change to the tag."
- );
- }
-
- @property bool empty()
- {
- return frontIndex == endIndex;
- }
-
- private size_t frontIndex;
- @property NamespaceAccess front()
- {
- return this[0];
- }
- void popFront()
- {
- if(empty)
- throw new DOMRangeException(tag, "Range is empty");
-
- frontIndex++;
- }
-
- private size_t endIndex; // One past the last element
- @property NamespaceAccess back()
- {
- return this[$-1];
- }
- void popBack()
- {
- if(empty)
- throw new DOMRangeException(tag, "Range is empty");
-
- endIndex--;
- }
-
- alias length opDollar;
- @property size_t length()
- {
- return endIndex - frontIndex;
- }
-
- @property NamespaceRange save()
- {
- auto r = NamespaceRange(this.tag, this.isMaybe);
- r.frontIndex = this.frontIndex;
- r.endIndex = this.endIndex;
- r.updateId = this.updateId;
- return r;
- }
-
- typeof(this) opSlice()
- {
- return save();
- }
-
- typeof(this) opSlice(size_t start, size_t end)
- {
- auto r = save();
- r.frontIndex = this.frontIndex + start;
- r.endIndex = this.frontIndex + end;
-
- if(
- r.frontIndex > this.endIndex ||
- r.endIndex > this.endIndex ||
- r.frontIndex > r.endIndex
- )
- throw new DOMRangeException(tag, "Slice out of range");
-
- return r;
- }
-
- NamespaceAccess opIndex(size_t index)
- {
- if(empty)
- throw new DOMRangeException(tag, "Range is empty");
-
- auto namespace = tag.allNamespaces[frontIndex+index];
- return NamespaceAccess(
- namespace,
- AttributeRange(tag, namespace, isMaybe),
- TagRange(tag, namespace, isMaybe)
- );
- }
-
- NamespaceAccess opIndex(string namespace)
- {
- if(!isMaybe && empty)
- throw new DOMRangeException(tag, "Range is empty");
-
- if(!isMaybe && namespace !in this)
- throw new DOMRangeException(tag, `No such namespace: "`~namespace~`"`);
-
- return NamespaceAccess(
- namespace,
- AttributeRange(tag, namespace, isMaybe),
- TagRange(tag, namespace, isMaybe)
- );
- }
-
- /// Inefficient when range is a slice or has used popFront/popBack, but it works.
- bool opBinaryRight(string op)(string namespace) if(op=="in")
- {
- if(frontIndex == 0 && endIndex == tag.allNamespaces.length)
- {
- return
- namespace in tag.attributeIndicies ||
- namespace in tag.tagIndicies;
- }
- else
- // Slower fallback method
- return tag.allNamespaces[frontIndex..endIndex].canFind(namespace);
- }
- }
-
- static struct NamespaceAccess
- {
- string name;
- AttributeRange attributes;
- TagRange tags;
- }
-
- alias MemberRange!(Attribute, "allAttributes", "attributeIndicies", "_attributes") AttributeRange;
- alias MemberRange!(Tag, "allTags", "tagIndicies", "_tags" ) TagRange;
- static assert(isRandomAccessRange!AttributeRange);
- static assert(isRandomAccessRange!TagRange);
- static assert(isRandomAccessRange!NamespaceRange);
-
- /++
- Access all attributes that don't have a namespace
-
- Returns a random access range of `Attribute` objects that supports
- numeric-indexing, string-indexing, slicing and length.
-
- Since SDLang allows multiple attributes with the same name,
- string-indexing returns a random access range of all attributes
- with the given name.
-
- The string-indexing does $(B $(I not)) support namespace prefixes.
- Use `namespace[string]`.`attributes` or `all`.`attributes` for that.
-
- See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview)
- for a high-level overview (and examples) of how to use this.
- +/
- @property AttributeRange attributes()
- {
- return AttributeRange(this, "", false);
- }
-
- /++
- Access all direct-child tags that don't have a namespace.
-
- Returns a random access range of `Tag` objects that supports
- numeric-indexing, string-indexing, slicing and length.
-
- Since SDLang allows multiple tags with the same name, string-indexing
- returns a random access range of all immediate child tags with the
- given name.
-
- The string-indexing does $(B $(I not)) support namespace prefixes.
- Use `namespace[string]`.`attributes` or `all`.`attributes` for that.
-
- See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview)
- for a high-level overview (and examples) of how to use this.
- +/
- @property TagRange tags()
- {
- return TagRange(this, "", false);
- }
-
- /++
- Access all namespaces in this tag, and the attributes/tags within them.
-
- Returns a random access range of `NamespaceAccess` elements that supports
- numeric-indexing, string-indexing, slicing and length.
-
- See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview)
- for a high-level overview (and examples) of how to use this.
- +/
- @property NamespaceRange namespaces()
- {
- return NamespaceRange(this, false);
- }
-
- /// Access all attributes and tags regardless of namespace.
- ///
- /// See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview)
- /// for a better understanding (and examples) of how to use this.
- @property NamespaceAccess all()
- {
- // "*" isn't a valid namespace name, so we can use it to indicate "all namespaces"
- return NamespaceAccess(
- "*",
- AttributeRange(this, "*", false),
- TagRange(this, "*", false)
- );
- }
-
- struct MaybeAccess
- {
- Tag tag;
-
- /// Access all attributes that don't have a namespace
- @property AttributeRange attributes()
- {
- return AttributeRange(tag, "", true);
- }
-
- /// Access all direct-child tags that don't have a namespace
- @property TagRange tags()
- {
- return TagRange(tag, "", true);
- }
-
- /// Access all namespaces in this tag, and the attributes/tags within them.
- @property NamespaceRange namespaces()
- {
- return NamespaceRange(tag, true);
- }
-
- /// Access all attributes and tags regardless of namespace.
- @property NamespaceAccess all()
- {
- // "*" isn't a valid namespace name, so we can use it to indicate "all namespaces"
- return NamespaceAccess(
- "*",
- AttributeRange(tag, "*", true),
- TagRange(tag, "*", true)
- );
- }
- }
-
- /// Access `attributes`, `tags`, `namespaces` and `all` like normal,
- /// except that looking up a non-existant name/namespace with
- /// opIndex(string) results in an empty array instead of
- /// a thrown `sdlang.exception.DOMRangeException`.
- ///
- /// See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview)
- /// for a more information (and examples) of how to use this.
- @property MaybeAccess maybe()
- {
- return MaybeAccess(this);
- }
-
- // Internal implementations for the get/expect functions further below:
-
- private Tag getTagImpl(FullName tagFullName, Tag defaultValue=null, bool useDefaultValue=true)
- {
- auto tagNS = tagFullName.namespace;
- auto tagName = tagFullName.name;
-
- // Can find namespace?
- if(tagNS !in _tags)
- {
- if(useDefaultValue)
- return defaultValue;
- else
- throw new TagNotFoundException(this, tagFullName, "No tags found in namespace '"~namespace~"'");
- }
-
- // Can find tag in namespace?
- if(tagName !in _tags[tagNS] || _tags[tagNS][tagName].length == 0)
- {
- if(useDefaultValue)
- return defaultValue;
- else
- throw new TagNotFoundException(this, tagFullName, "Can't find tag '"~tagFullName.toString()~"'");
- }
-
- // Return last matching tag found
- return _tags[tagNS][tagName][$-1];
- }
-
- private T getValueImpl(T)(T defaultValue, bool useDefaultValue=true)
- if(isValueType!T)
- {
- // Find value
- foreach(value; this.values)
- {
- if(value.type == typeid(T))
- return value.get!T();
- }
-
- // No value of type T found
- if(useDefaultValue)
- return defaultValue;
- else
- {
- throw new ValueNotFoundException(
- this,
- FullName(this.namespace, this.name),
- typeid(T),
- "No value of type "~T.stringof~" found."
- );
- }
- }
-
- private T getAttributeImpl(T)(FullName attrFullName, T defaultValue, bool useDefaultValue=true)
- if(isValueType!T)
- {
- auto attrNS = attrFullName.namespace;
- auto attrName = attrFullName.name;
-
- // Can find namespace and attribute name?
- if(attrNS !in this._attributes || attrName !in this._attributes[attrNS])
- {
- if(useDefaultValue)
- return defaultValue;
- else
- {
- throw new AttributeNotFoundException(
- this, this.getFullName(), attrFullName, typeid(T),
- "Can't find attribute '"~FullName.combine(attrNS, attrName)~"'"
- );
- }
- }
-
- // Find value with chosen type
- foreach(attr; this._attributes[attrNS][attrName])
- {
- if(attr.value.type == typeid(T))
- return attr.value.get!T();
- }
-
- // Chosen type not found
- if(useDefaultValue)
- return defaultValue;
- else
- {
- throw new AttributeNotFoundException(
- this, this.getFullName(), attrFullName, typeid(T),
- "Can't find attribute '"~FullName.combine(attrNS, attrName)~"' of type "~T.stringof
- );
- }
- }
-
- // High-level interfaces for get/expect funtions:
-
- /++
- Lookup a child tag by name. Returns null if not found.
-
- Useful if you only expect one, and only one, child tag of a given name.
- Only looks for immediate child tags of `this`, doesn't search recursively.
-
- If you expect multiple tags by the same name and want to get them all,
- use `maybe`.`tags[string]` instead.
-
- The name can optionally include a namespace, as in `"namespace:name"`.
- Or, you can search all namespaces using `"*:name"`. Use an empty string
- to search for anonymous tags, or `"namespace:"` for anonymous tags inside
- a namespace. Wildcard searching is only supported for namespaces, not names.
- Use `maybe`.`tags[0]` if you don't care about the name.
-
- If there are multiple tags by the chosen name, the $(B $(I last tag)) will
- always be chosen. That is, this function considers later tags with the
- same name to override previous ones.
-
- If the tag cannot be found, and you provides a default value, the default
- value is returned. Otherwise null is returned. If you'd prefer an
- exception thrown, use `expectTag` instead.
- +/
- Tag getTag(string fullTagName, Tag defaultValue=null)
- {
- auto parsedName = FullName.parse(fullTagName);
- parsedName.ensureNoWildcardName(
- "Instead, use 'Tag.maybe.tags[0]', 'Tag.maybe.all.tags[0]' or 'Tag.maybe.namespace[ns].tags[0]'."
- );
- return getTagImpl(parsedName, defaultValue);
- }
-
- ///
- @("Tag.getTag")
- unittest
- {
- import std.exception;
- import sdlang.parser;
-
- auto root = parseSource(`
- foo 1
- foo 2 // getTag considers this to override the first foo
-
- ns1:foo 3
- ns1:foo 4 // getTag considers this to override the first ns1:foo
- ns2:foo 33
- ns2:foo 44 // getTag considers this to override the first ns2:foo
- `);
- assert( root.getTag("foo" ).values[0].get!int() == 2 );
- assert( root.getTag("ns1:foo").values[0].get!int() == 4 );
- assert( root.getTag("*:foo" ).values[0].get!int() == 44 ); // Search all namespaces
-
- // Not found
- // If you'd prefer an exception, use `expectTag` instead.
- assert( root.getTag("doesnt-exist") is null );
-
- // Default value
- auto foo = root.getTag("foo");
- assert( root.getTag("doesnt-exist", foo) is foo );
- }
-
- /++
- Lookup a child tag by name. Throws if not found.
-
- Useful if you only expect one, and only one, child tag of a given name.
- Only looks for immediate child tags of `this`, doesn't search recursively.
-
- If you expect multiple tags by the same name and want to get them all,
- use `tags[string]` instead.
-
- The name can optionally include a namespace, as in `"namespace:name"`.
- Or, you can search all namespaces using `"*:name"`. Use an empty string
- to search for anonymous tags, or `"namespace:"` for anonymous tags inside
- a namespace. Wildcard searching is only supported for namespaces, not names.
- Use `tags[0]` if you don't care about the name.
-
- If there are multiple tags by the chosen name, the $(B $(I last tag)) will
- always be chosen. That is, this function considers later tags with the
- same name to override previous ones.
-
- If no such tag is found, an `sdlang.exception.TagNotFoundException` will
- be thrown. If you'd rather receive a default value, use `getTag` instead.
- +/
- Tag expectTag(string fullTagName)
- {
- auto parsedName = FullName.parse(fullTagName);
- parsedName.ensureNoWildcardName(
- "Instead, use 'Tag.tags[0]', 'Tag.all.tags[0]' or 'Tag.namespace[ns].tags[0]'."
- );
- return getTagImpl(parsedName, null, false);
- }
-
- ///
- @("Tag.expectTag")
- unittest
- {
- import std.exception;
- import sdlang.parser;
-
- auto root = parseSource(`
- foo 1
- foo 2 // expectTag considers this to override the first foo
-
- ns1:foo 3
- ns1:foo 4 // expectTag considers this to override the first ns1:foo
- ns2:foo 33
- ns2:foo 44 // expectTag considers this to override the first ns2:foo
- `);
- assert( root.expectTag("foo" ).values[0].get!int() == 2 );
- assert( root.expectTag("ns1:foo").values[0].get!int() == 4 );
- assert( root.expectTag("*:foo" ).values[0].get!int() == 44 ); // Search all namespaces
-
- // Not found
- // If you'd rather receive a default value than an exception, use `getTag` instead.
- assertThrown!TagNotFoundException( root.expectTag("doesnt-exist") );
- }
-
- /++
- Retrieve a value of type T from `this` tag. Returns a default value if not found.
-
- Useful if you only expect one value of type T from this tag. Only looks for
- values of `this` tag, it does not search child tags. If you wish to search
- for a value in a child tag (for example, if this current tag is a root tag),
- try `getTagValue`.
-
- If you want to get more than one value from this tag, use `values` instead.
-
- If this tag has multiple values, the $(B $(I first)) value matching the
- requested type will be returned. Ie, Extra values in the tag are ignored.
-
- You may provide a default value to be returned in case no value of
- the requested type can be found. If you don't provide a default value,
- `T.init` will be used.
-
- If you'd rather an exception be thrown when a value cannot be found,
- use `expectValue` instead.
- +/
- T getValue(T)(T defaultValue = T.init) if(isValueType!T)
- {
- return getValueImpl!T(defaultValue, true);
- }
-
- ///
- @("Tag.getValue")
- unittest
- {
- import std.exception;
- import std.math;
- import sdlang.parser;
-
- auto root = parseSource(`
- foo 1 true 2 false
- `);
- auto foo = root.getTag("foo");
- assert( foo.getValue!int() == 1 );
- assert( foo.getValue!bool() == true );
-
- // Value found, default value ignored.
- assert( foo.getValue!int(999) == 1 );
-
- // No strings found
- // If you'd prefer an exception, use `expectValue` instead.
- assert( foo.getValue!string("Default") == "Default" );
- assert( foo.getValue!string() is null );
-
- // No floats found
- assert( foo.getValue!float(99.9).approxEqual(99.9) );
- assert( foo.getValue!float().isNaN() );
- }
-
- /++
- Retrieve a value of type T from `this` tag. Throws if not found.
-
- Useful if you only expect one value of type T from this tag. Only looks
- for values of `this` tag, it does not search child tags. If you wish to
- search for a value in a child tag (for example, if this current tag is a
- root tag), try `expectTagValue`.
-
- If you want to get more than one value from this tag, use `values` instead.
-
- If this tag has multiple values, the $(B $(I first)) value matching the
- requested type will be returned. Ie, Extra values in the tag are ignored.
-
- An `sdlang.exception.ValueNotFoundException` will be thrown if no value of
- the requested type can be found. If you'd rather receive a default value,
- use `getValue` instead.
- +/
- T expectValue(T)() if(isValueType!T)
- {
- return getValueImpl!T(T.init, false);
- }
-
- ///
- @("Tag.expectValue")
- unittest
- {
- import std.exception;
- import std.math;
- import sdlang.parser;
-
- auto root = parseSource(`
- foo 1 true 2 false
- `);
- auto foo = root.getTag("foo");
- assert( foo.expectValue!int() == 1 );
- assert( foo.expectValue!bool() == true );
-
- // No strings or floats found
- // If you'd rather receive a default value than an exception, use `getValue` instead.
- assertThrown!ValueNotFoundException( foo.expectValue!string() );
- assertThrown!ValueNotFoundException( foo.expectValue!float() );
- }
-
- /++
- Lookup a child tag by name, and retrieve a value of type T from it.
- Returns a default value if not found.
-
- Useful if you only expect one value of type T from a given tag. Only looks
- for immediate child tags of `this`, doesn't search recursively.
-
- This is a shortcut for `getTag().getValue()`, except if the tag isn't found,
- then instead of a null reference error, it will return the requested
- `defaultValue` (or T.init by default).
- +/
- T getTagValue(T)(string fullTagName, T defaultValue = T.init) if(isValueType!T)
- {
- auto tag = getTag(fullTagName);
- if(!tag)
- return defaultValue;
-
- return tag.getValue!T(defaultValue);
- }
-
- ///
- @("Tag.getTagValue")
- unittest
- {
- import std.exception;
- import sdlang.parser;
-
- auto root = parseSource(`
- foo 1 "a" 2 "b"
- foo 3 "c" 4 "d" // getTagValue considers this to override the first foo
-
- bar "hi"
- bar 379 // getTagValue considers this to override the first bar
- `);
- assert( root.getTagValue!int("foo") == 3 );
- assert( root.getTagValue!string("foo") == "c" );
-
- // Value found, default value ignored.
- assert( root.getTagValue!int("foo", 999) == 3 );
-
- // Tag not found
- // If you'd prefer an exception, use `expectTagValue` instead.
- assert( root.getTagValue!int("doesnt-exist", 999) == 999 );
- assert( root.getTagValue!int("doesnt-exist") == 0 );
-
- // The last "bar" tag doesn't have an int (only the first "bar" tag does)
- assert( root.getTagValue!string("bar", "Default") == "Default" );
- assert( root.getTagValue!string("bar") is null );
-
- // Using namespaces:
- root = parseSource(`
- ns1:foo 1 "a" 2 "b"
- ns1:foo 3 "c" 4 "d"
- ns2:foo 11 "aa" 22 "bb"
- ns2:foo 33 "cc" 44 "dd"
-
- ns1:bar "hi"
- ns1:bar 379 // getTagValue considers this to override the first bar
- `);
- assert( root.getTagValue!int("ns1:foo") == 3 );
- assert( root.getTagValue!int("*:foo" ) == 33 ); // Search all namespaces
-
- assert( root.getTagValue!string("ns1:foo") == "c" );
- assert( root.getTagValue!string("*:foo" ) == "cc" ); // Search all namespaces
-
- // The last "bar" tag doesn't have a string (only the first "bar" tag does)
- assert( root.getTagValue!string("*:bar", "Default") == "Default" );
- assert( root.getTagValue!string("*:bar") is null );
- }
-
- /++
- Lookup a child tag by name, and retrieve a value of type T from it.
- Throws if not found,
-
- Useful if you only expect one value of type T from a given tag. Only
- looks for immediate child tags of `this`, doesn't search recursively.
-
- This is a shortcut for `expectTag().expectValue()`.
- +/
- T expectTagValue(T)(string fullTagName) if(isValueType!T)
- {
- return expectTag(fullTagName).expectValue!T();
- }
-
- ///
- @("Tag.expectTagValue")
- unittest
- {
- import std.exception;
- import sdlang.parser;
-
- auto root = parseSource(`
- foo 1 "a" 2 "b"
- foo 3 "c" 4 "d" // expectTagValue considers this to override the first foo
-
- bar "hi"
- bar 379 // expectTagValue considers this to override the first bar
- `);
- assert( root.expectTagValue!int("foo") == 3 );
- assert( root.expectTagValue!string("foo") == "c" );
-
- // The last "bar" tag doesn't have a string (only the first "bar" tag does)
- // If you'd rather receive a default value than an exception, use `getTagValue` instead.
- assertThrown!ValueNotFoundException( root.expectTagValue!string("bar") );
-
- // Tag not found
- assertThrown!TagNotFoundException( root.expectTagValue!int("doesnt-exist") );
-
- // Using namespaces:
- root = parseSource(`
- ns1:foo 1 "a" 2 "b"
- ns1:foo 3 "c" 4 "d"
- ns2:foo 11 "aa" 22 "bb"
- ns2:foo 33 "cc" 44 "dd"
-
- ns1:bar "hi"
- ns1:bar 379 // expectTagValue considers this to override the first bar
- `);
- assert( root.expectTagValue!int("ns1:foo") == 3 );
- assert( root.expectTagValue!int("*:foo" ) == 33 ); // Search all namespaces
-
- assert( root.expectTagValue!string("ns1:foo") == "c" );
- assert( root.expectTagValue!string("*:foo" ) == "cc" ); // Search all namespaces
-
- // The last "bar" tag doesn't have a string (only the first "bar" tag does)
- assertThrown!ValueNotFoundException( root.expectTagValue!string("*:bar") );
-
- // Namespace not found
- assertThrown!TagNotFoundException( root.expectTagValue!int("doesnt-exist:bar") );
- }
-
- /++
- Lookup an attribute of `this` tag by name, and retrieve a value of type T
- from it. Returns a default value if not found.
-
- Useful if you only expect one attribute of the given name and type.
-
- Only looks for attributes of `this` tag, it does not search child tags.
- If you wish to search for a value in a child tag (for example, if this
- current tag is a root tag), try `getTagAttribute`.
-
- If you expect multiple attributes by the same name and want to get them all,
- use `maybe`.`attributes[string]` instead.
-
- The attribute name can optionally include a namespace, as in
- `"namespace:name"`. Or, you can search all namespaces using `"*:name"`.
- (Note that unlike tags. attributes can't be anonymous - that's what
- values are.) Wildcard searching is only supported for namespaces, not names.
- Use `maybe`.`attributes[0]` if you don't care about the name.
-
- If this tag has multiple attributes, the $(B $(I first)) attribute
- matching the requested name and type will be returned. Ie, Extra
- attributes in the tag are ignored.
-
- You may provide a default value to be returned in case no attribute of
- the requested name and type can be found. If you don't provide a default
- value, `T.init` will be used.
-
- If you'd rather an exception be thrown when an attribute cannot be found,
- use `expectAttribute` instead.
- +/
- T getAttribute(T)(string fullAttributeName, T defaultValue = T.init) if(isValueType!T)
- {
- auto parsedName = FullName.parse(fullAttributeName);
- parsedName.ensureNoWildcardName(
- "Instead, use 'Attribute.maybe.tags[0]', 'Attribute.maybe.all.tags[0]' or 'Attribute.maybe.namespace[ns].tags[0]'."
- );
- return getAttributeImpl!T(parsedName, defaultValue);
- }
-
- ///
- @("Tag.getAttribute")
- unittest
- {
- import std.exception;
- import std.math;
- import sdlang.parser;
-
- auto root = parseSource(`
- foo z=0 X=1 X=true X=2 X=false
- `);
- auto foo = root.getTag("foo");
- assert( foo.getAttribute!int("X") == 1 );
- assert( foo.getAttribute!bool("X") == true );
-
- // Value found, default value ignored.
- assert( foo.getAttribute!int("X", 999) == 1 );
-
- // Attribute name not found
- // If you'd prefer an exception, use `expectValue` instead.
- assert( foo.getAttribute!int("doesnt-exist", 999) == 999 );
- assert( foo.getAttribute!int("doesnt-exist") == 0 );
-
- // No strings found
- assert( foo.getAttribute!string("X", "Default") == "Default" );
- assert( foo.getAttribute!string("X") is null );
-
- // No floats found
- assert( foo.getAttribute!float("X", 99.9).approxEqual(99.9) );
- assert( foo.getAttribute!float("X").isNaN() );
-
-
- // Using namespaces:
- root = parseSource(`
- foo ns1:z=0 ns1:X=1 ns1:X=2 ns2:X=3 ns2:X=4
- `);
- foo = root.getTag("foo");
- assert( foo.getAttribute!int("ns2:X") == 3 );
- assert( foo.getAttribute!int("*:X") == 1 ); // Search all namespaces
-
- // Namespace not found
- assert( foo.getAttribute!int("doesnt-exist:X", 999) == 999 );
-
- // No attribute X is in the default namespace
- assert( foo.getAttribute!int("X", 999) == 999 );
-
- // Attribute name not found
- assert( foo.getAttribute!int("ns1:doesnt-exist", 999) == 999 );
- }
-
- /++
- Lookup an attribute of `this` tag by name, and retrieve a value of type T
- from it. Throws if not found.
-
- Useful if you only expect one attribute of the given name and type.
-
- Only looks for attributes of `this` tag, it does not search child tags.
- If you wish to search for a value in a child tag (for example, if this
- current tag is a root tag), try `expectTagAttribute`.
-
- If you expect multiple attributes by the same name and want to get them all,
- use `attributes[string]` instead.
-
- The attribute name can optionally include a namespace, as in
- `"namespace:name"`. Or, you can search all namespaces using `"*:name"`.
- (Note that unlike tags. attributes can't be anonymous - that's what
- values are.) Wildcard searching is only supported for namespaces, not names.
- Use `attributes[0]` if you don't care about the name.
-
- If this tag has multiple attributes, the $(B $(I first)) attribute
- matching the requested name and type will be returned. Ie, Extra
- attributes in the tag are ignored.
-
- An `sdlang.exception.AttributeNotFoundException` will be thrown if no
- value of the requested type can be found. If you'd rather receive a
- default value, use `getAttribute` instead.
- +/
- T expectAttribute(T)(string fullAttributeName) if(isValueType!T)
- {
- auto parsedName = FullName.parse(fullAttributeName);
- parsedName.ensureNoWildcardName(
- "Instead, use 'Attribute.tags[0]', 'Attribute.all.tags[0]' or 'Attribute.namespace[ns].tags[0]'."
- );
- return getAttributeImpl!T(parsedName, T.init, false);
- }
-
- ///
- @("Tag.expectAttribute")
- unittest
- {
- import std.exception;
- import std.math;
- import sdlang.parser;
-
- auto root = parseSource(`
- foo z=0 X=1 X=true X=2 X=false
- `);
- auto foo = root.getTag("foo");
- assert( foo.expectAttribute!int("X") == 1 );
- assert( foo.expectAttribute!bool("X") == true );
-
- // Attribute name not found
- // If you'd rather receive a default value than an exception, use `getAttribute` instead.
- assertThrown!AttributeNotFoundException( foo.expectAttribute!int("doesnt-exist") );
-
- // No strings found
- assertThrown!AttributeNotFoundException( foo.expectAttribute!string("X") );
-
- // No floats found
- assertThrown!AttributeNotFoundException( foo.expectAttribute!float("X") );
-
-
- // Using namespaces:
- root = parseSource(`
- foo ns1:z=0 ns1:X=1 ns1:X=2 ns2:X=3 ns2:X=4
- `);
- foo = root.getTag("foo");
- assert( foo.expectAttribute!int("ns2:X") == 3 );
- assert( foo.expectAttribute!int("*:X") == 1 ); // Search all namespaces
-
- // Namespace not found
- assertThrown!AttributeNotFoundException( foo.expectAttribute!int("doesnt-exist:X") );
-
- // No attribute X is in the default namespace
- assertThrown!AttributeNotFoundException( foo.expectAttribute!int("X") );
-
- // Attribute name not found
- assertThrown!AttributeNotFoundException( foo.expectAttribute!int("ns1:doesnt-exist") );
- }
-
- /++
- Lookup a child tag and attribute by name, and retrieve a value of type T
- from it. Returns a default value if not found.
-
- Useful if you only expect one attribute of type T from given
- the tag and attribute names. Only looks for immediate child tags of
- `this`, doesn't search recursively.
-
- This is a shortcut for `getTag().getAttribute()`, except if the tag isn't
- found, then instead of a null reference error, it will return the requested
- `defaultValue` (or T.init by default).
- +/
- T getTagAttribute(T)(string fullTagName, string fullAttributeName, T defaultValue = T.init) if(isValueType!T)
- {
- auto tag = getTag(fullTagName);
- if(!tag)
- return defaultValue;
-
- return tag.getAttribute!T(fullAttributeName, defaultValue);
- }
-
- ///
- @("Tag.getTagAttribute")
- unittest
- {
- import std.exception;
- import sdlang.parser;
-
- auto root = parseSource(`
- foo X=1 X="a" X=2 X="b"
- foo X=3 X="c" X=4 X="d" // getTagAttribute considers this to override the first foo
-
- bar X="hi"
- bar X=379 // getTagAttribute considers this to override the first bar
- `);
- assert( root.getTagAttribute!int("foo", "X") == 3 );
- assert( root.getTagAttribute!string("foo", "X") == "c" );
-
- // Value found, default value ignored.
- assert( root.getTagAttribute!int("foo", "X", 999) == 3 );
-
- // Tag not found
- // If you'd prefer an exception, use `expectTagAttribute` instead of `getTagAttribute`
- assert( root.getTagAttribute!int("doesnt-exist", "X", 999) == 999 );
- assert( root.getTagAttribute!int("doesnt-exist", "X") == 0 );
- assert( root.getTagAttribute!int("foo", "doesnt-exist", 999) == 999 );
- assert( root.getTagAttribute!int("foo", "doesnt-exist") == 0 );
-
- // The last "bar" tag doesn't have a string (only the first "bar" tag does)
- assert( root.getTagAttribute!string("bar", "X", "Default") == "Default" );
- assert( root.getTagAttribute!string("bar", "X") is null );
-
-
- // Using namespaces:
- root = parseSource(`
- ns1:foo X=1 X="a" X=2 X="b"
- ns1:foo X=3 X="c" X=4 X="d"
- ns2:foo X=11 X="aa" X=22 X="bb"
- ns2:foo X=33 X="cc" X=44 X="dd"
-
- ns1:bar attrNS:X="hi"
- ns1:bar attrNS:X=379 // getTagAttribute considers this to override the first bar
- `);
- assert( root.getTagAttribute!int("ns1:foo", "X") == 3 );
- assert( root.getTagAttribute!int("*:foo", "X") == 33 ); // Search all namespaces
-
- assert( root.getTagAttribute!string("ns1:foo", "X") == "c" );
- assert( root.getTagAttribute!string("*:foo", "X") == "cc" ); // Search all namespaces
-
- // bar's attribute X is't in the default namespace
- assert( root.getTagAttribute!int("*:bar", "X", 999) == 999 );
- assert( root.getTagAttribute!int("*:bar", "X") == 0 );
-
- // The last "bar" tag's "attrNS:X" attribute doesn't have a string (only the first "bar" tag does)
- assert( root.getTagAttribute!string("*:bar", "attrNS:X", "Default") == "Default" );
- assert( root.getTagAttribute!string("*:bar", "attrNS:X") is null);
- }
-
- /++
- Lookup a child tag and attribute by name, and retrieve a value of type T
- from it. Throws if not found.
-
- Useful if you only expect one attribute of type T from given
- the tag and attribute names. Only looks for immediate child tags of
- `this`, doesn't search recursively.
-
- This is a shortcut for `expectTag().expectAttribute()`.
- +/
- T expectTagAttribute(T)(string fullTagName, string fullAttributeName) if(isValueType!T)
- {
- return expectTag(fullTagName).expectAttribute!T(fullAttributeName);
- }
-
- ///
- @("Tag.expectTagAttribute")
- unittest
- {
- import std.exception;
- import sdlang.parser;
-
- auto root = parseSource(`
- foo X=1 X="a" X=2 X="b"
- foo X=3 X="c" X=4 X="d" // expectTagAttribute considers this to override the first foo
-
- bar X="hi"
- bar X=379 // expectTagAttribute considers this to override the first bar
- `);
- assert( root.expectTagAttribute!int("foo", "X") == 3 );
- assert( root.expectTagAttribute!string("foo", "X") == "c" );
-
- // The last "bar" tag doesn't have an int attribute named "X" (only the first "bar" tag does)
- // If you'd rather receive a default value than an exception, use `getAttribute` instead.
- assertThrown!AttributeNotFoundException( root.expectTagAttribute!string("bar", "X") );
-
- // Tag not found
- assertThrown!TagNotFoundException( root.expectTagAttribute!int("doesnt-exist", "X") );
-
- // Using namespaces:
- root = parseSource(`
- ns1:foo X=1 X="a" X=2 X="b"
- ns1:foo X=3 X="c" X=4 X="d"
- ns2:foo X=11 X="aa" X=22 X="bb"
- ns2:foo X=33 X="cc" X=44 X="dd"
-
- ns1:bar attrNS:X="hi"
- ns1:bar attrNS:X=379 // expectTagAttribute considers this to override the first bar
- `);
- assert( root.expectTagAttribute!int("ns1:foo", "X") == 3 );
- assert( root.expectTagAttribute!int("*:foo", "X") == 33 ); // Search all namespaces
-
- assert( root.expectTagAttribute!string("ns1:foo", "X") == "c" );
- assert( root.expectTagAttribute!string("*:foo", "X") == "cc" ); // Search all namespaces
-
- // bar's attribute X is't in the default namespace
- assertThrown!AttributeNotFoundException( root.expectTagAttribute!int("*:bar", "X") );
-
- // The last "bar" tag's "attrNS:X" attribute doesn't have a string (only the first "bar" tag does)
- assertThrown!AttributeNotFoundException( root.expectTagAttribute!string("*:bar", "attrNS:X") );
-
- // Tag's namespace not found
- assertThrown!TagNotFoundException( root.expectTagAttribute!int("doesnt-exist:bar", "attrNS:X") );
- }
-
- /++
- Lookup a child tag by name, and retrieve all values from it.
-
- This just like using `getTag()`.`values`, except if the tag isn't found,
- it safely returns null (or an optional array of default values) instead of
- a dereferencing null error.
-
- Note that, unlike `getValue`, this doesn't discriminate by the value's
- type. It simply returns all values of a single tag as a `Value[]`.
-
- If you'd prefer an exception thrown when the tag isn't found, use
- `expectTag`.`values` instead.
- +/
- Value[] getTagValues(string fullTagName, Value[] defaultValues = null)
- {
- auto tag = getTag(fullTagName);
- if(tag)
- return tag.values;
- else
- return defaultValues;
- }
-
- ///
- @("getTagValues")
- unittest
- {
- import std.exception;
- import sdlang.parser;
-
- auto root = parseSource(`
- foo 1 "a" 2 "b"
- foo 3 "c" 4 "d" // getTagValues considers this to override the first foo
- `);
- assert( root.getTagValues("foo") == [Value(3), Value("c"), Value(4), Value("d")] );
-
- // Tag not found
- // If you'd prefer an exception, use `expectTag.values` instead.
- assert( root.getTagValues("doesnt-exist") is null );
- assert( root.getTagValues("doesnt-exist", [ Value(999), Value("Not found") ]) ==
- [ Value(999), Value("Not found") ] );
- }
-
- /++
- Lookup a child tag by name, and retrieve all attributes in a chosen
- (or default) namespace from it.
-
- This just like using `getTag()`.`attributes` (or
- `getTag()`.`namespace[...]`.`attributes`, or `getTag()`.`all`.`attributes`),
- except if the tag isn't found, it safely returns an empty range instead
- of a dereferencing null error.
-
- If provided, the `attributeNamespace` parameter can be either the name of
- a namespace, or an empty string for the default namespace (the default),
- or `"*"` to retreive attributes from all namespaces.
-
- Note that, unlike `getAttributes`, this doesn't discriminate by the
- value's type. It simply returns the usual `attributes` range.
-
- If you'd prefer an exception thrown when the tag isn't found, use
- `expectTag`.`attributes` instead.
- +/
- auto getTagAttributes(string fullTagName, string attributeNamespace = null)
- {
- auto tag = getTag(fullTagName);
- if(tag)
- {
- if(attributeNamespace && attributeNamespace in tag.namespaces)
- return tag.namespaces[attributeNamespace].attributes;
- else if(attributeNamespace == "*")
- return tag.all.attributes;
- else
- return tag.attributes;
- }
-
- return AttributeRange(null, null, false);
- }
-
- ///
- @("getTagAttributes")
- unittest
- {
- import std.exception;
- import sdlang.parser;
-
- auto root = parseSource(`
- foo X=1 X=2
-
- // getTagAttributes considers this to override the first foo
- foo X1=3 X2="c" namespace:bar=7 X3=4 X4="d"
- `);
-
- auto fooAttrs = root.getTagAttributes("foo");
- assert( !fooAttrs.empty );
- assert( fooAttrs.length == 4 );
- assert( fooAttrs[0].name == "X1" && fooAttrs[0].value == Value(3) );
- assert( fooAttrs[1].name == "X2" && fooAttrs[1].value == Value("c") );
- assert( fooAttrs[2].name == "X3" && fooAttrs[2].value == Value(4) );
- assert( fooAttrs[3].name == "X4" && fooAttrs[3].value == Value("d") );
-
- fooAttrs = root.getTagAttributes("foo", "namespace");
- assert( !fooAttrs.empty );
- assert( fooAttrs.length == 1 );
- assert( fooAttrs[0].name == "bar" && fooAttrs[0].value == Value(7) );
-
- fooAttrs = root.getTagAttributes("foo", "*");
- assert( !fooAttrs.empty );
- assert( fooAttrs.length == 5 );
- assert( fooAttrs[0].name == "X1" && fooAttrs[0].value == Value(3) );
- assert( fooAttrs[1].name == "X2" && fooAttrs[1].value == Value("c") );
- assert( fooAttrs[2].name == "bar" && fooAttrs[2].value == Value(7) );
- assert( fooAttrs[3].name == "X3" && fooAttrs[3].value == Value(4) );
- assert( fooAttrs[4].name == "X4" && fooAttrs[4].value == Value("d") );
-
- // Tag not found
- // If you'd prefer an exception, use `expectTag.attributes` instead.
- assert( root.getTagValues("doesnt-exist").empty );
- }
-
- @("*: Disallow wildcards for names")
- unittest
- {
- import std.exception;
- import std.math;
- import sdlang.parser;
-
- auto root = parseSource(`
- foo 1 X=2
- ns:foo 3 ns:X=4
- `);
- auto foo = root.getTag("foo");
- auto nsfoo = root.getTag("ns:foo");
-
- // Sanity check
- assert( foo !is null );
- assert( foo.name == "foo" );
- assert( foo.namespace == "" );
-
- assert( nsfoo !is null );
- assert( nsfoo.name == "foo" );
- assert( nsfoo.namespace == "ns" );
-
- assert( foo.getValue !int() == 1 );
- assert( foo.expectValue !int() == 1 );
- assert( nsfoo.getValue !int() == 3 );
- assert( nsfoo.expectValue!int() == 3 );
-
- assert( root.getTagValue !int("foo") == 1 );
- assert( root.expectTagValue!int("foo") == 1 );
- assert( root.getTagValue !int("ns:foo") == 3 );
- assert( root.expectTagValue!int("ns:foo") == 3 );
-
- assert( foo.getAttribute !int("X") == 2 );
- assert( foo.expectAttribute !int("X") == 2 );
- assert( nsfoo.getAttribute !int("ns:X") == 4 );
- assert( nsfoo.expectAttribute!int("ns:X") == 4 );
-
- assert( root.getTagAttribute !int("foo", "X") == 2 );
- assert( root.expectTagAttribute!int("foo", "X") == 2 );
- assert( root.getTagAttribute !int("ns:foo", "ns:X") == 4 );
- assert( root.expectTagAttribute!int("ns:foo", "ns:X") == 4 );
-
- // No namespace
- assertThrown!ArgumentException( root.getTag ("*") );
- assertThrown!ArgumentException( root.expectTag("*") );
-
- assertThrown!ArgumentException( root.getTagValue !int("*") );
- assertThrown!ArgumentException( root.expectTagValue!int("*") );
-
- assertThrown!ArgumentException( foo.getAttribute !int("*") );
- assertThrown!ArgumentException( foo.expectAttribute !int("*") );
- assertThrown!ArgumentException( root.getTagAttribute !int("*", "X") );
- assertThrown!ArgumentException( root.expectTagAttribute!int("*", "X") );
- assertThrown!ArgumentException( root.getTagAttribute !int("foo", "*") );
- assertThrown!ArgumentException( root.expectTagAttribute!int("foo", "*") );
-
- // With namespace
- assertThrown!ArgumentException( root.getTag ("ns:*") );
- assertThrown!ArgumentException( root.expectTag("ns:*") );
-
- assertThrown!ArgumentException( root.getTagValue !int("ns:*") );
- assertThrown!ArgumentException( root.expectTagValue!int("ns:*") );
-
- assertThrown!ArgumentException( nsfoo.getAttribute !int("ns:*") );
- assertThrown!ArgumentException( nsfoo.expectAttribute !int("ns:*") );
- assertThrown!ArgumentException( root.getTagAttribute !int("ns:*", "ns:X") );
- assertThrown!ArgumentException( root.expectTagAttribute!int("ns:*", "ns:X") );
- assertThrown!ArgumentException( root.getTagAttribute !int("ns:foo", "ns:*") );
- assertThrown!ArgumentException( root.expectTagAttribute!int("ns:foo", "ns:*") );
-
- // With wildcard namespace
- assertThrown!ArgumentException( root.getTag ("*:*") );
- assertThrown!ArgumentException( root.expectTag("*:*") );
-
- assertThrown!ArgumentException( root.getTagValue !int("*:*") );
- assertThrown!ArgumentException( root.expectTagValue!int("*:*") );
-
- assertThrown!ArgumentException( nsfoo.getAttribute !int("*:*") );
- assertThrown!ArgumentException( nsfoo.expectAttribute !int("*:*") );
- assertThrown!ArgumentException( root.getTagAttribute !int("*:*", "*:X") );
- assertThrown!ArgumentException( root.expectTagAttribute!int("*:*", "*:X") );
- assertThrown!ArgumentException( root.getTagAttribute !int("*:foo", "*:*") );
- assertThrown!ArgumentException( root.expectTagAttribute!int("*:foo", "*:*") );
- }
-
- override bool opEquals(Object o)
- {
- auto t = cast(Tag)o;
- if(!t)
- return false;
-
- if(_namespace != t._namespace || _name != t._name)
- return false;
-
- if(
- values .length != t.values .length ||
- allAttributes .length != t.allAttributes.length ||
- allNamespaces .length != t.allNamespaces.length ||
- allTags .length != t.allTags .length
- )
- return false;
-
- if(values != t.values)
- return false;
-
- if(allNamespaces != t.allNamespaces)
- return false;
-
- if(allAttributes != t.allAttributes)
- return false;
-
- // Ok because cycles are not allowed
- //TODO: Actually check for or prevent cycles.
- return allTags == t.allTags;
- }
-
- /// Treats `this` as the root tag. Note that root tags cannot have
- /// values or attributes, and cannot be part of a namespace.
- /// If this isn't a valid root tag, `sdlang.exception.ValidationException`
- /// will be thrown.
- string toSDLDocument()(string indent="\t", int indentLevel=0)
- {
- Appender!string sink;
- toSDLDocument(sink, indent, indentLevel);
- return sink.data;
- }
-
- ///ditto
- void toSDLDocument(Sink)(ref Sink sink, string indent="\t", int indentLevel=0)
- if(isOutputRange!(Sink,char))
- {
- if(values.length > 0)
- throw new ValidationException("Root tags cannot have any values, only child tags.");
-
- if(allAttributes.length > 0)
- throw new ValidationException("Root tags cannot have any attributes, only child tags.");
-
- if(_namespace != "")
- throw new ValidationException("Root tags cannot have a namespace.");
-
- foreach(tag; allTags)
- tag.toSDLString(sink, indent, indentLevel);
- }
-
- /// Output this entire tag in SDL format. Does $(B $(I not)) treat `this` as
- /// a root tag. If you intend this to be the root of a standard SDL
- /// document, use `toSDLDocument` instead.
- string toSDLString()(string indent="\t", int indentLevel=0)
- {
- Appender!string sink;
- toSDLString(sink, indent, indentLevel);
- return sink.data;
- }
-
- ///ditto
- void toSDLString(Sink)(ref Sink sink, string indent="\t", int indentLevel=0)
- if(isOutputRange!(Sink,char))
- {
- if(_name == "" && values.length == 0)
- throw new ValidationException("Anonymous tags must have at least one value.");
-
- if(_name == "" && _namespace != "")
- throw new ValidationException("Anonymous tags cannot have a namespace.");
-
- // Indent
- foreach(i; 0..indentLevel)
- sink.put(indent);
-
- // Name
- if(_namespace != "")
- {
- sink.put(_namespace);
- sink.put(':');
- }
- sink.put(_name);
-
- // Values
- foreach(i, v; values)
- {
- // Omit the first space for anonymous tags
- if(_name != "" || i > 0)
- sink.put(' ');
-
- v.toSDLString(sink);
- }
-
- // Attributes
- foreach(attr; allAttributes)
- {
- sink.put(' ');
- attr.toSDLString(sink);
- }
-
- // Child tags
- bool foundChild=false;
- foreach(tag; allTags)
- {
- if(!foundChild)
- {
- sink.put(" {\n");
- foundChild = true;
- }
-
- tag.toSDLString(sink, indent, indentLevel+1);
- }
- if(foundChild)
- {
- foreach(i; 0..indentLevel)
- sink.put(indent);
-
- sink.put("}\n");
- }
- else
- sink.put("\n");
- }
-
- /// Outputs full information on the tag.
- string toDebugString()
- {
- import std.algorithm : sort;
-
- Appender!string buf;
-
- buf.put("\n");
- buf.put("Tag ");
- if(_namespace != "")
- {
- buf.put("[");
- buf.put(_namespace);
- buf.put("]");
- }
- buf.put("'%s':\n".format(_name));
-
- // Values
- foreach(val; values)
- buf.put(" (%s): %s\n".format(.toString(val.type), val));
-
- // Attributes
- foreach(attrNamespace; _attributes.keys.sort())
- if(attrNamespace != "*")
- foreach(attrName; _attributes[attrNamespace].keys.sort())
- foreach(attr; _attributes[attrNamespace][attrName])
- {
- string namespaceStr;
- if(attr._namespace != "")
- namespaceStr = "["~attr._namespace~"]";
-
- buf.put(
- " %s%s(%s): %s\n".format(
- namespaceStr, attr._name, .toString(attr.value.type), attr.value
- )
- );
- }
-
- // Children
- foreach(tagNamespace; _tags.keys.sort())
- if(tagNamespace != "*")
- foreach(tagName; _tags[tagNamespace].keys.sort())
- foreach(tag; _tags[tagNamespace][tagName])
- buf.put( tag.toDebugString().replace("\n", "\n ") );
-
- return buf.data;
- }
-}
-
-version(unittest)
-{
- private void testRandomAccessRange(R, E)(R range, E[] expected, bool function(E, E) equals=null)
- {
- static assert(isRandomAccessRange!R);
- static assert(is(ElementType!R == E));
- static assert(hasLength!R);
- static assert(!isInfinite!R);
-
- assert(range.length == expected.length);
- if(range.length == 0)
- {
- assert(range.empty);
- return;
- }
-
- static bool defaultEquals(E e1, E e2)
- {
- return e1 == e2;
- }
- if(equals is null)
- equals = &defaultEquals;
-
- assert(equals(range.front, expected[0]));
- assert(equals(range.front, expected[0])); // Ensure consistent result from '.front'
- assert(equals(range.front, expected[0])); // Ensure consistent result from '.front'
-
- assert(equals(range.back, expected[$-1]));
- assert(equals(range.back, expected[$-1])); // Ensure consistent result from '.back'
- assert(equals(range.back, expected[$-1])); // Ensure consistent result from '.back'
-
- // Forward iteration
- auto original = range.save;
- auto r2 = range.save;
- foreach(i; 0..expected.length)
- {
- //trace("Forward iteration: ", i);
-
- // Test length/empty
- assert(range.length == expected.length - i);
- assert(range.length == r2.length);
- assert(!range.empty);
- assert(!r2.empty);
-
- // Test front
- assert(equals(range.front, expected[i]));
- assert(equals(range.front, r2.front));
-
- // Test back
- assert(equals(range.back, expected[$-1]));
- assert(equals(range.back, r2.back));
-
- // Test opIndex(0)
- assert(equals(range[0], expected[i]));
- assert(equals(range[0], r2[0]));
-
- // Test opIndex($-1)
- assert(equals(range[$-1], expected[$-1]));
- assert(equals(range[$-1], r2[$-1]));
-
- // Test popFront
- range.popFront();
- assert(range.length == r2.length - 1);
- r2.popFront();
- assert(range.length == r2.length);
- }
- assert(range.empty);
- assert(r2.empty);
- assert(original.length == expected.length);
-
- // Backwards iteration
- range = original.save;
- r2 = original.save;
- foreach(i; iota(0, expected.length).retro())
- {
- //trace("Backwards iteration: ", i);
-
- // Test length/empty
- assert(range.length == i+1);
- assert(range.length == r2.length);
- assert(!range.empty);
- assert(!r2.empty);
-
- // Test front
- assert(equals(range.front, expected[0]));
- assert(equals(range.front, r2.front));
-
- // Test back
- assert(equals(range.back, expected[i]));
- assert(equals(range.back, r2.back));
-
- // Test opIndex(0)
- assert(equals(range[0], expected[0]));
- assert(equals(range[0], r2[0]));
-
- // Test opIndex($-1)
- assert(equals(range[$-1], expected[i]));
- assert(equals(range[$-1], r2[$-1]));
-
- // Test popBack
- range.popBack();
- assert(range.length == r2.length - 1);
- r2.popBack();
- assert(range.length == r2.length);
- }
- assert(range.empty);
- assert(r2.empty);
- assert(original.length == expected.length);
-
- // Random access
- range = original.save;
- r2 = original.save;
- foreach(i; 0..expected.length)
- {
- //trace("Random access: ", i);
-
- // Test length/empty
- assert(range.length == expected.length);
- assert(range.length == r2.length);
- assert(!range.empty);
- assert(!r2.empty);
-
- // Test front
- assert(equals(range.front, expected[0]));
- assert(equals(range.front, r2.front));
-
- // Test back
- assert(equals(range.back, expected[$-1]));
- assert(equals(range.back, r2.back));
-
- // Test opIndex(i)
- assert(equals(range[i], expected[i]));
- assert(equals(range[i], r2[i]));
- }
- assert(!range.empty);
- assert(!r2.empty);
- assert(original.length == expected.length);
- }
-}
-
-@("*: Test sdlang ast")
-unittest
-{
- import std.exception;
- import sdlang.parser;
-
- Tag root;
- root = parseSource("");
- testRandomAccessRange(root.attributes, cast( Attribute[])[]);
- testRandomAccessRange(root.tags, cast( Tag[])[]);
- testRandomAccessRange(root.namespaces, cast(Tag.NamespaceAccess[])[]);
-
- root = parseSource(`
- blue 3 "Lee" isThree=true
- blue 5 "Chan" 12345 isThree=false
- stuff:orange 1 2 3 2 1
- stuff:square points=4 dimensions=2 points="Still four"
- stuff:triangle data:points=3 data:dimensions=2
- nothing
- namespaces small:A=1 med:A=2 big:A=3 small:B=10 big:B=30
-
- people visitor:a=1 b=2 {
- chiyo "Small" "Flies?" nemesis="Car" score=100
- yukari
- visitor:sana
- tomo
- visitor:hayama
- }
- `);
-
- auto blue3 = new Tag(
- null, "", "blue",
- [ Value(3), Value("Lee") ],
- [ new Attribute("isThree", Value(true)) ],
- null
- );
- auto blue5 = new Tag(
- null, "", "blue",
- [ Value(5), Value("Chan"), Value(12345) ],
- [ new Attribute("isThree", Value(false)) ],
- null
- );
- auto orange = new Tag(
- null, "stuff", "orange",
- [ Value(1), Value(2), Value(3), Value(2), Value(1) ],
- null,
- null
- );
- auto square = new Tag(
- null, "stuff", "square",
- null,
- [
- new Attribute("points", Value(4)),
- new Attribute("dimensions", Value(2)),
- new Attribute("points", Value("Still four")),
- ],
- null
- );
- auto triangle = new Tag(
- null, "stuff", "triangle",
- null,
- [
- new Attribute("data", "points", Value(3)),
- new Attribute("data", "dimensions", Value(2)),
- ],
- null
- );
- auto nothing = new Tag(
- null, "", "nothing",
- null, null, null
- );
- auto namespaces = new Tag(
- null, "", "namespaces",
- null,
- [
- new Attribute("small", "A", Value(1)),
- new Attribute("med", "A", Value(2)),
- new Attribute("big", "A", Value(3)),
- new Attribute("small", "B", Value(10)),
- new Attribute("big", "B", Value(30)),
- ],
- null
- );
- auto chiyo = new Tag(
- null, "", "chiyo",
- [ Value("Small"), Value("Flies?") ],
- [
- new Attribute("nemesis", Value("Car")),
- new Attribute("score", Value(100)),
- ],
- null
- );
- auto chiyo_ = new Tag(
- null, "", "chiyo_",
- [ Value("Small"), Value("Flies?") ],
- [
- new Attribute("nemesis", Value("Car")),
- new Attribute("score", Value(100)),
- ],
- null
- );
- auto yukari = new Tag(
- null, "", "yukari",
- null, null, null
- );
- auto sana = new Tag(
- null, "visitor", "sana",
- null, null, null
- );
- auto sana_ = new Tag(
- null, "visitor", "sana_",
- null, null, null
- );
- auto sanaVisitor_ = new Tag(
- null, "visitor_", "sana_",
- null, null, null
- );
- auto tomo = new Tag(
- null, "", "tomo",
- null, null, null
- );
- auto hayama = new Tag(
- null, "visitor", "hayama",
- null, null, null
- );
- auto people = new Tag(
- null, "", "people",
- null,
- [
- new Attribute("visitor", "a", Value(1)),
- new Attribute("b", Value(2)),
- ],
- [chiyo, yukari, sana, tomo, hayama]
- );
-
- assert(blue3 .opEquals( blue3 ));
- assert(blue5 .opEquals( blue5 ));
- assert(orange .opEquals( orange ));
- assert(square .opEquals( square ));
- assert(triangle .opEquals( triangle ));
- assert(nothing .opEquals( nothing ));
- assert(namespaces .opEquals( namespaces ));
- assert(people .opEquals( people ));
- assert(chiyo .opEquals( chiyo ));
- assert(yukari .opEquals( yukari ));
- assert(sana .opEquals( sana ));
- assert(tomo .opEquals( tomo ));
- assert(hayama .opEquals( hayama ));
-
- assert(!blue3.opEquals(orange));
- assert(!blue3.opEquals(people));
- assert(!blue3.opEquals(sana));
- assert(!blue3.opEquals(blue5));
- assert(!blue5.opEquals(blue3));
-
- alias Tag.NamespaceAccess NSA;
- static bool namespaceEquals(NSA n1, NSA n2)
- {
- return n1.name == n2.name;
- }
-
- testRandomAccessRange(root.attributes, cast(Attribute[])[]);
- testRandomAccessRange(root.tags, [blue3, blue5, nothing, namespaces, people]);
- testRandomAccessRange(root.namespaces, [NSA(""), NSA("stuff")], &namespaceEquals);
- testRandomAccessRange(root.namespaces[0].tags, [blue3, blue5, nothing, namespaces, people]);
- testRandomAccessRange(root.namespaces[1].tags, [orange, square, triangle]);
- assert("" in root.namespaces);
- assert("stuff" in root.namespaces);
- assert("foobar" !in root.namespaces);
- testRandomAccessRange(root.namespaces[ ""].tags, [blue3, blue5, nothing, namespaces, people]);
- testRandomAccessRange(root.namespaces["stuff"].tags, [orange, square, triangle]);
- testRandomAccessRange(root.all.attributes, cast(Attribute[])[]);
- testRandomAccessRange(root.all.tags, [blue3, blue5, orange, square, triangle, nothing, namespaces, people]);
- testRandomAccessRange(root.all.tags[], [blue3, blue5, orange, square, triangle, nothing, namespaces, people]);
- testRandomAccessRange(root.all.tags[3..6], [square, triangle, nothing]);
- assert("blue" in root.tags);
- assert("nothing" in root.tags);
- assert("people" in root.tags);
- assert("orange" !in root.tags);
- assert("square" !in root.tags);
- assert("foobar" !in root.tags);
- assert("blue" in root.all.tags);
- assert("nothing" in root.all.tags);
- assert("people" in root.all.tags);
- assert("orange" in root.all.tags);
- assert("square" in root.all.tags);
- assert("foobar" !in root.all.tags);
- assert("orange" in root.namespaces["stuff"].tags);
- assert("square" in root.namespaces["stuff"].tags);
- assert("square" in root.namespaces["stuff"].tags);
- assert("foobar" !in root.attributes);
- assert("foobar" !in root.all.attributes);
- assert("foobar" !in root.namespaces["stuff"].attributes);
- assert("blue" !in root.attributes);
- assert("blue" !in root.all.attributes);
- assert("blue" !in root.namespaces["stuff"].attributes);
- testRandomAccessRange(root.tags["nothing"], [nothing]);
- testRandomAccessRange(root.tags["blue"], [blue3, blue5]);
- testRandomAccessRange(root.namespaces["stuff"].tags["orange"], [orange]);
- testRandomAccessRange(root.all.tags["nothing"], [nothing]);
- testRandomAccessRange(root.all.tags["blue"], [blue3, blue5]);
- testRandomAccessRange(root.all.tags["orange"], [orange]);
-
- assertThrown!DOMRangeException(root.tags["foobar"]);
- assertThrown!DOMRangeException(root.all.tags["foobar"]);
- assertThrown!DOMRangeException(root.attributes["foobar"]);
- assertThrown!DOMRangeException(root.all.attributes["foobar"]);
-
- // DMD Issue #12585 causes a segfault in these two tests when using 2.064 or 2.065,
- // so work around it.
- //assertThrown!DOMRangeException(root.namespaces["foobar"].tags["foobar"]);
- //assertThrown!DOMRangeException(root.namespaces["foobar"].attributes["foobar"]);
- bool didCatch = false;
- try
- auto x = root.namespaces["foobar"].tags["foobar"];
- catch(DOMRangeException e)
- didCatch = true;
- assert(didCatch);
-
- didCatch = false;
- try
- auto x = root.namespaces["foobar"].attributes["foobar"];
- catch(DOMRangeException e)
- didCatch = true;
- assert(didCatch);
-
- testRandomAccessRange(root.maybe.tags["nothing"], [nothing]);
- testRandomAccessRange(root.maybe.tags["blue"], [blue3, blue5]);
- testRandomAccessRange(root.maybe.namespaces["stuff"].tags["orange"], [orange]);
- testRandomAccessRange(root.maybe.all.tags["nothing"], [nothing]);
- testRandomAccessRange(root.maybe.all.tags["blue"], [blue3, blue5]);
- testRandomAccessRange(root.maybe.all.tags["blue"][], [blue3, blue5]);
- testRandomAccessRange(root.maybe.all.tags["blue"][0..1], [blue3]);
- testRandomAccessRange(root.maybe.all.tags["blue"][1..2], [blue5]);
- testRandomAccessRange(root.maybe.all.tags["orange"], [orange]);
- testRandomAccessRange(root.maybe.tags["foobar"], cast(Tag[])[]);
- testRandomAccessRange(root.maybe.all.tags["foobar"], cast(Tag[])[]);
- testRandomAccessRange(root.maybe.namespaces["foobar"].tags["foobar"], cast(Tag[])[]);
- testRandomAccessRange(root.maybe.attributes["foobar"], cast(Attribute[])[]);
- testRandomAccessRange(root.maybe.all.attributes["foobar"], cast(Attribute[])[]);
- testRandomAccessRange(root.maybe.namespaces["foobar"].attributes["foobar"], cast(Attribute[])[]);
-
- testRandomAccessRange(blue3.attributes, [ new Attribute("isThree", Value(true)) ]);
- testRandomAccessRange(blue3.tags, cast(Tag[])[]);
- testRandomAccessRange(blue3.namespaces, [NSA("")], &namespaceEquals);
- testRandomAccessRange(blue3.all.attributes, [ new Attribute("isThree", Value(true)) ]);
- testRandomAccessRange(blue3.all.tags, cast(Tag[])[]);
-
- testRandomAccessRange(blue5.attributes, [ new Attribute("isThree", Value(false)) ]);
- testRandomAccessRange(blue5.tags, cast(Tag[])[]);
- testRandomAccessRange(blue5.namespaces, [NSA("")], &namespaceEquals);
- testRandomAccessRange(blue5.all.attributes, [ new Attribute("isThree", Value(false)) ]);
- testRandomAccessRange(blue5.all.tags, cast(Tag[])[]);
-
- testRandomAccessRange(orange.attributes, cast(Attribute[])[]);
- testRandomAccessRange(orange.tags, cast(Tag[])[]);
- testRandomAccessRange(orange.namespaces, cast(NSA[])[], &namespaceEquals);
- testRandomAccessRange(orange.all.attributes, cast(Attribute[])[]);
- testRandomAccessRange(orange.all.tags, cast(Tag[])[]);
-
- testRandomAccessRange(square.attributes, [
- new Attribute("points", Value(4)),
- new Attribute("dimensions", Value(2)),
- new Attribute("points", Value("Still four")),
- ]);
- testRandomAccessRange(square.tags, cast(Tag[])[]);
- testRandomAccessRange(square.namespaces, [NSA("")], &namespaceEquals);
- testRandomAccessRange(square.all.attributes, [
- new Attribute("points", Value(4)),
- new Attribute("dimensions", Value(2)),
- new Attribute("points", Value("Still four")),
- ]);
- testRandomAccessRange(square.all.tags, cast(Tag[])[]);
-
- testRandomAccessRange(triangle.attributes, cast(Attribute[])[]);
- testRandomAccessRange(triangle.tags, cast(Tag[])[]);
- testRandomAccessRange(triangle.namespaces, [NSA("data")], &namespaceEquals);
- testRandomAccessRange(triangle.namespaces[0].attributes, [
- new Attribute("data", "points", Value(3)),
- new Attribute("data", "dimensions", Value(2)),
- ]);
- assert("data" in triangle.namespaces);
- assert("foobar" !in triangle.namespaces);
- testRandomAccessRange(triangle.namespaces["data"].attributes, [
- new Attribute("data", "points", Value(3)),
- new Attribute("data", "dimensions", Value(2)),
- ]);
- testRandomAccessRange(triangle.all.attributes, [
- new Attribute("data", "points", Value(3)),
- new Attribute("data", "dimensions", Value(2)),
- ]);
- testRandomAccessRange(triangle.all.tags, cast(Tag[])[]);
-
- testRandomAccessRange(nothing.attributes, cast(Attribute[])[]);
- testRandomAccessRange(nothing.tags, cast(Tag[])[]);
- testRandomAccessRange(nothing.namespaces, cast(NSA[])[], &namespaceEquals);
- testRandomAccessRange(nothing.all.attributes, cast(Attribute[])[]);
- testRandomAccessRange(nothing.all.tags, cast(Tag[])[]);
-
- testRandomAccessRange(namespaces.attributes, cast(Attribute[])[]);
- testRandomAccessRange(namespaces.tags, cast(Tag[])[]);
- testRandomAccessRange(namespaces.namespaces, [NSA("small"), NSA("med"), NSA("big")], &namespaceEquals);
- testRandomAccessRange(namespaces.namespaces[], [NSA("small"), NSA("med"), NSA("big")], &namespaceEquals);
- testRandomAccessRange(namespaces.namespaces[1..2], [NSA("med")], &namespaceEquals);
- testRandomAccessRange(namespaces.namespaces[0].attributes, [
- new Attribute("small", "A", Value(1)),
- new Attribute("small", "B", Value(10)),
- ]);
- testRandomAccessRange(namespaces.namespaces[1].attributes, [
- new Attribute("med", "A", Value(2)),
- ]);
- testRandomAccessRange(namespaces.namespaces[2].attributes, [
- new Attribute("big", "A", Value(3)),
- new Attribute("big", "B", Value(30)),
- ]);
- testRandomAccessRange(namespaces.namespaces[1..2][0].attributes, [
- new Attribute("med", "A", Value(2)),
- ]);
- assert("small" in namespaces.namespaces);
- assert("med" in namespaces.namespaces);
- assert("big" in namespaces.namespaces);
- assert("foobar" !in namespaces.namespaces);
- assert("small" !in namespaces.namespaces[1..2]);
- assert("med" in namespaces.namespaces[1..2]);
- assert("big" !in namespaces.namespaces[1..2]);
- assert("foobar" !in namespaces.namespaces[1..2]);
- testRandomAccessRange(namespaces.namespaces["small"].attributes, [
- new Attribute("small", "A", Value(1)),
- new Attribute("small", "B", Value(10)),
- ]);
- testRandomAccessRange(namespaces.namespaces["med"].attributes, [
- new Attribute("med", "A", Value(2)),
- ]);
- testRandomAccessRange(namespaces.namespaces["big"].attributes, [
- new Attribute("big", "A", Value(3)),
- new Attribute("big", "B", Value(30)),
- ]);
- testRandomAccessRange(namespaces.all.attributes, [
- new Attribute("small", "A", Value(1)),
- new Attribute("med", "A", Value(2)),
- new Attribute("big", "A", Value(3)),
- new Attribute("small", "B", Value(10)),
- new Attribute("big", "B", Value(30)),
- ]);
- testRandomAccessRange(namespaces.all.attributes[], [
- new Attribute("small", "A", Value(1)),
- new Attribute("med", "A", Value(2)),
- new Attribute("big", "A", Value(3)),
- new Attribute("small", "B", Value(10)),
- new Attribute("big", "B", Value(30)),
- ]);
- testRandomAccessRange(namespaces.all.attributes[2..4], [
- new Attribute("big", "A", Value(3)),
- new Attribute("small", "B", Value(10)),
- ]);
- testRandomAccessRange(namespaces.all.tags, cast(Tag[])[]);
- assert("A" !in namespaces.attributes);
- assert("B" !in namespaces.attributes);
- assert("foobar" !in namespaces.attributes);
- assert("A" in namespaces.all.attributes);
- assert("B" in namespaces.all.attributes);
- assert("foobar" !in namespaces.all.attributes);
- assert("A" in namespaces.namespaces["small"].attributes);
- assert("B" in namespaces.namespaces["small"].attributes);
- assert("foobar" !in namespaces.namespaces["small"].attributes);
- assert("A" in namespaces.namespaces["med"].attributes);
- assert("B" !in namespaces.namespaces["med"].attributes);
- assert("foobar" !in namespaces.namespaces["med"].attributes);
- assert("A" in namespaces.namespaces["big"].attributes);
- assert("B" in namespaces.namespaces["big"].attributes);
- assert("foobar" !in namespaces.namespaces["big"].attributes);
- assert("foobar" !in namespaces.tags);
- assert("foobar" !in namespaces.all.tags);
- assert("foobar" !in namespaces.namespaces["small"].tags);
- assert("A" !in namespaces.tags);
- assert("A" !in namespaces.all.tags);
- assert("A" !in namespaces.namespaces["small"].tags);
- testRandomAccessRange(namespaces.namespaces["small"].attributes["A"], [
- new Attribute("small", "A", Value(1)),
- ]);
- testRandomAccessRange(namespaces.namespaces["med"].attributes["A"], [
- new Attribute("med", "A", Value(2)),
- ]);
- testRandomAccessRange(namespaces.namespaces["big"].attributes["A"], [
- new Attribute("big", "A", Value(3)),
- ]);
- testRandomAccessRange(namespaces.all.attributes["A"], [
- new Attribute("small", "A", Value(1)),
- new Attribute("med", "A", Value(2)),
- new Attribute("big", "A", Value(3)),
- ]);
- testRandomAccessRange(namespaces.all.attributes["B"], [
- new Attribute("small", "B", Value(10)),
- new Attribute("big", "B", Value(30)),
- ]);
-
- testRandomAccessRange(chiyo.attributes, [
- new Attribute("nemesis", Value("Car")),
- new Attribute("score", Value(100)),
- ]);
- testRandomAccessRange(chiyo.tags, cast(Tag[])[]);
- testRandomAccessRange(chiyo.namespaces, [NSA("")], &namespaceEquals);
- testRandomAccessRange(chiyo.all.attributes, [
- new Attribute("nemesis", Value("Car")),
- new Attribute("score", Value(100)),
- ]);
- testRandomAccessRange(chiyo.all.tags, cast(Tag[])[]);
-
- testRandomAccessRange(yukari.attributes, cast(Attribute[])[]);
- testRandomAccessRange(yukari.tags, cast(Tag[])[]);
- testRandomAccessRange(yukari.namespaces, cast(NSA[])[], &namespaceEquals);
- testRandomAccessRange(yukari.all.attributes, cast(Attribute[])[]);
- testRandomAccessRange(yukari.all.tags, cast(Tag[])[]);
-
- testRandomAccessRange(sana.attributes, cast(Attribute[])[]);
- testRandomAccessRange(sana.tags, cast(Tag[])[]);
- testRandomAccessRange(sana.namespaces, cast(NSA[])[], &namespaceEquals);
- testRandomAccessRange(sana.all.attributes, cast(Attribute[])[]);
- testRandomAccessRange(sana.all.tags, cast(Tag[])[]);
-
- testRandomAccessRange(people.attributes, [new Attribute("b", Value(2))]);
- testRandomAccessRange(people.tags, [chiyo, yukari, tomo]);
- testRandomAccessRange(people.namespaces, [NSA("visitor"), NSA("")], &namespaceEquals);
- testRandomAccessRange(people.namespaces[0].attributes, [new Attribute("visitor", "a", Value(1))]);
- testRandomAccessRange(people.namespaces[1].attributes, [new Attribute("b", Value(2))]);
- testRandomAccessRange(people.namespaces[0].tags, [sana, hayama]);
- testRandomAccessRange(people.namespaces[1].tags, [chiyo, yukari, tomo]);
- assert("visitor" in people.namespaces);
- assert("" in people.namespaces);
- assert("foobar" !in people.namespaces);
- testRandomAccessRange(people.namespaces["visitor"].attributes, [new Attribute("visitor", "a", Value(1))]);
- testRandomAccessRange(people.namespaces[ ""].attributes, [new Attribute("b", Value(2))]);
- testRandomAccessRange(people.namespaces["visitor"].tags, [sana, hayama]);
- testRandomAccessRange(people.namespaces[ ""].tags, [chiyo, yukari, tomo]);
- testRandomAccessRange(people.all.attributes, [
- new Attribute("visitor", "a", Value(1)),
- new Attribute("b", Value(2)),
- ]);
- testRandomAccessRange(people.all.tags, [chiyo, yukari, sana, tomo, hayama]);
-
- people.attributes["b"][0].name = "b_";
- people.namespaces["visitor"].attributes["a"][0].name = "a_";
- people.tags["chiyo"][0].name = "chiyo_";
- people.namespaces["visitor"].tags["sana"][0].name = "sana_";
-
- assert("b_" in people.attributes);
- assert("a_" in people.namespaces["visitor"].attributes);
- assert("chiyo_" in people.tags);
- assert("sana_" in people.namespaces["visitor"].tags);
-
- assert(people.attributes["b_"][0] == new Attribute("b_", Value(2)));
- assert(people.namespaces["visitor"].attributes["a_"][0] == new Attribute("visitor", "a_", Value(1)));
- assert(people.tags["chiyo_"][0] == chiyo_);
- assert(people.namespaces["visitor"].tags["sana_"][0] == sana_);
-
- assert("b" !in people.attributes);
- assert("a" !in people.namespaces["visitor"].attributes);
- assert("chiyo" !in people.tags);
- assert("sana" !in people.namespaces["visitor"].tags);
-
- assert(people.maybe.attributes["b"].length == 0);
- assert(people.maybe.namespaces["visitor"].attributes["a"].length == 0);
- assert(people.maybe.tags["chiyo"].length == 0);
- assert(people.maybe.namespaces["visitor"].tags["sana"].length == 0);
-
- people.tags["tomo"][0].remove();
- people.namespaces["visitor"].tags["hayama"][0].remove();
- people.tags["chiyo_"][0].remove();
- testRandomAccessRange(people.tags, [yukari]);
- testRandomAccessRange(people.namespaces, [NSA("visitor"), NSA("")], &namespaceEquals);
- testRandomAccessRange(people.namespaces[0].tags, [sana_]);
- testRandomAccessRange(people.namespaces[1].tags, [yukari]);
- assert("visitor" in people.namespaces);
- assert("" in people.namespaces);
- assert("foobar" !in people.namespaces);
- testRandomAccessRange(people.namespaces["visitor"].tags, [sana_]);
- testRandomAccessRange(people.namespaces[ ""].tags, [yukari]);
- testRandomAccessRange(people.all.tags, [yukari, sana_]);
-
- people.attributes["b_"][0].namespace = "_";
- people.namespaces["visitor"].attributes["a_"][0].namespace = "visitor_";
- assert("_" in people.namespaces);
- assert("visitor_" in people.namespaces);
- assert("" in people.namespaces);
- assert("visitor" in people.namespaces);
- people.namespaces["visitor"].tags["sana_"][0].namespace = "visitor_";
- assert("_" in people.namespaces);
- assert("visitor_" in people.namespaces);
- assert("" in people.namespaces);
- assert("visitor" !in people.namespaces);
-
- assert(people.namespaces["_" ].attributes["b_"][0] == new Attribute("_", "b_", Value(2)));
- assert(people.namespaces["visitor_"].attributes["a_"][0] == new Attribute("visitor_", "a_", Value(1)));
- assert(people.namespaces["visitor_"].tags["sana_"][0] == sanaVisitor_);
-
- people.tags["yukari"][0].remove();
- people.namespaces["visitor_"].tags["sana_"][0].remove();
- people.namespaces["visitor_"].attributes["a_"][0].namespace = "visitor";
- people.namespaces["_"].attributes["b_"][0].namespace = "";
- testRandomAccessRange(people.tags, cast(Tag[])[]);
- testRandomAccessRange(people.namespaces, [NSA("visitor"), NSA("")], &namespaceEquals);
- testRandomAccessRange(people.namespaces[0].tags, cast(Tag[])[]);
- testRandomAccessRange(people.namespaces[1].tags, cast(Tag[])[]);
- assert("visitor" in people.namespaces);
- assert("" in people.namespaces);
- assert("foobar" !in people.namespaces);
- testRandomAccessRange(people.namespaces["visitor"].tags, cast(Tag[])[]);
- testRandomAccessRange(people.namespaces[ ""].tags, cast(Tag[])[]);
- testRandomAccessRange(people.all.tags, cast(Tag[])[]);
-
- people.namespaces["visitor"].attributes["a_"][0].remove();
- testRandomAccessRange(people.attributes, [new Attribute("b_", Value(2))]);
- testRandomAccessRange(people.namespaces, [NSA("")], &namespaceEquals);
- testRandomAccessRange(people.namespaces[0].attributes, [new Attribute("b_", Value(2))]);
- assert("visitor" !in people.namespaces);
- assert("" in people.namespaces);
- assert("foobar" !in people.namespaces);
- testRandomAccessRange(people.namespaces[""].attributes, [new Attribute("b_", Value(2))]);
- testRandomAccessRange(people.all.attributes, [
- new Attribute("b_", Value(2)),
- ]);
-
- people.attributes["b_"][0].remove();
- testRandomAccessRange(people.attributes, cast(Attribute[])[]);
- testRandomAccessRange(people.namespaces, cast(NSA[])[], &namespaceEquals);
- assert("visitor" !in people.namespaces);
- assert("" !in people.namespaces);
- assert("foobar" !in people.namespaces);
- testRandomAccessRange(people.all.attributes, cast(Attribute[])[]);
-
- // Test clone()
- auto rootClone = root.clone();
- assert(rootClone !is root);
- assert(rootClone.parent is null);
- assert(rootClone.name == root.name);
- assert(rootClone.namespace == root.namespace);
- assert(rootClone.location == root.location);
- assert(rootClone.values == root.values);
- assert(rootClone.toSDLDocument() == root.toSDLDocument());
-
- auto peopleClone = people.clone();
- assert(peopleClone !is people);
- assert(peopleClone.parent is null);
- assert(peopleClone.name == people.name);
- assert(peopleClone.namespace == people.namespace);
- assert(peopleClone.location == people.location);
- assert(peopleClone.values == people.values);
- assert(peopleClone.toSDLString() == people.toSDLString());
-}
-
-// Regression test, issue #11: https://github.com/Abscissa/SDLang-D/issues/11
-@("*: Regression test issue #11")
-unittest
-{
- import sdlang.parser;
-
- auto root = parseSource(
-`//
-a`);
-
- assert("a" in root.tags);
-
- root = parseSource(
-`//
-parent {
- child
-}
-`);
-
- auto child = new Tag(
- null, "", "child",
- null, null, null
- );
-
- assert("parent" in root.tags);
- assert("child" !in root.tags);
- testRandomAccessRange(root.tags["parent"][0].tags, [child]);
- assert("child" in root.tags["parent"][0].tags);
-}