1 // SDLang-D 2 // Written in the D programming language. 3 4 module sdlang.ast; 5 6 import std.algorithm; 7 import std.array; 8 import std.conv; 9 import std.range; 10 import std..string; 11 12 import sdlang.exception; 13 import sdlang.token; 14 import sdlang.util; 15 16 class Attribute 17 { 18 Value value; 19 Location location; 20 21 private Tag _parent; 22 /// Get parent tag. To set a parent, attach this Attribute to its intended 23 /// parent tag by calling `Tag.add(...)`, or by passing it to 24 /// the parent tag's constructor. 25 @property Tag parent() 26 { 27 return _parent; 28 } 29 30 private string _namespace; 31 /++ 32 This tag's namespace. Empty string if no namespace. 33 34 Note that setting this value is O(n) because internal lookup structures 35 need to be updated. 36 37 Note also, that setting this may change where this tag is ordered among 38 its parent's list of tags. 39 +/ 40 @property string namespace() 41 { 42 return _namespace; 43 } 44 ///ditto 45 @property void namespace(string value) 46 { 47 if(_parent && _namespace != value) 48 { 49 // Remove 50 auto saveParent = _parent; 51 if(_parent) 52 this.remove(); 53 54 // Change namespace 55 _namespace = value; 56 57 // Re-add 58 if(saveParent) 59 saveParent.add(this); 60 } 61 else 62 _namespace = value; 63 } 64 65 private string _name; 66 /++ 67 This attribute's name, not including namespace. 68 69 Use `getFullName().toString` if you want the namespace included. 70 71 Note that setting this value is O(n) because internal lookup structures 72 need to be updated. 73 74 Note also, that setting this may change where this attribute is ordered 75 among its parent's list of tags. 76 +/ 77 @property string name() 78 { 79 return _name; 80 } 81 ///ditto 82 @property void name(string value) 83 { 84 if(_parent && _name != value) 85 { 86 _parent.updateId++; 87 88 void removeFromGroupedLookup(string ns) 89 { 90 // Remove from _parent._attributes[ns] 91 auto sameNameAttrs = _parent._attributes[ns][_name]; 92 auto targetIndex = sameNameAttrs.countUntil(this); 93 _parent._attributes[ns][_name].removeIndex(targetIndex); 94 } 95 96 // Remove from _parent._tags 97 removeFromGroupedLookup(_namespace); 98 removeFromGroupedLookup("*"); 99 100 // Change name 101 _name = value; 102 103 // Add to new locations in _parent._attributes 104 _parent._attributes[_namespace][_name] ~= this; 105 _parent._attributes["*"][_name] ~= this; 106 } 107 else 108 _name = value; 109 } 110 111 /// This tag's name, including namespace if one exists. 112 deprecated("Use 'getFullName().toString()'") 113 @property string fullName() 114 { 115 return getFullName().toString(); 116 } 117 118 /// This tag's name, including namespace if one exists. 119 FullName getFullName() 120 { 121 return FullName(_namespace, _name); 122 } 123 124 this(string namespace, string name, Value value, Location location = Location(0, 0, 0)) 125 { 126 this._namespace = namespace; 127 this._name = name; 128 this.location = location; 129 this.value = value; 130 } 131 132 this(string name, Value value, Location location = Location(0, 0, 0)) 133 { 134 this._namespace = ""; 135 this._name = name; 136 this.location = location; 137 this.value = value; 138 } 139 140 /// Copy this Attribute. 141 /// The clone does $(B $(I not)) have a parent, even if the original does. 142 Attribute clone() 143 { 144 return new Attribute(_namespace, _name, value, location); 145 } 146 147 /// Removes `this` from its parent, if any. Returns `this` for chaining. 148 /// Inefficient ATM, but it works. 149 Attribute remove() 150 { 151 if(!_parent) 152 return this; 153 154 void removeFromGroupedLookup(string ns) 155 { 156 // Remove from _parent._attributes[ns] 157 auto sameNameAttrs = _parent._attributes[ns][_name]; 158 auto targetIndex = sameNameAttrs.countUntil(this); 159 _parent._attributes[ns][_name].removeIndex(targetIndex); 160 } 161 162 // Remove from _parent._attributes 163 removeFromGroupedLookup(_namespace); 164 removeFromGroupedLookup("*"); 165 166 // Remove from _parent.allAttributes 167 auto allAttrsIndex = _parent.allAttributes.countUntil(this); 168 _parent.allAttributes.removeIndex(allAttrsIndex); 169 170 // Remove from _parent.attributeIndicies 171 auto sameNamespaceAttrs = _parent.attributeIndicies[_namespace]; 172 auto attrIndiciesIndex = sameNamespaceAttrs.countUntil(allAttrsIndex); 173 _parent.attributeIndicies[_namespace].removeIndex(attrIndiciesIndex); 174 175 // Fixup other indicies 176 foreach(ns, ref nsAttrIndicies; _parent.attributeIndicies) 177 foreach(k, ref v; nsAttrIndicies) 178 if(v > allAttrsIndex) 179 v--; 180 181 _parent.removeNamespaceIfEmpty(_namespace); 182 _parent.updateId++; 183 _parent = null; 184 return this; 185 } 186 187 override bool opEquals(Object o) 188 { 189 auto a = cast(Attribute)o; 190 if(!a) 191 return false; 192 193 return 194 _namespace == a._namespace && 195 _name == a._name && 196 value == a.value; 197 } 198 199 string toSDLString()() 200 { 201 Appender!string sink; 202 this.toSDLString(sink); 203 return sink.data; 204 } 205 206 void toSDLString(Sink)(ref Sink sink) if(isOutputRange!(Sink,char)) 207 { 208 if(_namespace != "") 209 { 210 sink.put(_namespace); 211 sink.put(':'); 212 } 213 214 sink.put(_name); 215 sink.put('='); 216 value.toSDLString(sink); 217 } 218 } 219 220 /// Deep-copy an array of Tag or Attribute. 221 /// The top-level clones are $(B $(I not)) attached to any parent, even if the originals are. 222 T[] clone(T)(T[] arr) if(is(T==Tag) || is(T==Attribute)) 223 { 224 T[] newArr; 225 newArr.length = arr.length; 226 227 foreach(i; 0..arr.length) 228 newArr[i] = arr[i].clone(); 229 230 return newArr; 231 } 232 233 class Tag 234 { 235 /// File/Line/Column/Index information for where this tag was located in 236 /// its original SDLang file. 237 Location location; 238 239 /// Access all this tag's values, as an array of type `sdlang.token.Value`. 240 Value[] values; 241 242 private Tag _parent; 243 /// Get parent tag. To set a parent, attach this Tag to its intended 244 /// parent tag by calling `Tag.add(...)`, or by passing it to 245 /// the parent tag's constructor. 246 @property Tag parent() 247 { 248 return _parent; 249 } 250 251 private string _namespace; 252 /++ 253 This tag's namespace. Empty string if no namespace. 254 255 Note that setting this value is O(n) because internal lookup structures 256 need to be updated. 257 258 Note also, that setting this may change where this tag is ordered among 259 its parent's list of tags. 260 +/ 261 @property string namespace() 262 { 263 return _namespace; 264 } 265 ///ditto 266 @property void namespace(string value) 267 { 268 //TODO: Can we do this in-place, without removing/adding and thus 269 // modyfying the internal order? 270 if(_parent && _namespace != value) 271 { 272 // Remove 273 auto saveParent = _parent; 274 if(_parent) 275 this.remove(); 276 277 // Change namespace 278 _namespace = value; 279 280 // Re-add 281 if(saveParent) 282 saveParent.add(this); 283 } 284 else 285 _namespace = value; 286 } 287 288 private string _name; 289 /++ 290 This tag's name, not including namespace. 291 292 Use `getFullName().toString` if you want the namespace included. 293 294 Note that setting this value is O(n) because internal lookup structures 295 need to be updated. 296 297 Note also, that setting this may change where this tag is ordered among 298 its parent's list of tags. 299 +/ 300 @property string name() 301 { 302 return _name; 303 } 304 ///ditto 305 @property void name(string value) 306 { 307 //TODO: Seriously? Can't we at least do the "*" modification *in-place*? 308 309 if(_parent && _name != value) 310 { 311 _parent.updateId++; 312 313 // Not the most efficient, but it works. 314 void removeFromGroupedLookup(string ns) 315 { 316 // Remove from _parent._tags[ns] 317 auto sameNameTags = _parent._tags[ns][_name]; 318 auto targetIndex = sameNameTags.countUntil(this); 319 _parent._tags[ns][_name].removeIndex(targetIndex); 320 } 321 322 // Remove from _parent._tags 323 removeFromGroupedLookup(_namespace); 324 removeFromGroupedLookup("*"); 325 326 // Change name 327 _name = value; 328 329 // Add to new locations in _parent._tags 330 //TODO: Can we re-insert while preserving the original order? 331 _parent._tags[_namespace][_name] ~= this; 332 _parent._tags["*"][_name] ~= this; 333 } 334 else 335 _name = value; 336 } 337 338 /// This tag's name, including namespace if one exists. 339 deprecated("Use 'getFullName().toString()'") 340 @property string fullName() 341 { 342 return getFullName().toString(); 343 } 344 345 /// This tag's name, including namespace if one exists. 346 FullName getFullName() 347 { 348 return FullName(_namespace, _name); 349 } 350 351 // Tracks dirtiness. This is incremented every time a change is made which 352 // could invalidate existing ranges. This way, the ranges can detect when 353 // they've been invalidated. 354 private size_t updateId=0; 355 356 this(Tag parent = null) 357 { 358 if(parent) 359 parent.add(this); 360 } 361 362 this( 363 string namespace, string name, 364 Value[] values=null, Attribute[] attributes=null, Tag[] children=null 365 ) 366 { 367 this(null, namespace, name, values, attributes, children); 368 } 369 370 this( 371 Tag parent, string namespace, string name, 372 Value[] values=null, Attribute[] attributes=null, Tag[] children=null 373 ) 374 { 375 this._namespace = namespace; 376 this._name = name; 377 378 if(parent) 379 parent.add(this); 380 381 this.values = values; 382 this.add(attributes); 383 this.add(children); 384 } 385 386 /// Deep-copy this Tag. 387 /// The clone does $(B $(I not)) have a parent, even if the original does. 388 Tag clone() 389 { 390 auto newTag = new Tag(_namespace, _name, values.dup, allAttributes.clone(), allTags.clone()); 391 newTag.location = location; 392 return newTag; 393 } 394 395 private Attribute[] allAttributes; // In same order as specified in SDL file. 396 private Tag[] allTags; // In same order as specified in SDL file. 397 private string[] allNamespaces; // In same order as specified in SDL file. 398 399 private size_t[][string] attributeIndicies; // allAttributes[ attributes[namespace][i] ] 400 private size_t[][string] tagIndicies; // allTags[ tags[namespace][i] ] 401 402 private Attribute[][string][string] _attributes; // attributes[namespace or "*"][name][i] 403 private Tag[][string][string] _tags; // tags[namespace or "*"][name][i] 404 405 /// Adds a Value, Attribute, Tag (or array of such) as a member/child of this Tag. 406 /// Returns `this` for chaining. 407 /// Throws `ValidationException` if trying to add an Attribute or Tag 408 /// that already has a parent. 409 Tag add(Value val) 410 { 411 values ~= val; 412 updateId++; 413 return this; 414 } 415 416 ///ditto 417 Tag add(Value[] vals) 418 { 419 foreach(val; vals) 420 add(val); 421 422 return this; 423 } 424 425 ///ditto 426 Tag add(Attribute attr) 427 { 428 if(attr._parent) 429 { 430 throw new ValidationException( 431 "Attribute is already attached to a parent tag. "~ 432 "Use Attribute.remove() before adding it to another tag." 433 ); 434 } 435 436 if(!allNamespaces.canFind(attr._namespace)) 437 allNamespaces ~= attr._namespace; 438 439 attr._parent = this; 440 441 allAttributes ~= attr; 442 attributeIndicies[attr._namespace] ~= allAttributes.length-1; 443 _attributes[attr._namespace][attr._name] ~= attr; 444 _attributes["*"] [attr._name] ~= attr; 445 446 updateId++; 447 return this; 448 } 449 450 ///ditto 451 Tag add(Attribute[] attrs) 452 { 453 foreach(attr; attrs) 454 add(attr); 455 456 return this; 457 } 458 459 ///ditto 460 Tag add(Tag tag) 461 { 462 if(tag._parent) 463 { 464 throw new ValidationException( 465 "Tag is already attached to a parent tag. "~ 466 "Use Tag.remove() before adding it to another tag." 467 ); 468 } 469 470 if(!allNamespaces.canFind(tag._namespace)) 471 allNamespaces ~= tag._namespace; 472 473 tag._parent = this; 474 475 allTags ~= tag; 476 tagIndicies[tag._namespace] ~= allTags.length-1; 477 _tags[tag._namespace][tag._name] ~= tag; 478 _tags["*"] [tag._name] ~= tag; 479 480 updateId++; 481 return this; 482 } 483 484 ///ditto 485 Tag add(Tag[] tags) 486 { 487 foreach(tag; tags) 488 add(tag); 489 490 return this; 491 } 492 493 /// Removes `this` from its parent, if any. Returns `this` for chaining. 494 /// Inefficient ATM, but it works. 495 Tag remove() 496 { 497 if(!_parent) 498 return this; 499 500 void removeFromGroupedLookup(string ns) 501 { 502 // Remove from _parent._tags[ns] 503 auto sameNameTags = _parent._tags[ns][_name]; 504 auto targetIndex = sameNameTags.countUntil(this); 505 _parent._tags[ns][_name].removeIndex(targetIndex); 506 } 507 508 // Remove from _parent._tags 509 removeFromGroupedLookup(_namespace); 510 removeFromGroupedLookup("*"); 511 512 // Remove from _parent.allTags 513 auto allTagsIndex = _parent.allTags.countUntil(this); 514 _parent.allTags.removeIndex(allTagsIndex); 515 516 // Remove from _parent.tagIndicies 517 auto sameNamespaceTags = _parent.tagIndicies[_namespace]; 518 auto tagIndiciesIndex = sameNamespaceTags.countUntil(allTagsIndex); 519 _parent.tagIndicies[_namespace].removeIndex(tagIndiciesIndex); 520 521 // Fixup other indicies 522 foreach(ns, ref nsTagIndicies; _parent.tagIndicies) 523 foreach(k, ref v; nsTagIndicies) 524 if(v > allTagsIndex) 525 v--; 526 527 _parent.removeNamespaceIfEmpty(_namespace); 528 _parent.updateId++; 529 _parent = null; 530 return this; 531 } 532 533 private void removeNamespaceIfEmpty(string namespace) 534 { 535 // If namespace has no attributes, remove it from attributeIndicies/_attributes 536 if(namespace in attributeIndicies && attributeIndicies[namespace].length == 0) 537 { 538 attributeIndicies.remove(namespace); 539 _attributes.remove(namespace); 540 } 541 542 // If namespace has no tags, remove it from tagIndicies/_tags 543 if(namespace in tagIndicies && tagIndicies[namespace].length == 0) 544 { 545 tagIndicies.remove(namespace); 546 _tags.remove(namespace); 547 } 548 549 // If namespace is now empty, remove it from allNamespaces 550 if( 551 namespace !in tagIndicies && 552 namespace !in attributeIndicies 553 ) 554 { 555 auto allNamespacesIndex = allNamespaces.length - allNamespaces.find(namespace).length; 556 allNamespaces = allNamespaces[0..allNamespacesIndex] ~ allNamespaces[allNamespacesIndex+1..$]; 557 } 558 } 559 560 struct NamedMemberRange(T, string membersGrouped) 561 { 562 private Tag tag; 563 private string namespace; // "*" indicates "all namespaces" (ok since it's not a valid namespace name) 564 private string name; 565 private size_t updateId; // Tag's updateId when this range was created. 566 567 this(Tag tag, string namespace, string name, size_t updateId) 568 { 569 this.tag = tag; 570 this.namespace = namespace; 571 this.name = name; 572 this.updateId = updateId; 573 frontIndex = 0; 574 575 if( 576 tag !is null && 577 namespace in mixin("tag."~membersGrouped) && 578 name in mixin("tag."~membersGrouped~"[namespace]") 579 ) 580 endIndex = mixin("tag."~membersGrouped~"[namespace][name].length"); 581 else 582 endIndex = 0; 583 } 584 585 invariant() 586 { 587 assert( 588 this.updateId == tag.updateId, 589 "This range has been invalidated by a change to the tag." 590 ); 591 } 592 593 @property bool empty() 594 { 595 return tag is null || frontIndex == endIndex; 596 } 597 598 private size_t frontIndex; 599 @property T front() 600 { 601 return this[0]; 602 } 603 void popFront() 604 { 605 if(empty) 606 throw new DOMRangeException(tag, "Range is empty"); 607 608 frontIndex++; 609 } 610 611 private size_t endIndex; // One past the last element 612 @property T back() 613 { 614 return this[$-1]; 615 } 616 void popBack() 617 { 618 if(empty) 619 throw new DOMRangeException(tag, "Range is empty"); 620 621 endIndex--; 622 } 623 624 alias length opDollar; 625 @property size_t length() 626 { 627 return endIndex - frontIndex; 628 } 629 630 @property typeof(this) save() 631 { 632 auto r = typeof(this)(this.tag, this.namespace, this.name, this.updateId); 633 r.frontIndex = this.frontIndex; 634 r.endIndex = this.endIndex; 635 return r; 636 } 637 638 typeof(this) opSlice() 639 { 640 return save(); 641 } 642 643 typeof(this) opSlice(size_t start, size_t end) 644 { 645 auto r = save(); 646 r.frontIndex = this.frontIndex + start; 647 r.endIndex = this.frontIndex + end; 648 649 if( 650 r.frontIndex > this.endIndex || 651 r.endIndex > this.endIndex || 652 r.frontIndex > r.endIndex 653 ) 654 throw new DOMRangeException(tag, "Slice out of range"); 655 656 return r; 657 } 658 659 T opIndex(size_t index) 660 { 661 if(empty) 662 throw new DOMRangeException(tag, "Range is empty"); 663 664 return mixin("tag."~membersGrouped~"[namespace][name][frontIndex+index]"); 665 } 666 } 667 668 struct MemberRange(T, string allMembers, string memberIndicies, string membersGrouped) 669 { 670 private Tag tag; 671 private string namespace; // "*" indicates "all namespaces" (ok since it's not a valid namespace name) 672 private bool isMaybe; 673 private size_t updateId; // Tag's updateId when this range was created. 674 private size_t initialEndIndex; 675 676 this(Tag tag, string namespace, bool isMaybe) 677 { 678 this.tag = tag; 679 this.namespace = namespace; 680 this.isMaybe = isMaybe; 681 frontIndex = 0; 682 683 if(tag is null) 684 endIndex = 0; 685 else 686 { 687 this.updateId = tag.updateId; 688 689 if(namespace == "*") 690 initialEndIndex = mixin("tag."~allMembers~".length"); 691 else if(namespace in mixin("tag."~memberIndicies)) 692 initialEndIndex = mixin("tag."~memberIndicies~"[namespace].length"); 693 else 694 initialEndIndex = 0; 695 696 endIndex = initialEndIndex; 697 } 698 } 699 700 invariant() 701 { 702 assert( 703 this.updateId == tag.updateId, 704 "This range has been invalidated by a change to the tag." 705 ); 706 } 707 708 @property bool empty() 709 { 710 return tag is null || frontIndex == endIndex; 711 } 712 713 private size_t frontIndex; 714 @property T front() 715 { 716 return this[0]; 717 } 718 void popFront() 719 { 720 if(empty) 721 throw new DOMRangeException(tag, "Range is empty"); 722 723 frontIndex++; 724 } 725 726 private size_t endIndex; // One past the last element 727 @property T back() 728 { 729 return this[$-1]; 730 } 731 void popBack() 732 { 733 if(empty) 734 throw new DOMRangeException(tag, "Range is empty"); 735 736 endIndex--; 737 } 738 739 alias length opDollar; 740 @property size_t length() 741 { 742 return endIndex - frontIndex; 743 } 744 745 @property typeof(this) save() 746 { 747 auto r = typeof(this)(this.tag, this.namespace, this.isMaybe); 748 r.frontIndex = this.frontIndex; 749 r.endIndex = this.endIndex; 750 r.initialEndIndex = this.initialEndIndex; 751 r.updateId = this.updateId; 752 return r; 753 } 754 755 typeof(this) opSlice() 756 { 757 return save(); 758 } 759 760 typeof(this) opSlice(size_t start, size_t end) 761 { 762 auto r = save(); 763 r.frontIndex = this.frontIndex + start; 764 r.endIndex = this.frontIndex + end; 765 766 if( 767 r.frontIndex > this.endIndex || 768 r.endIndex > this.endIndex || 769 r.frontIndex > r.endIndex 770 ) 771 throw new DOMRangeException(tag, "Slice out of range"); 772 773 return r; 774 } 775 776 T opIndex(size_t index) 777 { 778 if(empty) 779 throw new DOMRangeException(tag, "Range is empty"); 780 781 if(namespace == "*") 782 return mixin("tag."~allMembers~"[ frontIndex+index ]"); 783 else 784 return mixin("tag."~allMembers~"[ tag."~memberIndicies~"[namespace][frontIndex+index] ]"); 785 } 786 787 alias NamedMemberRange!(T,membersGrouped) ThisNamedMemberRange; 788 ThisNamedMemberRange opIndex(string name) 789 { 790 if(frontIndex != 0 || endIndex != initialEndIndex) 791 { 792 throw new DOMRangeException(tag, 793 "Cannot lookup tags/attributes by name on a subset of a range, "~ 794 "only across the entire tag. "~ 795 "Please make sure you haven't called popFront or popBack on this "~ 796 "range and that you aren't using a slice of the range." 797 ); 798 } 799 800 if(!isMaybe && empty) 801 throw new DOMRangeException(tag, "Range is empty"); 802 803 if(!isMaybe && name !in this) 804 throw new DOMRangeException(tag, `No such `~T.stringof~` named: "`~name~`"`); 805 806 return ThisNamedMemberRange(tag, namespace, name, updateId); 807 } 808 809 bool opBinaryRight(string op)(string name) if(op=="in") 810 { 811 if(frontIndex != 0 || endIndex != initialEndIndex) 812 { 813 throw new DOMRangeException(tag, 814 "Cannot lookup tags/attributes by name on a subset of a range, "~ 815 "only across the entire tag. "~ 816 "Please make sure you haven't called popFront or popBack on this "~ 817 "range and that you aren't using a slice of the range." 818 ); 819 } 820 821 if(tag is null) 822 return false; 823 824 return 825 namespace in mixin("tag."~membersGrouped) && 826 name in mixin("tag."~membersGrouped~"[namespace]") && 827 mixin("tag."~membersGrouped~"[namespace][name].length") > 0; 828 } 829 } 830 831 struct NamespaceRange 832 { 833 private Tag tag; 834 private bool isMaybe; 835 private size_t updateId; // Tag's updateId when this range was created. 836 837 this(Tag tag, bool isMaybe) 838 { 839 this.tag = tag; 840 this.isMaybe = isMaybe; 841 if(tag !is null) 842 this.updateId = tag.updateId; 843 frontIndex = 0; 844 endIndex = tag.allNamespaces.length; 845 } 846 847 invariant() 848 { 849 assert( 850 this.updateId == tag.updateId, 851 "This range has been invalidated by a change to the tag." 852 ); 853 } 854 855 @property bool empty() 856 { 857 return frontIndex == endIndex; 858 } 859 860 private size_t frontIndex; 861 @property NamespaceAccess front() 862 { 863 return this[0]; 864 } 865 void popFront() 866 { 867 if(empty) 868 throw new DOMRangeException(tag, "Range is empty"); 869 870 frontIndex++; 871 } 872 873 private size_t endIndex; // One past the last element 874 @property NamespaceAccess back() 875 { 876 return this[$-1]; 877 } 878 void popBack() 879 { 880 if(empty) 881 throw new DOMRangeException(tag, "Range is empty"); 882 883 endIndex--; 884 } 885 886 alias length opDollar; 887 @property size_t length() 888 { 889 return endIndex - frontIndex; 890 } 891 892 @property NamespaceRange save() 893 { 894 auto r = NamespaceRange(this.tag, this.isMaybe); 895 r.frontIndex = this.frontIndex; 896 r.endIndex = this.endIndex; 897 r.updateId = this.updateId; 898 return r; 899 } 900 901 typeof(this) opSlice() 902 { 903 return save(); 904 } 905 906 typeof(this) opSlice(size_t start, size_t end) 907 { 908 auto r = save(); 909 r.frontIndex = this.frontIndex + start; 910 r.endIndex = this.frontIndex + end; 911 912 if( 913 r.frontIndex > this.endIndex || 914 r.endIndex > this.endIndex || 915 r.frontIndex > r.endIndex 916 ) 917 throw new DOMRangeException(tag, "Slice out of range"); 918 919 return r; 920 } 921 922 NamespaceAccess opIndex(size_t index) 923 { 924 if(empty) 925 throw new DOMRangeException(tag, "Range is empty"); 926 927 auto namespace = tag.allNamespaces[frontIndex+index]; 928 return NamespaceAccess( 929 namespace, 930 AttributeRange(tag, namespace, isMaybe), 931 TagRange(tag, namespace, isMaybe) 932 ); 933 } 934 935 NamespaceAccess opIndex(string namespace) 936 { 937 if(!isMaybe && empty) 938 throw new DOMRangeException(tag, "Range is empty"); 939 940 if(!isMaybe && namespace !in this) 941 throw new DOMRangeException(tag, `No such namespace: "`~namespace~`"`); 942 943 return NamespaceAccess( 944 namespace, 945 AttributeRange(tag, namespace, isMaybe), 946 TagRange(tag, namespace, isMaybe) 947 ); 948 } 949 950 /// Inefficient when range is a slice or has used popFront/popBack, but it works. 951 bool opBinaryRight(string op)(string namespace) if(op=="in") 952 { 953 if(frontIndex == 0 && endIndex == tag.allNamespaces.length) 954 { 955 return 956 namespace in tag.attributeIndicies || 957 namespace in tag.tagIndicies; 958 } 959 else 960 // Slower fallback method 961 return tag.allNamespaces[frontIndex..endIndex].canFind(namespace); 962 } 963 } 964 965 static struct NamespaceAccess 966 { 967 string name; 968 AttributeRange attributes; 969 TagRange tags; 970 } 971 972 alias MemberRange!(Attribute, "allAttributes", "attributeIndicies", "_attributes") AttributeRange; 973 alias MemberRange!(Tag, "allTags", "tagIndicies", "_tags" ) TagRange; 974 static assert(isRandomAccessRange!AttributeRange); 975 static assert(isRandomAccessRange!TagRange); 976 static assert(isRandomAccessRange!NamespaceRange); 977 978 /++ 979 Access all attributes that don't have a namespace 980 981 Returns a random access range of `Attribute` objects that supports 982 numeric-indexing, string-indexing, slicing and length. 983 984 Since SDLang allows multiple attributes with the same name, 985 string-indexing returns a random access range of all attributes 986 with the given name. 987 988 The string-indexing does $(B $(I not)) support namespace prefixes. 989 Use `namespace[string]`.`attributes` or `all`.`attributes` for that. 990 991 See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview) 992 for a high-level overview (and examples) of how to use this. 993 +/ 994 @property AttributeRange attributes() 995 { 996 return AttributeRange(this, "", false); 997 } 998 999 /++ 1000 Access all direct-child tags that don't have a namespace. 1001 1002 Returns a random access range of `Tag` objects that supports 1003 numeric-indexing, string-indexing, slicing and length. 1004 1005 Since SDLang allows multiple tags with the same name, string-indexing 1006 returns a random access range of all immediate child tags with the 1007 given name. 1008 1009 The string-indexing does $(B $(I not)) support namespace prefixes. 1010 Use `namespace[string]`.`attributes` or `all`.`attributes` for that. 1011 1012 See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview) 1013 for a high-level overview (and examples) of how to use this. 1014 +/ 1015 @property TagRange tags() 1016 { 1017 return TagRange(this, "", false); 1018 } 1019 1020 /++ 1021 Access all namespaces in this tag, and the attributes/tags within them. 1022 1023 Returns a random access range of `NamespaceAccess` elements that supports 1024 numeric-indexing, string-indexing, slicing and length. 1025 1026 See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview) 1027 for a high-level overview (and examples) of how to use this. 1028 +/ 1029 @property NamespaceRange namespaces() 1030 { 1031 return NamespaceRange(this, false); 1032 } 1033 1034 /// Access all attributes and tags regardless of namespace. 1035 /// 1036 /// See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview) 1037 /// for a better understanding (and examples) of how to use this. 1038 @property NamespaceAccess all() 1039 { 1040 // "*" isn't a valid namespace name, so we can use it to indicate "all namespaces" 1041 return NamespaceAccess( 1042 "*", 1043 AttributeRange(this, "*", false), 1044 TagRange(this, "*", false) 1045 ); 1046 } 1047 1048 struct MaybeAccess 1049 { 1050 Tag tag; 1051 1052 /// Access all attributes that don't have a namespace 1053 @property AttributeRange attributes() 1054 { 1055 return AttributeRange(tag, "", true); 1056 } 1057 1058 /// Access all direct-child tags that don't have a namespace 1059 @property TagRange tags() 1060 { 1061 return TagRange(tag, "", true); 1062 } 1063 1064 /// Access all namespaces in this tag, and the attributes/tags within them. 1065 @property NamespaceRange namespaces() 1066 { 1067 return NamespaceRange(tag, true); 1068 } 1069 1070 /// Access all attributes and tags regardless of namespace. 1071 @property NamespaceAccess all() 1072 { 1073 // "*" isn't a valid namespace name, so we can use it to indicate "all namespaces" 1074 return NamespaceAccess( 1075 "*", 1076 AttributeRange(tag, "*", true), 1077 TagRange(tag, "*", true) 1078 ); 1079 } 1080 } 1081 1082 /// Access `attributes`, `tags`, `namespaces` and `all` like normal, 1083 /// except that looking up a non-existant name/namespace with 1084 /// opIndex(string) results in an empty array instead of 1085 /// a thrown `sdlang.exception.DOMRangeException`. 1086 /// 1087 /// See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview) 1088 /// for a more information (and examples) of how to use this. 1089 @property MaybeAccess maybe() 1090 { 1091 return MaybeAccess(this); 1092 } 1093 1094 // Internal implementations for the get/expect functions further below: 1095 1096 private Tag getTagImpl(FullName tagFullName, Tag defaultValue=null, bool useDefaultValue=true) 1097 { 1098 auto tagNS = tagFullName.namespace; 1099 auto tagName = tagFullName.name; 1100 1101 // Can find namespace? 1102 if(tagNS !in _tags) 1103 { 1104 if(useDefaultValue) 1105 return defaultValue; 1106 else 1107 throw new TagNotFoundException(this, tagFullName, "No tags found in namespace '"~namespace~"'"); 1108 } 1109 1110 // Can find tag in namespace? 1111 if(tagName !in _tags[tagNS] || _tags[tagNS][tagName].length == 0) 1112 { 1113 if(useDefaultValue) 1114 return defaultValue; 1115 else 1116 throw new TagNotFoundException(this, tagFullName, "Can't find tag '"~tagFullName.toString()~"'"); 1117 } 1118 1119 // Return last matching tag found 1120 return _tags[tagNS][tagName][$-1]; 1121 } 1122 1123 private T getValueImpl(T)(T defaultValue, bool useDefaultValue=true) 1124 if(isValueType!T) 1125 { 1126 // Find value 1127 foreach(value; this.values) 1128 { 1129 if(value.type == typeid(T)) 1130 return value.get!T(); 1131 } 1132 1133 // No value of type T found 1134 if(useDefaultValue) 1135 return defaultValue; 1136 else 1137 { 1138 throw new ValueNotFoundException( 1139 this, 1140 FullName(this.namespace, this.name), 1141 typeid(T), 1142 "No value of type "~T.stringof~" found." 1143 ); 1144 } 1145 } 1146 1147 private T getAttributeImpl(T)(FullName attrFullName, T defaultValue, bool useDefaultValue=true) 1148 if(isValueType!T) 1149 { 1150 auto attrNS = attrFullName.namespace; 1151 auto attrName = attrFullName.name; 1152 1153 // Can find namespace and attribute name? 1154 if(attrNS !in this._attributes || attrName !in this._attributes[attrNS]) 1155 { 1156 if(useDefaultValue) 1157 return defaultValue; 1158 else 1159 { 1160 throw new AttributeNotFoundException( 1161 this, this.getFullName(), attrFullName, typeid(T), 1162 "Can't find attribute '"~FullName.combine(attrNS, attrName)~"'" 1163 ); 1164 } 1165 } 1166 1167 // Find value with chosen type 1168 foreach(attr; this._attributes[attrNS][attrName]) 1169 { 1170 if(attr.value.type == typeid(T)) 1171 return attr.value.get!T(); 1172 } 1173 1174 // Chosen type not found 1175 if(useDefaultValue) 1176 return defaultValue; 1177 else 1178 { 1179 throw new AttributeNotFoundException( 1180 this, this.getFullName(), attrFullName, typeid(T), 1181 "Can't find attribute '"~FullName.combine(attrNS, attrName)~"' of type "~T.stringof 1182 ); 1183 } 1184 } 1185 1186 // High-level interfaces for get/expect funtions: 1187 1188 /++ 1189 Lookup a child tag by name. Returns null if not found. 1190 1191 Useful if you only expect one, and only one, child tag of a given name. 1192 Only looks for immediate child tags of `this`, doesn't search recursively. 1193 1194 If you expect multiple tags by the same name and want to get them all, 1195 use `maybe`.`tags[string]` instead. 1196 1197 The name can optionally include a namespace, as in `"namespace:name"`. 1198 Or, you can search all namespaces using `"*:name"`. Use an empty string 1199 to search for anonymous tags, or `"namespace:"` for anonymous tags inside 1200 a namespace. Wildcard searching is only supported for namespaces, not names. 1201 Use `maybe`.`tags[0]` if you don't care about the name. 1202 1203 If there are multiple tags by the chosen name, the $(B $(I last tag)) will 1204 always be chosen. That is, this function considers later tags with the 1205 same name to override previous ones. 1206 1207 If the tag cannot be found, and you provides a default value, the default 1208 value is returned. Otherwise null is returned. If you'd prefer an 1209 exception thrown, use `expectTag` instead. 1210 +/ 1211 Tag getTag(string fullTagName, Tag defaultValue=null) 1212 { 1213 auto parsedName = FullName.parse(fullTagName); 1214 parsedName.ensureNoWildcardName( 1215 "Instead, use 'Tag.maybe.tags[0]', 'Tag.maybe.all.tags[0]' or 'Tag.maybe.namespace[ns].tags[0]'." 1216 ); 1217 return getTagImpl(parsedName, defaultValue); 1218 } 1219 1220 /// 1221 @("Tag.getTag") 1222 unittest 1223 { 1224 import std.exception; 1225 import sdlang.parser; 1226 1227 auto root = parseSource(` 1228 foo 1 1229 foo 2 // getTag considers this to override the first foo 1230 1231 ns1:foo 3 1232 ns1:foo 4 // getTag considers this to override the first ns1:foo 1233 ns2:foo 33 1234 ns2:foo 44 // getTag considers this to override the first ns2:foo 1235 `); 1236 assert( root.getTag("foo" ).values[0].get!int() == 2 ); 1237 assert( root.getTag("ns1:foo").values[0].get!int() == 4 ); 1238 assert( root.getTag("*:foo" ).values[0].get!int() == 44 ); // Search all namespaces 1239 1240 // Not found 1241 // If you'd prefer an exception, use `expectTag` instead. 1242 assert( root.getTag("doesnt-exist") is null ); 1243 1244 // Default value 1245 auto foo = root.getTag("foo"); 1246 assert( root.getTag("doesnt-exist", foo) is foo ); 1247 } 1248 1249 /++ 1250 Lookup a child tag by name. Throws if not found. 1251 1252 Useful if you only expect one, and only one, child tag of a given name. 1253 Only looks for immediate child tags of `this`, doesn't search recursively. 1254 1255 If you expect multiple tags by the same name and want to get them all, 1256 use `tags[string]` instead. 1257 1258 The name can optionally include a namespace, as in `"namespace:name"`. 1259 Or, you can search all namespaces using `"*:name"`. Use an empty string 1260 to search for anonymous tags, or `"namespace:"` for anonymous tags inside 1261 a namespace. Wildcard searching is only supported for namespaces, not names. 1262 Use `tags[0]` if you don't care about the name. 1263 1264 If there are multiple tags by the chosen name, the $(B $(I last tag)) will 1265 always be chosen. That is, this function considers later tags with the 1266 same name to override previous ones. 1267 1268 If no such tag is found, an `sdlang.exception.TagNotFoundException` will 1269 be thrown. If you'd rather receive a default value, use `getTag` instead. 1270 +/ 1271 Tag expectTag(string fullTagName) 1272 { 1273 auto parsedName = FullName.parse(fullTagName); 1274 parsedName.ensureNoWildcardName( 1275 "Instead, use 'Tag.tags[0]', 'Tag.all.tags[0]' or 'Tag.namespace[ns].tags[0]'." 1276 ); 1277 return getTagImpl(parsedName, null, false); 1278 } 1279 1280 /// 1281 @("Tag.expectTag") 1282 unittest 1283 { 1284 import std.exception; 1285 import sdlang.parser; 1286 1287 auto root = parseSource(` 1288 foo 1 1289 foo 2 // expectTag considers this to override the first foo 1290 1291 ns1:foo 3 1292 ns1:foo 4 // expectTag considers this to override the first ns1:foo 1293 ns2:foo 33 1294 ns2:foo 44 // expectTag considers this to override the first ns2:foo 1295 `); 1296 assert( root.expectTag("foo" ).values[0].get!int() == 2 ); 1297 assert( root.expectTag("ns1:foo").values[0].get!int() == 4 ); 1298 assert( root.expectTag("*:foo" ).values[0].get!int() == 44 ); // Search all namespaces 1299 1300 // Not found 1301 // If you'd rather receive a default value than an exception, use `getTag` instead. 1302 assertThrown!TagNotFoundException( root.expectTag("doesnt-exist") ); 1303 } 1304 1305 /++ 1306 Retrieve a value of type T from `this` tag. Returns a default value if not found. 1307 1308 Useful if you only expect one value of type T from this tag. Only looks for 1309 values of `this` tag, it does not search child tags. If you wish to search 1310 for a value in a child tag (for example, if this current tag is a root tag), 1311 try `getTagValue`. 1312 1313 If you want to get more than one value from this tag, use `values` instead. 1314 1315 If this tag has multiple values, the $(B $(I first)) value matching the 1316 requested type will be returned. Ie, Extra values in the tag are ignored. 1317 1318 You may provide a default value to be returned in case no value of 1319 the requested type can be found. If you don't provide a default value, 1320 `T.init` will be used. 1321 1322 If you'd rather an exception be thrown when a value cannot be found, 1323 use `expectValue` instead. 1324 +/ 1325 T getValue(T)(T defaultValue = T.init) if(isValueType!T) 1326 { 1327 return getValueImpl!T(defaultValue, true); 1328 } 1329 1330 /// 1331 @("Tag.getValue") 1332 unittest 1333 { 1334 import std.exception; 1335 import std.math; 1336 import sdlang.parser; 1337 1338 auto root = parseSource(` 1339 foo 1 true 2 false 1340 `); 1341 auto foo = root.getTag("foo"); 1342 assert( foo.getValue!int() == 1 ); 1343 assert( foo.getValue!bool() == true ); 1344 1345 // Value found, default value ignored. 1346 assert( foo.getValue!int(999) == 1 ); 1347 1348 // No strings found 1349 // If you'd prefer an exception, use `expectValue` instead. 1350 assert( foo.getValue!string("Default") == "Default" ); 1351 assert( foo.getValue!string() is null ); 1352 1353 // No floats found 1354 assert( foo.getValue!float(99.9).approxEqual(99.9) ); 1355 assert( foo.getValue!float().isNaN() ); 1356 } 1357 1358 /++ 1359 Retrieve a value of type T from `this` tag. Throws if not found. 1360 1361 Useful if you only expect one value of type T from this tag. Only looks 1362 for values of `this` tag, it does not search child tags. If you wish to 1363 search for a value in a child tag (for example, if this current tag is a 1364 root tag), try `expectTagValue`. 1365 1366 If you want to get more than one value from this tag, use `values` instead. 1367 1368 If this tag has multiple values, the $(B $(I first)) value matching the 1369 requested type will be returned. Ie, Extra values in the tag are ignored. 1370 1371 An `sdlang.exception.ValueNotFoundException` will be thrown if no value of 1372 the requested type can be found. If you'd rather receive a default value, 1373 use `getValue` instead. 1374 +/ 1375 T expectValue(T)() if(isValueType!T) 1376 { 1377 return getValueImpl!T(T.init, false); 1378 } 1379 1380 /// 1381 @("Tag.expectValue") 1382 unittest 1383 { 1384 import std.exception; 1385 import std.math; 1386 import sdlang.parser; 1387 1388 auto root = parseSource(` 1389 foo 1 true 2 false 1390 `); 1391 auto foo = root.getTag("foo"); 1392 assert( foo.expectValue!int() == 1 ); 1393 assert( foo.expectValue!bool() == true ); 1394 1395 // No strings or floats found 1396 // If you'd rather receive a default value than an exception, use `getValue` instead. 1397 assertThrown!ValueNotFoundException( foo.expectValue!string() ); 1398 assertThrown!ValueNotFoundException( foo.expectValue!float() ); 1399 } 1400 1401 /++ 1402 Lookup a child tag by name, and retrieve a value of type T from it. 1403 Returns a default value if not found. 1404 1405 Useful if you only expect one value of type T from a given tag. Only looks 1406 for immediate child tags of `this`, doesn't search recursively. 1407 1408 This is a shortcut for `getTag().getValue()`, except if the tag isn't found, 1409 then instead of a null reference error, it will return the requested 1410 `defaultValue` (or T.init by default). 1411 +/ 1412 T getTagValue(T)(string fullTagName, T defaultValue = T.init) if(isValueType!T) 1413 { 1414 auto tag = getTag(fullTagName); 1415 if(!tag) 1416 return defaultValue; 1417 1418 return tag.getValue!T(defaultValue); 1419 } 1420 1421 /// 1422 @("Tag.getTagValue") 1423 unittest 1424 { 1425 import std.exception; 1426 import sdlang.parser; 1427 1428 auto root = parseSource(` 1429 foo 1 "a" 2 "b" 1430 foo 3 "c" 4 "d" // getTagValue considers this to override the first foo 1431 1432 bar "hi" 1433 bar 379 // getTagValue considers this to override the first bar 1434 `); 1435 assert( root.getTagValue!int("foo") == 3 ); 1436 assert( root.getTagValue!string("foo") == "c" ); 1437 1438 // Value found, default value ignored. 1439 assert( root.getTagValue!int("foo", 999) == 3 ); 1440 1441 // Tag not found 1442 // If you'd prefer an exception, use `expectTagValue` instead. 1443 assert( root.getTagValue!int("doesnt-exist", 999) == 999 ); 1444 assert( root.getTagValue!int("doesnt-exist") == 0 ); 1445 1446 // The last "bar" tag doesn't have an int (only the first "bar" tag does) 1447 assert( root.getTagValue!string("bar", "Default") == "Default" ); 1448 assert( root.getTagValue!string("bar") is null ); 1449 1450 // Using namespaces: 1451 root = parseSource(` 1452 ns1:foo 1 "a" 2 "b" 1453 ns1:foo 3 "c" 4 "d" 1454 ns2:foo 11 "aa" 22 "bb" 1455 ns2:foo 33 "cc" 44 "dd" 1456 1457 ns1:bar "hi" 1458 ns1:bar 379 // getTagValue considers this to override the first bar 1459 `); 1460 assert( root.getTagValue!int("ns1:foo") == 3 ); 1461 assert( root.getTagValue!int("*:foo" ) == 33 ); // Search all namespaces 1462 1463 assert( root.getTagValue!string("ns1:foo") == "c" ); 1464 assert( root.getTagValue!string("*:foo" ) == "cc" ); // Search all namespaces 1465 1466 // The last "bar" tag doesn't have a string (only the first "bar" tag does) 1467 assert( root.getTagValue!string("*:bar", "Default") == "Default" ); 1468 assert( root.getTagValue!string("*:bar") is null ); 1469 } 1470 1471 /++ 1472 Lookup a child tag by name, and retrieve a value of type T from it. 1473 Throws if not found, 1474 1475 Useful if you only expect one value of type T from a given tag. Only 1476 looks for immediate child tags of `this`, doesn't search recursively. 1477 1478 This is a shortcut for `expectTag().expectValue()`. 1479 +/ 1480 T expectTagValue(T)(string fullTagName) if(isValueType!T) 1481 { 1482 return expectTag(fullTagName).expectValue!T(); 1483 } 1484 1485 /// 1486 @("Tag.expectTagValue") 1487 unittest 1488 { 1489 import std.exception; 1490 import sdlang.parser; 1491 1492 auto root = parseSource(` 1493 foo 1 "a" 2 "b" 1494 foo 3 "c" 4 "d" // expectTagValue considers this to override the first foo 1495 1496 bar "hi" 1497 bar 379 // expectTagValue considers this to override the first bar 1498 `); 1499 assert( root.expectTagValue!int("foo") == 3 ); 1500 assert( root.expectTagValue!string("foo") == "c" ); 1501 1502 // The last "bar" tag doesn't have a string (only the first "bar" tag does) 1503 // If you'd rather receive a default value than an exception, use `getTagValue` instead. 1504 assertThrown!ValueNotFoundException( root.expectTagValue!string("bar") ); 1505 1506 // Tag not found 1507 assertThrown!TagNotFoundException( root.expectTagValue!int("doesnt-exist") ); 1508 1509 // Using namespaces: 1510 root = parseSource(` 1511 ns1:foo 1 "a" 2 "b" 1512 ns1:foo 3 "c" 4 "d" 1513 ns2:foo 11 "aa" 22 "bb" 1514 ns2:foo 33 "cc" 44 "dd" 1515 1516 ns1:bar "hi" 1517 ns1:bar 379 // expectTagValue considers this to override the first bar 1518 `); 1519 assert( root.expectTagValue!int("ns1:foo") == 3 ); 1520 assert( root.expectTagValue!int("*:foo" ) == 33 ); // Search all namespaces 1521 1522 assert( root.expectTagValue!string("ns1:foo") == "c" ); 1523 assert( root.expectTagValue!string("*:foo" ) == "cc" ); // Search all namespaces 1524 1525 // The last "bar" tag doesn't have a string (only the first "bar" tag does) 1526 assertThrown!ValueNotFoundException( root.expectTagValue!string("*:bar") ); 1527 1528 // Namespace not found 1529 assertThrown!TagNotFoundException( root.expectTagValue!int("doesnt-exist:bar") ); 1530 } 1531 1532 /++ 1533 Lookup an attribute of `this` tag by name, and retrieve a value of type T 1534 from it. Returns a default value if not found. 1535 1536 Useful if you only expect one attribute of the given name and type. 1537 1538 Only looks for attributes of `this` tag, it does not search child tags. 1539 If you wish to search for a value in a child tag (for example, if this 1540 current tag is a root tag), try `getTagAttribute`. 1541 1542 If you expect multiple attributes by the same name and want to get them all, 1543 use `maybe`.`attributes[string]` instead. 1544 1545 The attribute name can optionally include a namespace, as in 1546 `"namespace:name"`. Or, you can search all namespaces using `"*:name"`. 1547 (Note that unlike tags. attributes can't be anonymous - that's what 1548 values are.) Wildcard searching is only supported for namespaces, not names. 1549 Use `maybe`.`attributes[0]` if you don't care about the name. 1550 1551 If this tag has multiple attributes, the $(B $(I first)) attribute 1552 matching the requested name and type will be returned. Ie, Extra 1553 attributes in the tag are ignored. 1554 1555 You may provide a default value to be returned in case no attribute of 1556 the requested name and type can be found. If you don't provide a default 1557 value, `T.init` will be used. 1558 1559 If you'd rather an exception be thrown when an attribute cannot be found, 1560 use `expectAttribute` instead. 1561 +/ 1562 T getAttribute(T)(string fullAttributeName, T defaultValue = T.init) if(isValueType!T) 1563 { 1564 auto parsedName = FullName.parse(fullAttributeName); 1565 parsedName.ensureNoWildcardName( 1566 "Instead, use 'Attribute.maybe.tags[0]', 'Attribute.maybe.all.tags[0]' or 'Attribute.maybe.namespace[ns].tags[0]'." 1567 ); 1568 return getAttributeImpl!T(parsedName, defaultValue); 1569 } 1570 1571 /// 1572 @("Tag.getAttribute") 1573 unittest 1574 { 1575 import std.exception; 1576 import std.math; 1577 import sdlang.parser; 1578 1579 auto root = parseSource(` 1580 foo z=0 X=1 X=true X=2 X=false 1581 `); 1582 auto foo = root.getTag("foo"); 1583 assert( foo.getAttribute!int("X") == 1 ); 1584 assert( foo.getAttribute!bool("X") == true ); 1585 1586 // Value found, default value ignored. 1587 assert( foo.getAttribute!int("X", 999) == 1 ); 1588 1589 // Attribute name not found 1590 // If you'd prefer an exception, use `expectValue` instead. 1591 assert( foo.getAttribute!int("doesnt-exist", 999) == 999 ); 1592 assert( foo.getAttribute!int("doesnt-exist") == 0 ); 1593 1594 // No strings found 1595 assert( foo.getAttribute!string("X", "Default") == "Default" ); 1596 assert( foo.getAttribute!string("X") is null ); 1597 1598 // No floats found 1599 assert( foo.getAttribute!float("X", 99.9).approxEqual(99.9) ); 1600 assert( foo.getAttribute!float("X").isNaN() ); 1601 1602 1603 // Using namespaces: 1604 root = parseSource(` 1605 foo ns1:z=0 ns1:X=1 ns1:X=2 ns2:X=3 ns2:X=4 1606 `); 1607 foo = root.getTag("foo"); 1608 assert( foo.getAttribute!int("ns2:X") == 3 ); 1609 assert( foo.getAttribute!int("*:X") == 1 ); // Search all namespaces 1610 1611 // Namespace not found 1612 assert( foo.getAttribute!int("doesnt-exist:X", 999) == 999 ); 1613 1614 // No attribute X is in the default namespace 1615 assert( foo.getAttribute!int("X", 999) == 999 ); 1616 1617 // Attribute name not found 1618 assert( foo.getAttribute!int("ns1:doesnt-exist", 999) == 999 ); 1619 } 1620 1621 /++ 1622 Lookup an attribute of `this` tag by name, and retrieve a value of type T 1623 from it. Throws if not found. 1624 1625 Useful if you only expect one attribute of the given name and type. 1626 1627 Only looks for attributes of `this` tag, it does not search child tags. 1628 If you wish to search for a value in a child tag (for example, if this 1629 current tag is a root tag), try `expectTagAttribute`. 1630 1631 If you expect multiple attributes by the same name and want to get them all, 1632 use `attributes[string]` instead. 1633 1634 The attribute name can optionally include a namespace, as in 1635 `"namespace:name"`. Or, you can search all namespaces using `"*:name"`. 1636 (Note that unlike tags. attributes can't be anonymous - that's what 1637 values are.) Wildcard searching is only supported for namespaces, not names. 1638 Use `attributes[0]` if you don't care about the name. 1639 1640 If this tag has multiple attributes, the $(B $(I first)) attribute 1641 matching the requested name and type will be returned. Ie, Extra 1642 attributes in the tag are ignored. 1643 1644 An `sdlang.exception.AttributeNotFoundException` will be thrown if no 1645 value of the requested type can be found. If you'd rather receive a 1646 default value, use `getAttribute` instead. 1647 +/ 1648 T expectAttribute(T)(string fullAttributeName) if(isValueType!T) 1649 { 1650 auto parsedName = FullName.parse(fullAttributeName); 1651 parsedName.ensureNoWildcardName( 1652 "Instead, use 'Attribute.tags[0]', 'Attribute.all.tags[0]' or 'Attribute.namespace[ns].tags[0]'." 1653 ); 1654 return getAttributeImpl!T(parsedName, T.init, false); 1655 } 1656 1657 /// 1658 @("Tag.expectAttribute") 1659 unittest 1660 { 1661 import std.exception; 1662 import std.math; 1663 import sdlang.parser; 1664 1665 auto root = parseSource(` 1666 foo z=0 X=1 X=true X=2 X=false 1667 `); 1668 auto foo = root.getTag("foo"); 1669 assert( foo.expectAttribute!int("X") == 1 ); 1670 assert( foo.expectAttribute!bool("X") == true ); 1671 1672 // Attribute name not found 1673 // If you'd rather receive a default value than an exception, use `getAttribute` instead. 1674 assertThrown!AttributeNotFoundException( foo.expectAttribute!int("doesnt-exist") ); 1675 1676 // No strings found 1677 assertThrown!AttributeNotFoundException( foo.expectAttribute!string("X") ); 1678 1679 // No floats found 1680 assertThrown!AttributeNotFoundException( foo.expectAttribute!float("X") ); 1681 1682 1683 // Using namespaces: 1684 root = parseSource(` 1685 foo ns1:z=0 ns1:X=1 ns1:X=2 ns2:X=3 ns2:X=4 1686 `); 1687 foo = root.getTag("foo"); 1688 assert( foo.expectAttribute!int("ns2:X") == 3 ); 1689 assert( foo.expectAttribute!int("*:X") == 1 ); // Search all namespaces 1690 1691 // Namespace not found 1692 assertThrown!AttributeNotFoundException( foo.expectAttribute!int("doesnt-exist:X") ); 1693 1694 // No attribute X is in the default namespace 1695 assertThrown!AttributeNotFoundException( foo.expectAttribute!int("X") ); 1696 1697 // Attribute name not found 1698 assertThrown!AttributeNotFoundException( foo.expectAttribute!int("ns1:doesnt-exist") ); 1699 } 1700 1701 /++ 1702 Lookup a child tag and attribute by name, and retrieve a value of type T 1703 from it. Returns a default value if not found. 1704 1705 Useful if you only expect one attribute of type T from given 1706 the tag and attribute names. Only looks for immediate child tags of 1707 `this`, doesn't search recursively. 1708 1709 This is a shortcut for `getTag().getAttribute()`, except if the tag isn't 1710 found, then instead of a null reference error, it will return the requested 1711 `defaultValue` (or T.init by default). 1712 +/ 1713 T getTagAttribute(T)(string fullTagName, string fullAttributeName, T defaultValue = T.init) if(isValueType!T) 1714 { 1715 auto tag = getTag(fullTagName); 1716 if(!tag) 1717 return defaultValue; 1718 1719 return tag.getAttribute!T(fullAttributeName, defaultValue); 1720 } 1721 1722 /// 1723 @("Tag.getTagAttribute") 1724 unittest 1725 { 1726 import std.exception; 1727 import sdlang.parser; 1728 1729 auto root = parseSource(` 1730 foo X=1 X="a" X=2 X="b" 1731 foo X=3 X="c" X=4 X="d" // getTagAttribute considers this to override the first foo 1732 1733 bar X="hi" 1734 bar X=379 // getTagAttribute considers this to override the first bar 1735 `); 1736 assert( root.getTagAttribute!int("foo", "X") == 3 ); 1737 assert( root.getTagAttribute!string("foo", "X") == "c" ); 1738 1739 // Value found, default value ignored. 1740 assert( root.getTagAttribute!int("foo", "X", 999) == 3 ); 1741 1742 // Tag not found 1743 // If you'd prefer an exception, use `expectTagAttribute` instead of `getTagAttribute` 1744 assert( root.getTagAttribute!int("doesnt-exist", "X", 999) == 999 ); 1745 assert( root.getTagAttribute!int("doesnt-exist", "X") == 0 ); 1746 assert( root.getTagAttribute!int("foo", "doesnt-exist", 999) == 999 ); 1747 assert( root.getTagAttribute!int("foo", "doesnt-exist") == 0 ); 1748 1749 // The last "bar" tag doesn't have a string (only the first "bar" tag does) 1750 assert( root.getTagAttribute!string("bar", "X", "Default") == "Default" ); 1751 assert( root.getTagAttribute!string("bar", "X") is null ); 1752 1753 1754 // Using namespaces: 1755 root = parseSource(` 1756 ns1:foo X=1 X="a" X=2 X="b" 1757 ns1:foo X=3 X="c" X=4 X="d" 1758 ns2:foo X=11 X="aa" X=22 X="bb" 1759 ns2:foo X=33 X="cc" X=44 X="dd" 1760 1761 ns1:bar attrNS:X="hi" 1762 ns1:bar attrNS:X=379 // getTagAttribute considers this to override the first bar 1763 `); 1764 assert( root.getTagAttribute!int("ns1:foo", "X") == 3 ); 1765 assert( root.getTagAttribute!int("*:foo", "X") == 33 ); // Search all namespaces 1766 1767 assert( root.getTagAttribute!string("ns1:foo", "X") == "c" ); 1768 assert( root.getTagAttribute!string("*:foo", "X") == "cc" ); // Search all namespaces 1769 1770 // bar's attribute X is't in the default namespace 1771 assert( root.getTagAttribute!int("*:bar", "X", 999) == 999 ); 1772 assert( root.getTagAttribute!int("*:bar", "X") == 0 ); 1773 1774 // The last "bar" tag's "attrNS:X" attribute doesn't have a string (only the first "bar" tag does) 1775 assert( root.getTagAttribute!string("*:bar", "attrNS:X", "Default") == "Default" ); 1776 assert( root.getTagAttribute!string("*:bar", "attrNS:X") is null); 1777 } 1778 1779 /++ 1780 Lookup a child tag and attribute by name, and retrieve a value of type T 1781 from it. Throws if not found. 1782 1783 Useful if you only expect one attribute of type T from given 1784 the tag and attribute names. Only looks for immediate child tags of 1785 `this`, doesn't search recursively. 1786 1787 This is a shortcut for `expectTag().expectAttribute()`. 1788 +/ 1789 T expectTagAttribute(T)(string fullTagName, string fullAttributeName) if(isValueType!T) 1790 { 1791 return expectTag(fullTagName).expectAttribute!T(fullAttributeName); 1792 } 1793 1794 /// 1795 @("Tag.expectTagAttribute") 1796 unittest 1797 { 1798 import std.exception; 1799 import sdlang.parser; 1800 1801 auto root = parseSource(` 1802 foo X=1 X="a" X=2 X="b" 1803 foo X=3 X="c" X=4 X="d" // expectTagAttribute considers this to override the first foo 1804 1805 bar X="hi" 1806 bar X=379 // expectTagAttribute considers this to override the first bar 1807 `); 1808 assert( root.expectTagAttribute!int("foo", "X") == 3 ); 1809 assert( root.expectTagAttribute!string("foo", "X") == "c" ); 1810 1811 // The last "bar" tag doesn't have an int attribute named "X" (only the first "bar" tag does) 1812 // If you'd rather receive a default value than an exception, use `getAttribute` instead. 1813 assertThrown!AttributeNotFoundException( root.expectTagAttribute!string("bar", "X") ); 1814 1815 // Tag not found 1816 assertThrown!TagNotFoundException( root.expectTagAttribute!int("doesnt-exist", "X") ); 1817 1818 // Using namespaces: 1819 root = parseSource(` 1820 ns1:foo X=1 X="a" X=2 X="b" 1821 ns1:foo X=3 X="c" X=4 X="d" 1822 ns2:foo X=11 X="aa" X=22 X="bb" 1823 ns2:foo X=33 X="cc" X=44 X="dd" 1824 1825 ns1:bar attrNS:X="hi" 1826 ns1:bar attrNS:X=379 // expectTagAttribute considers this to override the first bar 1827 `); 1828 assert( root.expectTagAttribute!int("ns1:foo", "X") == 3 ); 1829 assert( root.expectTagAttribute!int("*:foo", "X") == 33 ); // Search all namespaces 1830 1831 assert( root.expectTagAttribute!string("ns1:foo", "X") == "c" ); 1832 assert( root.expectTagAttribute!string("*:foo", "X") == "cc" ); // Search all namespaces 1833 1834 // bar's attribute X is't in the default namespace 1835 assertThrown!AttributeNotFoundException( root.expectTagAttribute!int("*:bar", "X") ); 1836 1837 // The last "bar" tag's "attrNS:X" attribute doesn't have a string (only the first "bar" tag does) 1838 assertThrown!AttributeNotFoundException( root.expectTagAttribute!string("*:bar", "attrNS:X") ); 1839 1840 // Tag's namespace not found 1841 assertThrown!TagNotFoundException( root.expectTagAttribute!int("doesnt-exist:bar", "attrNS:X") ); 1842 } 1843 1844 /++ 1845 Lookup a child tag by name, and retrieve all values from it. 1846 1847 This just like using `getTag()`.`values`, except if the tag isn't found, 1848 it safely returns null (or an optional array of default values) instead of 1849 a dereferencing null error. 1850 1851 Note that, unlike `getValue`, this doesn't discriminate by the value's 1852 type. It simply returns all values of a single tag as a `Value[]`. 1853 1854 If you'd prefer an exception thrown when the tag isn't found, use 1855 `expectTag`.`values` instead. 1856 +/ 1857 Value[] getTagValues(string fullTagName, Value[] defaultValues = null) 1858 { 1859 auto tag = getTag(fullTagName); 1860 if(tag) 1861 return tag.values; 1862 else 1863 return defaultValues; 1864 } 1865 1866 /// 1867 @("getTagValues") 1868 unittest 1869 { 1870 import std.exception; 1871 import sdlang.parser; 1872 1873 auto root = parseSource(` 1874 foo 1 "a" 2 "b" 1875 foo 3 "c" 4 "d" // getTagValues considers this to override the first foo 1876 `); 1877 assert( root.getTagValues("foo") == [Value(3), Value("c"), Value(4), Value("d")] ); 1878 1879 // Tag not found 1880 // If you'd prefer an exception, use `expectTag.values` instead. 1881 assert( root.getTagValues("doesnt-exist") is null ); 1882 assert( root.getTagValues("doesnt-exist", [ Value(999), Value("Not found") ]) == 1883 [ Value(999), Value("Not found") ] ); 1884 } 1885 1886 /++ 1887 Lookup a child tag by name, and retrieve all attributes in a chosen 1888 (or default) namespace from it. 1889 1890 This just like using `getTag()`.`attributes` (or 1891 `getTag()`.`namespace[...]`.`attributes`, or `getTag()`.`all`.`attributes`), 1892 except if the tag isn't found, it safely returns an empty range instead 1893 of a dereferencing null error. 1894 1895 If provided, the `attributeNamespace` parameter can be either the name of 1896 a namespace, or an empty string for the default namespace (the default), 1897 or `"*"` to retreive attributes from all namespaces. 1898 1899 Note that, unlike `getAttributes`, this doesn't discriminate by the 1900 value's type. It simply returns the usual `attributes` range. 1901 1902 If you'd prefer an exception thrown when the tag isn't found, use 1903 `expectTag`.`attributes` instead. 1904 +/ 1905 auto getTagAttributes(string fullTagName, string attributeNamespace = null) 1906 { 1907 auto tag = getTag(fullTagName); 1908 if(tag) 1909 { 1910 if(attributeNamespace && attributeNamespace in tag.namespaces) 1911 return tag.namespaces[attributeNamespace].attributes; 1912 else if(attributeNamespace == "*") 1913 return tag.all.attributes; 1914 else 1915 return tag.attributes; 1916 } 1917 1918 return AttributeRange(null, null, false); 1919 } 1920 1921 /// 1922 @("getTagAttributes") 1923 unittest 1924 { 1925 import std.exception; 1926 import sdlang.parser; 1927 1928 auto root = parseSource(` 1929 foo X=1 X=2 1930 1931 // getTagAttributes considers this to override the first foo 1932 foo X1=3 X2="c" namespace:bar=7 X3=4 X4="d" 1933 `); 1934 1935 auto fooAttrs = root.getTagAttributes("foo"); 1936 assert( !fooAttrs.empty ); 1937 assert( fooAttrs.length == 4 ); 1938 assert( fooAttrs[0].name == "X1" && fooAttrs[0].value == Value(3) ); 1939 assert( fooAttrs[1].name == "X2" && fooAttrs[1].value == Value("c") ); 1940 assert( fooAttrs[2].name == "X3" && fooAttrs[2].value == Value(4) ); 1941 assert( fooAttrs[3].name == "X4" && fooAttrs[3].value == Value("d") ); 1942 1943 fooAttrs = root.getTagAttributes("foo", "namespace"); 1944 assert( !fooAttrs.empty ); 1945 assert( fooAttrs.length == 1 ); 1946 assert( fooAttrs[0].name == "bar" && fooAttrs[0].value == Value(7) ); 1947 1948 fooAttrs = root.getTagAttributes("foo", "*"); 1949 assert( !fooAttrs.empty ); 1950 assert( fooAttrs.length == 5 ); 1951 assert( fooAttrs[0].name == "X1" && fooAttrs[0].value == Value(3) ); 1952 assert( fooAttrs[1].name == "X2" && fooAttrs[1].value == Value("c") ); 1953 assert( fooAttrs[2].name == "bar" && fooAttrs[2].value == Value(7) ); 1954 assert( fooAttrs[3].name == "X3" && fooAttrs[3].value == Value(4) ); 1955 assert( fooAttrs[4].name == "X4" && fooAttrs[4].value == Value("d") ); 1956 1957 // Tag not found 1958 // If you'd prefer an exception, use `expectTag.attributes` instead. 1959 assert( root.getTagValues("doesnt-exist").empty ); 1960 } 1961 1962 @("*: Disallow wildcards for names") 1963 unittest 1964 { 1965 import std.exception; 1966 import std.math; 1967 import sdlang.parser; 1968 1969 auto root = parseSource(` 1970 foo 1 X=2 1971 ns:foo 3 ns:X=4 1972 `); 1973 auto foo = root.getTag("foo"); 1974 auto nsfoo = root.getTag("ns:foo"); 1975 1976 // Sanity check 1977 assert( foo !is null ); 1978 assert( foo.name == "foo" ); 1979 assert( foo.namespace == "" ); 1980 1981 assert( nsfoo !is null ); 1982 assert( nsfoo.name == "foo" ); 1983 assert( nsfoo.namespace == "ns" ); 1984 1985 assert( foo.getValue !int() == 1 ); 1986 assert( foo.expectValue !int() == 1 ); 1987 assert( nsfoo.getValue !int() == 3 ); 1988 assert( nsfoo.expectValue!int() == 3 ); 1989 1990 assert( root.getTagValue !int("foo") == 1 ); 1991 assert( root.expectTagValue!int("foo") == 1 ); 1992 assert( root.getTagValue !int("ns:foo") == 3 ); 1993 assert( root.expectTagValue!int("ns:foo") == 3 ); 1994 1995 assert( foo.getAttribute !int("X") == 2 ); 1996 assert( foo.expectAttribute !int("X") == 2 ); 1997 assert( nsfoo.getAttribute !int("ns:X") == 4 ); 1998 assert( nsfoo.expectAttribute!int("ns:X") == 4 ); 1999 2000 assert( root.getTagAttribute !int("foo", "X") == 2 ); 2001 assert( root.expectTagAttribute!int("foo", "X") == 2 ); 2002 assert( root.getTagAttribute !int("ns:foo", "ns:X") == 4 ); 2003 assert( root.expectTagAttribute!int("ns:foo", "ns:X") == 4 ); 2004 2005 // No namespace 2006 assertThrown!ArgumentException( root.getTag ("*") ); 2007 assertThrown!ArgumentException( root.expectTag("*") ); 2008 2009 assertThrown!ArgumentException( root.getTagValue !int("*") ); 2010 assertThrown!ArgumentException( root.expectTagValue!int("*") ); 2011 2012 assertThrown!ArgumentException( foo.getAttribute !int("*") ); 2013 assertThrown!ArgumentException( foo.expectAttribute !int("*") ); 2014 assertThrown!ArgumentException( root.getTagAttribute !int("*", "X") ); 2015 assertThrown!ArgumentException( root.expectTagAttribute!int("*", "X") ); 2016 assertThrown!ArgumentException( root.getTagAttribute !int("foo", "*") ); 2017 assertThrown!ArgumentException( root.expectTagAttribute!int("foo", "*") ); 2018 2019 // With namespace 2020 assertThrown!ArgumentException( root.getTag ("ns:*") ); 2021 assertThrown!ArgumentException( root.expectTag("ns:*") ); 2022 2023 assertThrown!ArgumentException( root.getTagValue !int("ns:*") ); 2024 assertThrown!ArgumentException( root.expectTagValue!int("ns:*") ); 2025 2026 assertThrown!ArgumentException( nsfoo.getAttribute !int("ns:*") ); 2027 assertThrown!ArgumentException( nsfoo.expectAttribute !int("ns:*") ); 2028 assertThrown!ArgumentException( root.getTagAttribute !int("ns:*", "ns:X") ); 2029 assertThrown!ArgumentException( root.expectTagAttribute!int("ns:*", "ns:X") ); 2030 assertThrown!ArgumentException( root.getTagAttribute !int("ns:foo", "ns:*") ); 2031 assertThrown!ArgumentException( root.expectTagAttribute!int("ns:foo", "ns:*") ); 2032 2033 // With wildcard namespace 2034 assertThrown!ArgumentException( root.getTag ("*:*") ); 2035 assertThrown!ArgumentException( root.expectTag("*:*") ); 2036 2037 assertThrown!ArgumentException( root.getTagValue !int("*:*") ); 2038 assertThrown!ArgumentException( root.expectTagValue!int("*:*") ); 2039 2040 assertThrown!ArgumentException( nsfoo.getAttribute !int("*:*") ); 2041 assertThrown!ArgumentException( nsfoo.expectAttribute !int("*:*") ); 2042 assertThrown!ArgumentException( root.getTagAttribute !int("*:*", "*:X") ); 2043 assertThrown!ArgumentException( root.expectTagAttribute!int("*:*", "*:X") ); 2044 assertThrown!ArgumentException( root.getTagAttribute !int("*:foo", "*:*") ); 2045 assertThrown!ArgumentException( root.expectTagAttribute!int("*:foo", "*:*") ); 2046 } 2047 2048 override bool opEquals(Object o) 2049 { 2050 auto t = cast(Tag)o; 2051 if(!t) 2052 return false; 2053 2054 if(_namespace != t._namespace || _name != t._name) 2055 return false; 2056 2057 if( 2058 values .length != t.values .length || 2059 allAttributes .length != t.allAttributes.length || 2060 allNamespaces .length != t.allNamespaces.length || 2061 allTags .length != t.allTags .length 2062 ) 2063 return false; 2064 2065 if(values != t.values) 2066 return false; 2067 2068 if(allNamespaces != t.allNamespaces) 2069 return false; 2070 2071 if(allAttributes != t.allAttributes) 2072 return false; 2073 2074 // Ok because cycles are not allowed 2075 //TODO: Actually check for or prevent cycles. 2076 return allTags == t.allTags; 2077 } 2078 2079 /// Treats `this` as the root tag. Note that root tags cannot have 2080 /// values or attributes, and cannot be part of a namespace. 2081 /// If this isn't a valid root tag, `sdlang.exception.ValidationException` 2082 /// will be thrown. 2083 string toSDLDocument()(string indent="\t", int indentLevel=0) 2084 { 2085 Appender!string sink; 2086 toSDLDocument(sink, indent, indentLevel); 2087 return sink.data; 2088 } 2089 2090 ///ditto 2091 void toSDLDocument(Sink)(ref Sink sink, string indent="\t", int indentLevel=0) 2092 if(isOutputRange!(Sink,char)) 2093 { 2094 if(values.length > 0) 2095 throw new ValidationException("Root tags cannot have any values, only child tags."); 2096 2097 if(allAttributes.length > 0) 2098 throw new ValidationException("Root tags cannot have any attributes, only child tags."); 2099 2100 if(_namespace != "") 2101 throw new ValidationException("Root tags cannot have a namespace."); 2102 2103 foreach(tag; allTags) 2104 tag.toSDLString(sink, indent, indentLevel); 2105 } 2106 2107 /// Output this entire tag in SDL format. Does $(B $(I not)) treat `this` as 2108 /// a root tag. If you intend this to be the root of a standard SDL 2109 /// document, use `toSDLDocument` instead. 2110 string toSDLString()(string indent="\t", int indentLevel=0) 2111 { 2112 Appender!string sink; 2113 toSDLString(sink, indent, indentLevel); 2114 return sink.data; 2115 } 2116 2117 ///ditto 2118 void toSDLString(Sink)(ref Sink sink, string indent="\t", int indentLevel=0) 2119 if(isOutputRange!(Sink,char)) 2120 { 2121 if(_name == "" && values.length == 0) 2122 throw new ValidationException("Anonymous tags must have at least one value."); 2123 2124 if(_name == "" && _namespace != "") 2125 throw new ValidationException("Anonymous tags cannot have a namespace."); 2126 2127 // Indent 2128 foreach(i; 0..indentLevel) 2129 sink.put(indent); 2130 2131 // Name 2132 if(_namespace != "") 2133 { 2134 sink.put(_namespace); 2135 sink.put(':'); 2136 } 2137 sink.put(_name); 2138 2139 // Values 2140 foreach(i, v; values) 2141 { 2142 // Omit the first space for anonymous tags 2143 if(_name != "" || i > 0) 2144 sink.put(' '); 2145 2146 v.toSDLString(sink); 2147 } 2148 2149 // Attributes 2150 foreach(attr; allAttributes) 2151 { 2152 sink.put(' '); 2153 attr.toSDLString(sink); 2154 } 2155 2156 // Child tags 2157 bool foundChild=false; 2158 foreach(tag; allTags) 2159 { 2160 if(!foundChild) 2161 { 2162 sink.put(" {\n"); 2163 foundChild = true; 2164 } 2165 2166 tag.toSDLString(sink, indent, indentLevel+1); 2167 } 2168 if(foundChild) 2169 { 2170 foreach(i; 0..indentLevel) 2171 sink.put(indent); 2172 2173 sink.put("}\n"); 2174 } 2175 else 2176 sink.put("\n"); 2177 } 2178 2179 /// Outputs full information on the tag. 2180 string toDebugString() 2181 { 2182 import std.algorithm : sort; 2183 2184 Appender!string buf; 2185 2186 buf.put("\n"); 2187 buf.put("Tag "); 2188 if(_namespace != "") 2189 { 2190 buf.put("["); 2191 buf.put(_namespace); 2192 buf.put("]"); 2193 } 2194 buf.put("'%s':\n".format(_name)); 2195 2196 // Values 2197 foreach(val; values) 2198 buf.put(" (%s): %s\n".format(.toString(val.type), val)); 2199 2200 // Attributes 2201 foreach(attrNamespace; _attributes.keys.sort()) 2202 if(attrNamespace != "*") 2203 foreach(attrName; _attributes[attrNamespace].keys.sort()) 2204 foreach(attr; _attributes[attrNamespace][attrName]) 2205 { 2206 string namespaceStr; 2207 if(attr._namespace != "") 2208 namespaceStr = "["~attr._namespace~"]"; 2209 2210 buf.put( 2211 " %s%s(%s): %s\n".format( 2212 namespaceStr, attr._name, .toString(attr.value.type), attr.value 2213 ) 2214 ); 2215 } 2216 2217 // Children 2218 foreach(tagNamespace; _tags.keys.sort()) 2219 if(tagNamespace != "*") 2220 foreach(tagName; _tags[tagNamespace].keys.sort()) 2221 foreach(tag; _tags[tagNamespace][tagName]) 2222 buf.put( tag.toDebugString().replace("\n", "\n ") ); 2223 2224 return buf.data; 2225 } 2226 } 2227 2228 version(unittest) 2229 { 2230 private void testRandomAccessRange(R, E)(R range, E[] expected, bool function(E, E) equals=null) 2231 { 2232 static assert(isRandomAccessRange!R); 2233 static assert(is(ElementType!R == E)); 2234 static assert(hasLength!R); 2235 static assert(!isInfinite!R); 2236 2237 assert(range.length == expected.length); 2238 if(range.length == 0) 2239 { 2240 assert(range.empty); 2241 return; 2242 } 2243 2244 static bool defaultEquals(E e1, E e2) 2245 { 2246 return e1 == e2; 2247 } 2248 if(equals is null) 2249 equals = &defaultEquals; 2250 2251 assert(equals(range.front, expected[0])); 2252 assert(equals(range.front, expected[0])); // Ensure consistent result from '.front' 2253 assert(equals(range.front, expected[0])); // Ensure consistent result from '.front' 2254 2255 assert(equals(range.back, expected[$-1])); 2256 assert(equals(range.back, expected[$-1])); // Ensure consistent result from '.back' 2257 assert(equals(range.back, expected[$-1])); // Ensure consistent result from '.back' 2258 2259 // Forward iteration 2260 auto original = range.save; 2261 auto r2 = range.save; 2262 foreach(i; 0..expected.length) 2263 { 2264 //trace("Forward iteration: ", i); 2265 2266 // Test length/empty 2267 assert(range.length == expected.length - i); 2268 assert(range.length == r2.length); 2269 assert(!range.empty); 2270 assert(!r2.empty); 2271 2272 // Test front 2273 assert(equals(range.front, expected[i])); 2274 assert(equals(range.front, r2.front)); 2275 2276 // Test back 2277 assert(equals(range.back, expected[$-1])); 2278 assert(equals(range.back, r2.back)); 2279 2280 // Test opIndex(0) 2281 assert(equals(range[0], expected[i])); 2282 assert(equals(range[0], r2[0])); 2283 2284 // Test opIndex($-1) 2285 assert(equals(range[$-1], expected[$-1])); 2286 assert(equals(range[$-1], r2[$-1])); 2287 2288 // Test popFront 2289 range.popFront(); 2290 assert(range.length == r2.length - 1); 2291 r2.popFront(); 2292 assert(range.length == r2.length); 2293 } 2294 assert(range.empty); 2295 assert(r2.empty); 2296 assert(original.length == expected.length); 2297 2298 // Backwards iteration 2299 range = original.save; 2300 r2 = original.save; 2301 foreach(i; iota(0, expected.length).retro()) 2302 { 2303 //trace("Backwards iteration: ", i); 2304 2305 // Test length/empty 2306 assert(range.length == i+1); 2307 assert(range.length == r2.length); 2308 assert(!range.empty); 2309 assert(!r2.empty); 2310 2311 // Test front 2312 assert(equals(range.front, expected[0])); 2313 assert(equals(range.front, r2.front)); 2314 2315 // Test back 2316 assert(equals(range.back, expected[i])); 2317 assert(equals(range.back, r2.back)); 2318 2319 // Test opIndex(0) 2320 assert(equals(range[0], expected[0])); 2321 assert(equals(range[0], r2[0])); 2322 2323 // Test opIndex($-1) 2324 assert(equals(range[$-1], expected[i])); 2325 assert(equals(range[$-1], r2[$-1])); 2326 2327 // Test popBack 2328 range.popBack(); 2329 assert(range.length == r2.length - 1); 2330 r2.popBack(); 2331 assert(range.length == r2.length); 2332 } 2333 assert(range.empty); 2334 assert(r2.empty); 2335 assert(original.length == expected.length); 2336 2337 // Random access 2338 range = original.save; 2339 r2 = original.save; 2340 foreach(i; 0..expected.length) 2341 { 2342 //trace("Random access: ", i); 2343 2344 // Test length/empty 2345 assert(range.length == expected.length); 2346 assert(range.length == r2.length); 2347 assert(!range.empty); 2348 assert(!r2.empty); 2349 2350 // Test front 2351 assert(equals(range.front, expected[0])); 2352 assert(equals(range.front, r2.front)); 2353 2354 // Test back 2355 assert(equals(range.back, expected[$-1])); 2356 assert(equals(range.back, r2.back)); 2357 2358 // Test opIndex(i) 2359 assert(equals(range[i], expected[i])); 2360 assert(equals(range[i], r2[i])); 2361 } 2362 assert(!range.empty); 2363 assert(!r2.empty); 2364 assert(original.length == expected.length); 2365 } 2366 } 2367 2368 @("*: Test sdlang ast") 2369 unittest 2370 { 2371 import std.exception; 2372 import sdlang.parser; 2373 2374 Tag root; 2375 root = parseSource(""); 2376 testRandomAccessRange(root.attributes, cast( Attribute[])[]); 2377 testRandomAccessRange(root.tags, cast( Tag[])[]); 2378 testRandomAccessRange(root.namespaces, cast(Tag.NamespaceAccess[])[]); 2379 2380 root = parseSource(` 2381 blue 3 "Lee" isThree=true 2382 blue 5 "Chan" 12345 isThree=false 2383 stuff:orange 1 2 3 2 1 2384 stuff:square points=4 dimensions=2 points="Still four" 2385 stuff:triangle data:points=3 data:dimensions=2 2386 nothing 2387 namespaces small:A=1 med:A=2 big:A=3 small:B=10 big:B=30 2388 2389 people visitor:a=1 b=2 { 2390 chiyo "Small" "Flies?" nemesis="Car" score=100 2391 yukari 2392 visitor:sana 2393 tomo 2394 visitor:hayama 2395 } 2396 `); 2397 2398 auto blue3 = new Tag( 2399 null, "", "blue", 2400 [ Value(3), Value("Lee") ], 2401 [ new Attribute("isThree", Value(true)) ], 2402 null 2403 ); 2404 auto blue5 = new Tag( 2405 null, "", "blue", 2406 [ Value(5), Value("Chan"), Value(12345) ], 2407 [ new Attribute("isThree", Value(false)) ], 2408 null 2409 ); 2410 auto orange = new Tag( 2411 null, "stuff", "orange", 2412 [ Value(1), Value(2), Value(3), Value(2), Value(1) ], 2413 null, 2414 null 2415 ); 2416 auto square = new Tag( 2417 null, "stuff", "square", 2418 null, 2419 [ 2420 new Attribute("points", Value(4)), 2421 new Attribute("dimensions", Value(2)), 2422 new Attribute("points", Value("Still four")), 2423 ], 2424 null 2425 ); 2426 auto triangle = new Tag( 2427 null, "stuff", "triangle", 2428 null, 2429 [ 2430 new Attribute("data", "points", Value(3)), 2431 new Attribute("data", "dimensions", Value(2)), 2432 ], 2433 null 2434 ); 2435 auto nothing = new Tag( 2436 null, "", "nothing", 2437 null, null, null 2438 ); 2439 auto namespaces = new Tag( 2440 null, "", "namespaces", 2441 null, 2442 [ 2443 new Attribute("small", "A", Value(1)), 2444 new Attribute("med", "A", Value(2)), 2445 new Attribute("big", "A", Value(3)), 2446 new Attribute("small", "B", Value(10)), 2447 new Attribute("big", "B", Value(30)), 2448 ], 2449 null 2450 ); 2451 auto chiyo = new Tag( 2452 null, "", "chiyo", 2453 [ Value("Small"), Value("Flies?") ], 2454 [ 2455 new Attribute("nemesis", Value("Car")), 2456 new Attribute("score", Value(100)), 2457 ], 2458 null 2459 ); 2460 auto chiyo_ = new Tag( 2461 null, "", "chiyo_", 2462 [ Value("Small"), Value("Flies?") ], 2463 [ 2464 new Attribute("nemesis", Value("Car")), 2465 new Attribute("score", Value(100)), 2466 ], 2467 null 2468 ); 2469 auto yukari = new Tag( 2470 null, "", "yukari", 2471 null, null, null 2472 ); 2473 auto sana = new Tag( 2474 null, "visitor", "sana", 2475 null, null, null 2476 ); 2477 auto sana_ = new Tag( 2478 null, "visitor", "sana_", 2479 null, null, null 2480 ); 2481 auto sanaVisitor_ = new Tag( 2482 null, "visitor_", "sana_", 2483 null, null, null 2484 ); 2485 auto tomo = new Tag( 2486 null, "", "tomo", 2487 null, null, null 2488 ); 2489 auto hayama = new Tag( 2490 null, "visitor", "hayama", 2491 null, null, null 2492 ); 2493 auto people = new Tag( 2494 null, "", "people", 2495 null, 2496 [ 2497 new Attribute("visitor", "a", Value(1)), 2498 new Attribute("b", Value(2)), 2499 ], 2500 [chiyo, yukari, sana, tomo, hayama] 2501 ); 2502 2503 assert(blue3 .opEquals( blue3 )); 2504 assert(blue5 .opEquals( blue5 )); 2505 assert(orange .opEquals( orange )); 2506 assert(square .opEquals( square )); 2507 assert(triangle .opEquals( triangle )); 2508 assert(nothing .opEquals( nothing )); 2509 assert(namespaces .opEquals( namespaces )); 2510 assert(people .opEquals( people )); 2511 assert(chiyo .opEquals( chiyo )); 2512 assert(yukari .opEquals( yukari )); 2513 assert(sana .opEquals( sana )); 2514 assert(tomo .opEquals( tomo )); 2515 assert(hayama .opEquals( hayama )); 2516 2517 assert(!blue3.opEquals(orange)); 2518 assert(!blue3.opEquals(people)); 2519 assert(!blue3.opEquals(sana)); 2520 assert(!blue3.opEquals(blue5)); 2521 assert(!blue5.opEquals(blue3)); 2522 2523 alias Tag.NamespaceAccess NSA; 2524 static bool namespaceEquals(NSA n1, NSA n2) 2525 { 2526 return n1.name == n2.name; 2527 } 2528 2529 testRandomAccessRange(root.attributes, cast(Attribute[])[]); 2530 testRandomAccessRange(root.tags, [blue3, blue5, nothing, namespaces, people]); 2531 testRandomAccessRange(root.namespaces, [NSA(""), NSA("stuff")], &namespaceEquals); 2532 testRandomAccessRange(root.namespaces[0].tags, [blue3, blue5, nothing, namespaces, people]); 2533 testRandomAccessRange(root.namespaces[1].tags, [orange, square, triangle]); 2534 assert("" in root.namespaces); 2535 assert("stuff" in root.namespaces); 2536 assert("foobar" !in root.namespaces); 2537 testRandomAccessRange(root.namespaces[ ""].tags, [blue3, blue5, nothing, namespaces, people]); 2538 testRandomAccessRange(root.namespaces["stuff"].tags, [orange, square, triangle]); 2539 testRandomAccessRange(root.all.attributes, cast(Attribute[])[]); 2540 testRandomAccessRange(root.all.tags, [blue3, blue5, orange, square, triangle, nothing, namespaces, people]); 2541 testRandomAccessRange(root.all.tags[], [blue3, blue5, orange, square, triangle, nothing, namespaces, people]); 2542 testRandomAccessRange(root.all.tags[3..6], [square, triangle, nothing]); 2543 assert("blue" in root.tags); 2544 assert("nothing" in root.tags); 2545 assert("people" in root.tags); 2546 assert("orange" !in root.tags); 2547 assert("square" !in root.tags); 2548 assert("foobar" !in root.tags); 2549 assert("blue" in root.all.tags); 2550 assert("nothing" in root.all.tags); 2551 assert("people" in root.all.tags); 2552 assert("orange" in root.all.tags); 2553 assert("square" in root.all.tags); 2554 assert("foobar" !in root.all.tags); 2555 assert("orange" in root.namespaces["stuff"].tags); 2556 assert("square" in root.namespaces["stuff"].tags); 2557 assert("square" in root.namespaces["stuff"].tags); 2558 assert("foobar" !in root.attributes); 2559 assert("foobar" !in root.all.attributes); 2560 assert("foobar" !in root.namespaces["stuff"].attributes); 2561 assert("blue" !in root.attributes); 2562 assert("blue" !in root.all.attributes); 2563 assert("blue" !in root.namespaces["stuff"].attributes); 2564 testRandomAccessRange(root.tags["nothing"], [nothing]); 2565 testRandomAccessRange(root.tags["blue"], [blue3, blue5]); 2566 testRandomAccessRange(root.namespaces["stuff"].tags["orange"], [orange]); 2567 testRandomAccessRange(root.all.tags["nothing"], [nothing]); 2568 testRandomAccessRange(root.all.tags["blue"], [blue3, blue5]); 2569 testRandomAccessRange(root.all.tags["orange"], [orange]); 2570 2571 assertThrown!DOMRangeException(root.tags["foobar"]); 2572 assertThrown!DOMRangeException(root.all.tags["foobar"]); 2573 assertThrown!DOMRangeException(root.attributes["foobar"]); 2574 assertThrown!DOMRangeException(root.all.attributes["foobar"]); 2575 2576 // DMD Issue #12585 causes a segfault in these two tests when using 2.064 or 2.065, 2577 // so work around it. 2578 //assertThrown!DOMRangeException(root.namespaces["foobar"].tags["foobar"]); 2579 //assertThrown!DOMRangeException(root.namespaces["foobar"].attributes["foobar"]); 2580 bool didCatch = false; 2581 try 2582 auto x = root.namespaces["foobar"].tags["foobar"]; 2583 catch(DOMRangeException e) 2584 didCatch = true; 2585 assert(didCatch); 2586 2587 didCatch = false; 2588 try 2589 auto x = root.namespaces["foobar"].attributes["foobar"]; 2590 catch(DOMRangeException e) 2591 didCatch = true; 2592 assert(didCatch); 2593 2594 testRandomAccessRange(root.maybe.tags["nothing"], [nothing]); 2595 testRandomAccessRange(root.maybe.tags["blue"], [blue3, blue5]); 2596 testRandomAccessRange(root.maybe.namespaces["stuff"].tags["orange"], [orange]); 2597 testRandomAccessRange(root.maybe.all.tags["nothing"], [nothing]); 2598 testRandomAccessRange(root.maybe.all.tags["blue"], [blue3, blue5]); 2599 testRandomAccessRange(root.maybe.all.tags["blue"][], [blue3, blue5]); 2600 testRandomAccessRange(root.maybe.all.tags["blue"][0..1], [blue3]); 2601 testRandomAccessRange(root.maybe.all.tags["blue"][1..2], [blue5]); 2602 testRandomAccessRange(root.maybe.all.tags["orange"], [orange]); 2603 testRandomAccessRange(root.maybe.tags["foobar"], cast(Tag[])[]); 2604 testRandomAccessRange(root.maybe.all.tags["foobar"], cast(Tag[])[]); 2605 testRandomAccessRange(root.maybe.namespaces["foobar"].tags["foobar"], cast(Tag[])[]); 2606 testRandomAccessRange(root.maybe.attributes["foobar"], cast(Attribute[])[]); 2607 testRandomAccessRange(root.maybe.all.attributes["foobar"], cast(Attribute[])[]); 2608 testRandomAccessRange(root.maybe.namespaces["foobar"].attributes["foobar"], cast(Attribute[])[]); 2609 2610 testRandomAccessRange(blue3.attributes, [ new Attribute("isThree", Value(true)) ]); 2611 testRandomAccessRange(blue3.tags, cast(Tag[])[]); 2612 testRandomAccessRange(blue3.namespaces, [NSA("")], &namespaceEquals); 2613 testRandomAccessRange(blue3.all.attributes, [ new Attribute("isThree", Value(true)) ]); 2614 testRandomAccessRange(blue3.all.tags, cast(Tag[])[]); 2615 2616 testRandomAccessRange(blue5.attributes, [ new Attribute("isThree", Value(false)) ]); 2617 testRandomAccessRange(blue5.tags, cast(Tag[])[]); 2618 testRandomAccessRange(blue5.namespaces, [NSA("")], &namespaceEquals); 2619 testRandomAccessRange(blue5.all.attributes, [ new Attribute("isThree", Value(false)) ]); 2620 testRandomAccessRange(blue5.all.tags, cast(Tag[])[]); 2621 2622 testRandomAccessRange(orange.attributes, cast(Attribute[])[]); 2623 testRandomAccessRange(orange.tags, cast(Tag[])[]); 2624 testRandomAccessRange(orange.namespaces, cast(NSA[])[], &namespaceEquals); 2625 testRandomAccessRange(orange.all.attributes, cast(Attribute[])[]); 2626 testRandomAccessRange(orange.all.tags, cast(Tag[])[]); 2627 2628 testRandomAccessRange(square.attributes, [ 2629 new Attribute("points", Value(4)), 2630 new Attribute("dimensions", Value(2)), 2631 new Attribute("points", Value("Still four")), 2632 ]); 2633 testRandomAccessRange(square.tags, cast(Tag[])[]); 2634 testRandomAccessRange(square.namespaces, [NSA("")], &namespaceEquals); 2635 testRandomAccessRange(square.all.attributes, [ 2636 new Attribute("points", Value(4)), 2637 new Attribute("dimensions", Value(2)), 2638 new Attribute("points", Value("Still four")), 2639 ]); 2640 testRandomAccessRange(square.all.tags, cast(Tag[])[]); 2641 2642 testRandomAccessRange(triangle.attributes, cast(Attribute[])[]); 2643 testRandomAccessRange(triangle.tags, cast(Tag[])[]); 2644 testRandomAccessRange(triangle.namespaces, [NSA("data")], &namespaceEquals); 2645 testRandomAccessRange(triangle.namespaces[0].attributes, [ 2646 new Attribute("data", "points", Value(3)), 2647 new Attribute("data", "dimensions", Value(2)), 2648 ]); 2649 assert("data" in triangle.namespaces); 2650 assert("foobar" !in triangle.namespaces); 2651 testRandomAccessRange(triangle.namespaces["data"].attributes, [ 2652 new Attribute("data", "points", Value(3)), 2653 new Attribute("data", "dimensions", Value(2)), 2654 ]); 2655 testRandomAccessRange(triangle.all.attributes, [ 2656 new Attribute("data", "points", Value(3)), 2657 new Attribute("data", "dimensions", Value(2)), 2658 ]); 2659 testRandomAccessRange(triangle.all.tags, cast(Tag[])[]); 2660 2661 testRandomAccessRange(nothing.attributes, cast(Attribute[])[]); 2662 testRandomAccessRange(nothing.tags, cast(Tag[])[]); 2663 testRandomAccessRange(nothing.namespaces, cast(NSA[])[], &namespaceEquals); 2664 testRandomAccessRange(nothing.all.attributes, cast(Attribute[])[]); 2665 testRandomAccessRange(nothing.all.tags, cast(Tag[])[]); 2666 2667 testRandomAccessRange(namespaces.attributes, cast(Attribute[])[]); 2668 testRandomAccessRange(namespaces.tags, cast(Tag[])[]); 2669 testRandomAccessRange(namespaces.namespaces, [NSA("small"), NSA("med"), NSA("big")], &namespaceEquals); 2670 testRandomAccessRange(namespaces.namespaces[], [NSA("small"), NSA("med"), NSA("big")], &namespaceEquals); 2671 testRandomAccessRange(namespaces.namespaces[1..2], [NSA("med")], &namespaceEquals); 2672 testRandomAccessRange(namespaces.namespaces[0].attributes, [ 2673 new Attribute("small", "A", Value(1)), 2674 new Attribute("small", "B", Value(10)), 2675 ]); 2676 testRandomAccessRange(namespaces.namespaces[1].attributes, [ 2677 new Attribute("med", "A", Value(2)), 2678 ]); 2679 testRandomAccessRange(namespaces.namespaces[2].attributes, [ 2680 new Attribute("big", "A", Value(3)), 2681 new Attribute("big", "B", Value(30)), 2682 ]); 2683 testRandomAccessRange(namespaces.namespaces[1..2][0].attributes, [ 2684 new Attribute("med", "A", Value(2)), 2685 ]); 2686 assert("small" in namespaces.namespaces); 2687 assert("med" in namespaces.namespaces); 2688 assert("big" in namespaces.namespaces); 2689 assert("foobar" !in namespaces.namespaces); 2690 assert("small" !in namespaces.namespaces[1..2]); 2691 assert("med" in namespaces.namespaces[1..2]); 2692 assert("big" !in namespaces.namespaces[1..2]); 2693 assert("foobar" !in namespaces.namespaces[1..2]); 2694 testRandomAccessRange(namespaces.namespaces["small"].attributes, [ 2695 new Attribute("small", "A", Value(1)), 2696 new Attribute("small", "B", Value(10)), 2697 ]); 2698 testRandomAccessRange(namespaces.namespaces["med"].attributes, [ 2699 new Attribute("med", "A", Value(2)), 2700 ]); 2701 testRandomAccessRange(namespaces.namespaces["big"].attributes, [ 2702 new Attribute("big", "A", Value(3)), 2703 new Attribute("big", "B", Value(30)), 2704 ]); 2705 testRandomAccessRange(namespaces.all.attributes, [ 2706 new Attribute("small", "A", Value(1)), 2707 new Attribute("med", "A", Value(2)), 2708 new Attribute("big", "A", Value(3)), 2709 new Attribute("small", "B", Value(10)), 2710 new Attribute("big", "B", Value(30)), 2711 ]); 2712 testRandomAccessRange(namespaces.all.attributes[], [ 2713 new Attribute("small", "A", Value(1)), 2714 new Attribute("med", "A", Value(2)), 2715 new Attribute("big", "A", Value(3)), 2716 new Attribute("small", "B", Value(10)), 2717 new Attribute("big", "B", Value(30)), 2718 ]); 2719 testRandomAccessRange(namespaces.all.attributes[2..4], [ 2720 new Attribute("big", "A", Value(3)), 2721 new Attribute("small", "B", Value(10)), 2722 ]); 2723 testRandomAccessRange(namespaces.all.tags, cast(Tag[])[]); 2724 assert("A" !in namespaces.attributes); 2725 assert("B" !in namespaces.attributes); 2726 assert("foobar" !in namespaces.attributes); 2727 assert("A" in namespaces.all.attributes); 2728 assert("B" in namespaces.all.attributes); 2729 assert("foobar" !in namespaces.all.attributes); 2730 assert("A" in namespaces.namespaces["small"].attributes); 2731 assert("B" in namespaces.namespaces["small"].attributes); 2732 assert("foobar" !in namespaces.namespaces["small"].attributes); 2733 assert("A" in namespaces.namespaces["med"].attributes); 2734 assert("B" !in namespaces.namespaces["med"].attributes); 2735 assert("foobar" !in namespaces.namespaces["med"].attributes); 2736 assert("A" in namespaces.namespaces["big"].attributes); 2737 assert("B" in namespaces.namespaces["big"].attributes); 2738 assert("foobar" !in namespaces.namespaces["big"].attributes); 2739 assert("foobar" !in namespaces.tags); 2740 assert("foobar" !in namespaces.all.tags); 2741 assert("foobar" !in namespaces.namespaces["small"].tags); 2742 assert("A" !in namespaces.tags); 2743 assert("A" !in namespaces.all.tags); 2744 assert("A" !in namespaces.namespaces["small"].tags); 2745 testRandomAccessRange(namespaces.namespaces["small"].attributes["A"], [ 2746 new Attribute("small", "A", Value(1)), 2747 ]); 2748 testRandomAccessRange(namespaces.namespaces["med"].attributes["A"], [ 2749 new Attribute("med", "A", Value(2)), 2750 ]); 2751 testRandomAccessRange(namespaces.namespaces["big"].attributes["A"], [ 2752 new Attribute("big", "A", Value(3)), 2753 ]); 2754 testRandomAccessRange(namespaces.all.attributes["A"], [ 2755 new Attribute("small", "A", Value(1)), 2756 new Attribute("med", "A", Value(2)), 2757 new Attribute("big", "A", Value(3)), 2758 ]); 2759 testRandomAccessRange(namespaces.all.attributes["B"], [ 2760 new Attribute("small", "B", Value(10)), 2761 new Attribute("big", "B", Value(30)), 2762 ]); 2763 2764 testRandomAccessRange(chiyo.attributes, [ 2765 new Attribute("nemesis", Value("Car")), 2766 new Attribute("score", Value(100)), 2767 ]); 2768 testRandomAccessRange(chiyo.tags, cast(Tag[])[]); 2769 testRandomAccessRange(chiyo.namespaces, [NSA("")], &namespaceEquals); 2770 testRandomAccessRange(chiyo.all.attributes, [ 2771 new Attribute("nemesis", Value("Car")), 2772 new Attribute("score", Value(100)), 2773 ]); 2774 testRandomAccessRange(chiyo.all.tags, cast(Tag[])[]); 2775 2776 testRandomAccessRange(yukari.attributes, cast(Attribute[])[]); 2777 testRandomAccessRange(yukari.tags, cast(Tag[])[]); 2778 testRandomAccessRange(yukari.namespaces, cast(NSA[])[], &namespaceEquals); 2779 testRandomAccessRange(yukari.all.attributes, cast(Attribute[])[]); 2780 testRandomAccessRange(yukari.all.tags, cast(Tag[])[]); 2781 2782 testRandomAccessRange(sana.attributes, cast(Attribute[])[]); 2783 testRandomAccessRange(sana.tags, cast(Tag[])[]); 2784 testRandomAccessRange(sana.namespaces, cast(NSA[])[], &namespaceEquals); 2785 testRandomAccessRange(sana.all.attributes, cast(Attribute[])[]); 2786 testRandomAccessRange(sana.all.tags, cast(Tag[])[]); 2787 2788 testRandomAccessRange(people.attributes, [new Attribute("b", Value(2))]); 2789 testRandomAccessRange(people.tags, [chiyo, yukari, tomo]); 2790 testRandomAccessRange(people.namespaces, [NSA("visitor"), NSA("")], &namespaceEquals); 2791 testRandomAccessRange(people.namespaces[0].attributes, [new Attribute("visitor", "a", Value(1))]); 2792 testRandomAccessRange(people.namespaces[1].attributes, [new Attribute("b", Value(2))]); 2793 testRandomAccessRange(people.namespaces[0].tags, [sana, hayama]); 2794 testRandomAccessRange(people.namespaces[1].tags, [chiyo, yukari, tomo]); 2795 assert("visitor" in people.namespaces); 2796 assert("" in people.namespaces); 2797 assert("foobar" !in people.namespaces); 2798 testRandomAccessRange(people.namespaces["visitor"].attributes, [new Attribute("visitor", "a", Value(1))]); 2799 testRandomAccessRange(people.namespaces[ ""].attributes, [new Attribute("b", Value(2))]); 2800 testRandomAccessRange(people.namespaces["visitor"].tags, [sana, hayama]); 2801 testRandomAccessRange(people.namespaces[ ""].tags, [chiyo, yukari, tomo]); 2802 testRandomAccessRange(people.all.attributes, [ 2803 new Attribute("visitor", "a", Value(1)), 2804 new Attribute("b", Value(2)), 2805 ]); 2806 testRandomAccessRange(people.all.tags, [chiyo, yukari, sana, tomo, hayama]); 2807 2808 people.attributes["b"][0].name = "b_"; 2809 people.namespaces["visitor"].attributes["a"][0].name = "a_"; 2810 people.tags["chiyo"][0].name = "chiyo_"; 2811 people.namespaces["visitor"].tags["sana"][0].name = "sana_"; 2812 2813 assert("b_" in people.attributes); 2814 assert("a_" in people.namespaces["visitor"].attributes); 2815 assert("chiyo_" in people.tags); 2816 assert("sana_" in people.namespaces["visitor"].tags); 2817 2818 assert(people.attributes["b_"][0] == new Attribute("b_", Value(2))); 2819 assert(people.namespaces["visitor"].attributes["a_"][0] == new Attribute("visitor", "a_", Value(1))); 2820 assert(people.tags["chiyo_"][0] == chiyo_); 2821 assert(people.namespaces["visitor"].tags["sana_"][0] == sana_); 2822 2823 assert("b" !in people.attributes); 2824 assert("a" !in people.namespaces["visitor"].attributes); 2825 assert("chiyo" !in people.tags); 2826 assert("sana" !in people.namespaces["visitor"].tags); 2827 2828 assert(people.maybe.attributes["b"].length == 0); 2829 assert(people.maybe.namespaces["visitor"].attributes["a"].length == 0); 2830 assert(people.maybe.tags["chiyo"].length == 0); 2831 assert(people.maybe.namespaces["visitor"].tags["sana"].length == 0); 2832 2833 people.tags["tomo"][0].remove(); 2834 people.namespaces["visitor"].tags["hayama"][0].remove(); 2835 people.tags["chiyo_"][0].remove(); 2836 testRandomAccessRange(people.tags, [yukari]); 2837 testRandomAccessRange(people.namespaces, [NSA("visitor"), NSA("")], &namespaceEquals); 2838 testRandomAccessRange(people.namespaces[0].tags, [sana_]); 2839 testRandomAccessRange(people.namespaces[1].tags, [yukari]); 2840 assert("visitor" in people.namespaces); 2841 assert("" in people.namespaces); 2842 assert("foobar" !in people.namespaces); 2843 testRandomAccessRange(people.namespaces["visitor"].tags, [sana_]); 2844 testRandomAccessRange(people.namespaces[ ""].tags, [yukari]); 2845 testRandomAccessRange(people.all.tags, [yukari, sana_]); 2846 2847 people.attributes["b_"][0].namespace = "_"; 2848 people.namespaces["visitor"].attributes["a_"][0].namespace = "visitor_"; 2849 assert("_" in people.namespaces); 2850 assert("visitor_" in people.namespaces); 2851 assert("" in people.namespaces); 2852 assert("visitor" in people.namespaces); 2853 people.namespaces["visitor"].tags["sana_"][0].namespace = "visitor_"; 2854 assert("_" in people.namespaces); 2855 assert("visitor_" in people.namespaces); 2856 assert("" in people.namespaces); 2857 assert("visitor" !in people.namespaces); 2858 2859 assert(people.namespaces["_" ].attributes["b_"][0] == new Attribute("_", "b_", Value(2))); 2860 assert(people.namespaces["visitor_"].attributes["a_"][0] == new Attribute("visitor_", "a_", Value(1))); 2861 assert(people.namespaces["visitor_"].tags["sana_"][0] == sanaVisitor_); 2862 2863 people.tags["yukari"][0].remove(); 2864 people.namespaces["visitor_"].tags["sana_"][0].remove(); 2865 people.namespaces["visitor_"].attributes["a_"][0].namespace = "visitor"; 2866 people.namespaces["_"].attributes["b_"][0].namespace = ""; 2867 testRandomAccessRange(people.tags, cast(Tag[])[]); 2868 testRandomAccessRange(people.namespaces, [NSA("visitor"), NSA("")], &namespaceEquals); 2869 testRandomAccessRange(people.namespaces[0].tags, cast(Tag[])[]); 2870 testRandomAccessRange(people.namespaces[1].tags, cast(Tag[])[]); 2871 assert("visitor" in people.namespaces); 2872 assert("" in people.namespaces); 2873 assert("foobar" !in people.namespaces); 2874 testRandomAccessRange(people.namespaces["visitor"].tags, cast(Tag[])[]); 2875 testRandomAccessRange(people.namespaces[ ""].tags, cast(Tag[])[]); 2876 testRandomAccessRange(people.all.tags, cast(Tag[])[]); 2877 2878 people.namespaces["visitor"].attributes["a_"][0].remove(); 2879 testRandomAccessRange(people.attributes, [new Attribute("b_", Value(2))]); 2880 testRandomAccessRange(people.namespaces, [NSA("")], &namespaceEquals); 2881 testRandomAccessRange(people.namespaces[0].attributes, [new Attribute("b_", Value(2))]); 2882 assert("visitor" !in people.namespaces); 2883 assert("" in people.namespaces); 2884 assert("foobar" !in people.namespaces); 2885 testRandomAccessRange(people.namespaces[""].attributes, [new Attribute("b_", Value(2))]); 2886 testRandomAccessRange(people.all.attributes, [ 2887 new Attribute("b_", Value(2)), 2888 ]); 2889 2890 people.attributes["b_"][0].remove(); 2891 testRandomAccessRange(people.attributes, cast(Attribute[])[]); 2892 testRandomAccessRange(people.namespaces, cast(NSA[])[], &namespaceEquals); 2893 assert("visitor" !in people.namespaces); 2894 assert("" !in people.namespaces); 2895 assert("foobar" !in people.namespaces); 2896 testRandomAccessRange(people.all.attributes, cast(Attribute[])[]); 2897 2898 // Test clone() 2899 auto rootClone = root.clone(); 2900 assert(rootClone !is root); 2901 assert(rootClone.parent is null); 2902 assert(rootClone.name == root.name); 2903 assert(rootClone.namespace == root.namespace); 2904 assert(rootClone.location == root.location); 2905 assert(rootClone.values == root.values); 2906 assert(rootClone.toSDLDocument() == root.toSDLDocument()); 2907 2908 auto peopleClone = people.clone(); 2909 assert(peopleClone !is people); 2910 assert(peopleClone.parent is null); 2911 assert(peopleClone.name == people.name); 2912 assert(peopleClone.namespace == people.namespace); 2913 assert(peopleClone.location == people.location); 2914 assert(peopleClone.values == people.values); 2915 assert(peopleClone.toSDLString() == people.toSDLString()); 2916 } 2917 2918 // Regression test, issue #11: https://github.com/Abscissa/SDLang-D/issues/11 2919 @("*: Regression test issue #11") 2920 unittest 2921 { 2922 import sdlang.parser; 2923 2924 auto root = parseSource( 2925 `// 2926 a`); 2927 2928 assert("a" in root.tags); 2929 2930 root = parseSource( 2931 `// 2932 parent { 2933 child 2934 } 2935 `); 2936 2937 auto child = new Tag( 2938 null, "", "child", 2939 null, null, null 2940 ); 2941 2942 assert("parent" in root.tags); 2943 assert("child" !in root.tags); 2944 testRandomAccessRange(root.tags["parent"][0].tags, [child]); 2945 assert("child" in root.tags["parent"][0].tags); 2946 }