From ba1712e77b31704fd9ba16d14e15518e7a7dd104 Mon Sep 17 00:00:00 2001 From: Ralph Amissah Date: Sat, 1 Oct 2016 14:12:13 -0400 Subject: 0.7.0 using dub remote dependencies (local src related to sdlang removed) --- src/sdlang/ast.d | 2945 ------------------------------------------------------ 1 file changed, 2945 deletions(-) delete mode 100644 src/sdlang/ast.d (limited to 'src/sdlang/ast.d') 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); -} -- cgit v1.2.3