1 2 // Copyright Ferdinand Majerech 2011. 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE_1_0.txt or copy at 5 // http://www.boost.org/LICENSE_1_0.txt) 6 7 /// Node of a YAML document. Used to read YAML data once it's loaded, 8 /// and to prepare data to emit. 9 module dyaml.node; 10 11 12 import std.algorithm; 13 import std.array; 14 import std.conv; 15 import std.datetime; 16 import std.exception; 17 import std.math; 18 import std.range; 19 import std.stdio; 20 import std.string; 21 import std.traits; 22 import std.typecons; 23 import std.variant; 24 25 import dyaml.event; 26 import dyaml.exception; 27 import dyaml.style; 28 import dyaml.tag; 29 30 31 /// Exception thrown at node related errors. 32 class NodeException : YAMLException 33 { 34 package: 35 // Construct a NodeException. 36 // 37 // Params: msg = Error message. 38 // start = Start position of the node. 39 this(string msg, Mark start, string file = __FILE__, int line = __LINE__) 40 @safe 41 { 42 super(msg ~ "\nNode at: " ~ start.toString(), file, line); 43 } 44 } 45 46 private alias NodeException Error; 47 48 // Node kinds. 49 package enum NodeID : ubyte 50 { 51 Scalar, 52 Sequence, 53 Mapping 54 } 55 56 /// Null YAML type. Used in nodes with _null values. 57 struct YAMLNull 58 { 59 /// Used for string conversion. 60 string toString() const pure @safe nothrow {return "null";} 61 } 62 63 // Merge YAML type, used to support "tag:yaml.org,2002:merge". 64 package struct YAMLMerge{} 65 66 // Base class for YAMLContainer - used for user defined YAML types. 67 package abstract class YAMLObject 68 { 69 public: 70 // Get type of the stored value. 71 @property TypeInfo type() const pure @safe nothrow {assert(false);} 72 73 protected: 74 // Compare with another YAMLObject. 75 int cmp(const YAMLObject rhs) const @system {assert(false);}; 76 } 77 78 // Stores a user defined YAML data type. 79 package class YAMLContainer(T) if (!Node.allowed!T): YAMLObject 80 { 81 private: 82 // Stored value. 83 T value_; 84 85 public: 86 // Get type of the stored value. 87 @property override TypeInfo type() const pure @safe nothrow {return typeid(T);} 88 89 // Get string representation of the container. 90 override string toString() @system 91 { 92 static if(!hasMember!(T, "toString")) 93 { 94 return super.toString(); 95 } 96 else 97 { 98 return format("YAMLContainer(%s)", value_.toString()); 99 } 100 } 101 102 protected: 103 // Compare with another YAMLObject. 104 override int cmp(const YAMLObject rhs) const @system 105 { 106 const typeCmp = type.opCmp(rhs.type); 107 if(typeCmp != 0){return typeCmp;} 108 109 // Const-casting here as Object opCmp is not const. 110 T* v1 = cast(T*)&value_; 111 T* v2 = cast(T*)&((cast(YAMLContainer)rhs).value_); 112 return (*v1).opCmp(*v2); 113 } 114 115 private: 116 // Construct a YAMLContainer holding specified value. 117 this(T value) @trusted {value_ = value;} 118 } 119 120 121 // Key-value pair of YAML nodes, used in mappings. 122 private struct Pair 123 { 124 public: 125 /// Key node. 126 Node key; 127 /// Value node. 128 Node value; 129 130 public: 131 /// Construct a Pair from two values. Will be converted to Nodes if needed. 132 this(K, V)(K key, V value) @safe 133 { 134 static if(is(Unqual!K == Node)){this.key = key;} 135 else {this.key = Node(key);} 136 static if(is(Unqual!V == Node)){this.value = value;} 137 else {this.value = Node(value);} 138 } 139 140 /// Equality test with another Pair. 141 bool opEquals(const ref Pair rhs) const @safe 142 { 143 return cmp!(Yes.useTag)(rhs) == 0; 144 } 145 146 /// Assignment (shallow copy) by value. 147 void opAssign(Pair rhs) @safe nothrow 148 { 149 opAssign(rhs); 150 } 151 152 /// Assignment (shallow copy) by reference. 153 void opAssign(ref Pair rhs) @safe nothrow 154 { 155 key = rhs.key; 156 value = rhs.value; 157 } 158 159 private: 160 // Comparison with another Pair. 161 // 162 // useTag determines whether or not we consider node tags 163 // in the comparison. 164 int cmp(Flag!"useTag" useTag)(ref const(Pair) rhs) const @safe 165 { 166 const keyCmp = key.cmp!useTag(rhs.key); 167 return keyCmp != 0 ? keyCmp 168 : value.cmp!useTag(rhs.value); 169 } 170 171 // @disable causes a linker error with DMD 2.054, so we temporarily use 172 // a private opCmp. Apparently this must also match the attributes of 173 // the Node's opCmp to avoid a linker error. 174 @disable int opCmp(ref Pair); 175 int opCmp(ref const(Pair) pair) const @safe 176 { 177 assert(false, "This should never be called"); 178 } 179 } 180 181 /** YAML node. 182 * 183 * This is a pseudo-dynamic type that can store any YAML value, including a 184 * sequence or mapping of nodes. You can get data from a Node directly or 185 * iterate over it if it's a collection. 186 */ 187 struct Node 188 { 189 public: 190 alias Pair = .Pair; 191 192 package: 193 // YAML value type. 194 alias Algebraic!(YAMLNull, YAMLMerge, bool, long, real, ubyte[], SysTime, string, 195 Node.Pair[], Node[], YAMLObject) Value; 196 197 // Can Value hold this type without wrapping it in a YAMLObject? 198 template allowed(T) 199 { 200 enum allowed = isIntegral!T || 201 isFloatingPoint!T || 202 isSomeString!T || 203 is(Unqual!T == bool) || 204 Value.allowed!T; 205 } 206 207 private: 208 // Stored value. 209 Value value_; 210 // Start position of the node. 211 Mark startMark_; 212 213 package: 214 // Tag of the node. 215 Tag tag_; 216 // Node scalar style. Used to remember style this node was loaded with. 217 ScalarStyle scalarStyle = ScalarStyle.Invalid; 218 // Node collection style. Used to remember style this node was loaded with. 219 CollectionStyle collectionStyle = CollectionStyle.Invalid; 220 221 static assert(Value.sizeof <= 24, "Unexpected YAML value size"); 222 static assert(Node.sizeof <= 48, "Unexpected YAML node size"); 223 224 // If scalarCtorNothrow!T is true, scalar node ctor from T can be nothrow. 225 // 226 // TODO 227 // Eventually we should simplify this and make all Node constructors except from 228 // user values nothrow (and think even about those user values). 2014-08-28 229 enum scalarCtorNothrow(T) = 230 (is(Unqual!T == string) || isIntegral!T || isFloatingPoint!T) || is(Unqual!T == bool) || 231 (Value.allowed!T && (!is(Unqual!T == Value) && !isSomeString!T && !isArray!T && !isAssociativeArray!T)); 232 public: 233 /** Construct a Node from a value. 234 * 235 * Any type except for Node can be stored in a Node, but default YAML 236 * types (integers, floats, strings, timestamps, etc.) will be stored 237 * more efficiently. To create a node representing a null value, 238 * construct it from YAMLNull. 239 * 240 * 241 * Note that to emit any non-default types you store 242 * in a node, you need a Representer to represent them in YAML - 243 * otherwise emitting will fail. 244 * 245 * Params: value = Value to store in the node. 246 * tag = Overrides tag of the node when emitted, regardless 247 * of tag determined by Representer. Representer uses 248 * this to determine YAML data type when a D data type 249 * maps to multiple different YAML data types. Tag must 250 * be in full form, e.g. "tag:yaml.org,2002:int", not 251 * a shortcut, like "!!int". 252 */ 253 this(T)(T value, const string tag = null) @trusted 254 if(!scalarCtorNothrow!T && (!isArray!T && !isAssociativeArray!T)) 255 { 256 tag_ = Tag(tag); 257 258 // No copyconstruction. 259 static assert(!is(Unqual!T == Node)); 260 261 enum unexpectedType = "Unexpected type in the non-nothrow YAML node constructor"; 262 static if(isSomeString!T) { value_ = Value(value.to!string); } 263 else static if(is(Unqual!T == Value)) { value_ = Value(value); } 264 else static if(Value.allowed!T) { static assert(false, unexpectedType); } 265 // User defined type. 266 else { value_ = userValue(value); } 267 } 268 /// Ditto. 269 // Overload for types where we can make this nothrow. 270 this(T)(T value, const string tag = null) @trusted pure nothrow 271 if(scalarCtorNothrow!T) 272 { 273 tag_ = Tag(tag); 274 // We can easily store ints, floats, strings. 275 static if(isIntegral!T) { value_ = Value(cast(long)value); } 276 else static if(is(Unqual!T==bool)){ value_ = Value(cast(bool)value); } 277 else static if(isFloatingPoint!T) { value_ = Value(cast(real)value); } 278 // User defined type or plain string. 279 else { value_ = Value(value);} 280 } 281 unittest 282 { 283 { 284 auto node = Node(42); 285 assert(node.isScalar && !node.isSequence && 286 !node.isMapping && !node.isUserType); 287 assert(node.as!int == 42 && node.as!float == 42.0f && node.as!string == "42"); 288 assert(!node.isUserType); 289 } 290 291 { 292 auto node = Node(new class{int a = 5;}); 293 assert(node.isUserType); 294 } 295 { 296 auto node = Node("string"); 297 assert(node.as!string == "string"); 298 } 299 } 300 301 /** Construct a node from an _array. 302 * 303 * If _array is an _array of nodes or pairs, it is stored directly. 304 * Otherwise, every value in the array is converted to a node, and 305 * those nodes are stored. 306 * 307 * Params: array = Values to store in the node. 308 * tag = Overrides tag of the node when emitted, regardless 309 * of tag determined by Representer. Representer uses 310 * this to determine YAML data type when a D data type 311 * maps to multiple different YAML data types. 312 * This is used to differentiate between YAML sequences 313 * ("!!seq") and sets ("!!set"), which both are 314 * internally represented as an array_ of nodes. Tag 315 * must be in full form, e.g. "tag:yaml.org,2002:set", 316 * not a shortcut, like "!!set". 317 * 318 * Examples: 319 * -------------------- 320 * // Will be emitted as a sequence (default for arrays) 321 * auto seq = Node([1, 2, 3, 4, 5]); 322 * // Will be emitted as a set (overriden tag) 323 * auto set = Node([1, 2, 3, 4, 5], "tag:yaml.org,2002:set"); 324 * -------------------- 325 */ 326 this(T)(T[] array, const string tag = null) @trusted 327 if (!isSomeString!(T[])) 328 { 329 tag_ = Tag(tag); 330 331 // Construction from raw node or pair array. 332 static if(is(Unqual!T == Node) || is(Unqual!T == Node.Pair)) 333 { 334 value_ = Value(array); 335 } 336 // Need to handle byte buffers separately. 337 else static if(is(Unqual!T == byte) || is(Unqual!T == ubyte)) 338 { 339 value_ = Value(cast(ubyte[]) array); 340 } 341 else 342 { 343 Node[] nodes; 344 foreach(ref value; array){nodes ~= Node(value);} 345 value_ = Value(nodes); 346 } 347 } 348 unittest 349 { 350 with(Node([1, 2, 3])) 351 { 352 assert(!isScalar() && isSequence && !isMapping && !isUserType); 353 assert(length == 3); 354 assert(opIndex(2).as!int == 3); 355 } 356 357 // Will be emitted as a sequence (default for arrays) 358 auto seq = Node([1, 2, 3, 4, 5]); 359 // Will be emitted as a set (overriden tag) 360 auto set = Node([1, 2, 3, 4, 5], "tag:yaml.org,2002:set"); 361 } 362 363 /** Construct a node from an associative _array. 364 * 365 * If keys and/or values of _array are nodes, they stored directly. 366 * Otherwise they are converted to nodes and then stored. 367 * 368 * Params: array = Values to store in the node. 369 * tag = Overrides tag of the node when emitted, regardless 370 * of tag determined by Representer. Representer uses 371 * this to determine YAML data type when a D data type 372 * maps to multiple different YAML data types. 373 * This is used to differentiate between YAML unordered 374 * mappings ("!!map"), ordered mappings ("!!omap"), and 375 * pairs ("!!pairs") which are all internally 376 * represented as an _array of node pairs. Tag must be 377 * in full form, e.g. "tag:yaml.org,2002:omap", not a 378 * shortcut, like "!!omap". 379 * 380 * Examples: 381 * -------------------- 382 * // Will be emitted as an unordered mapping (default for mappings) 383 * auto map = Node([1 : "a", 2 : "b"]); 384 * // Will be emitted as an ordered map (overriden tag) 385 * auto omap = Node([1 : "a", 2 : "b"], "tag:yaml.org,2002:omap"); 386 * // Will be emitted as pairs (overriden tag) 387 * auto pairs = Node([1 : "a", 2 : "b"], "tag:yaml.org,2002:pairs"); 388 * -------------------- 389 */ 390 this(K, V)(V[K] array, const string tag = null) @trusted 391 { 392 tag_ = Tag(tag); 393 394 Node.Pair[] pairs; 395 foreach(key, ref value; array){pairs ~= Pair(key, value);} 396 value_ = Value(pairs); 397 } 398 unittest 399 { 400 int[string] aa; 401 aa["1"] = 1; 402 aa["2"] = 2; 403 with(Node(aa)) 404 { 405 assert(!isScalar() && !isSequence && isMapping && !isUserType); 406 assert(length == 2); 407 assert(opIndex("2").as!int == 2); 408 } 409 410 // Will be emitted as an unordered mapping (default for mappings) 411 auto map = Node([1 : "a", 2 : "b"]); 412 // Will be emitted as an ordered map (overriden tag) 413 auto omap = Node([1 : "a", 2 : "b"], "tag:yaml.org,2002:omap"); 414 // Will be emitted as pairs (overriden tag) 415 auto pairs = Node([1 : "a", 2 : "b"], "tag:yaml.org,2002:pairs"); 416 } 417 418 /** Construct a node from arrays of _keys and _values. 419 * 420 * Constructs a mapping node with key-value pairs from 421 * _keys and _values, keeping their order. Useful when order 422 * is important (ordered maps, pairs). 423 * 424 * 425 * keys and values must have equal length. 426 * 427 * 428 * If _keys and/or _values are nodes, they are stored directly/ 429 * Otherwise they are converted to nodes and then stored. 430 * 431 * Params: keys = Keys of the mapping, from first to last pair. 432 * values = Values of the mapping, from first to last pair. 433 * tag = Overrides tag of the node when emitted, regardless 434 * of tag determined by Representer. Representer uses 435 * this to determine YAML data type when a D data type 436 * maps to multiple different YAML data types. 437 * This is used to differentiate between YAML unordered 438 * mappings ("!!map"), ordered mappings ("!!omap"), and 439 * pairs ("!!pairs") which are all internally 440 * represented as an array of node pairs. Tag must be 441 * in full form, e.g. "tag:yaml.org,2002:omap", not a 442 * shortcut, like "!!omap". 443 * 444 * Examples: 445 * -------------------- 446 * // Will be emitted as an unordered mapping (default for mappings) 447 * auto map = Node([1, 2], ["a", "b"]); 448 * // Will be emitted as an ordered map (overriden tag) 449 * auto omap = Node([1, 2], ["a", "b"], "tag:yaml.org,2002:omap"); 450 * // Will be emitted as pairs (overriden tag) 451 * auto pairs = Node([1, 2], ["a", "b"], "tag:yaml.org,2002:pairs"); 452 * -------------------- 453 */ 454 this(K, V)(K[] keys, V[] values, const string tag = null) @trusted 455 if(!(isSomeString!(K[]) || isSomeString!(V[]))) 456 in 457 { 458 assert(keys.length == values.length, 459 "Lengths of keys and values arrays to construct " ~ 460 "a YAML node from don't match"); 461 } 462 body 463 { 464 tag_ = Tag(tag); 465 466 Node.Pair[] pairs; 467 foreach(i; 0 .. keys.length){pairs ~= Pair(keys[i], values[i]);} 468 value_ = Value(pairs); 469 } 470 unittest 471 { 472 with(Node(["1", "2"], [1, 2])) 473 { 474 assert(!isScalar() && !isSequence && isMapping && !isUserType); 475 assert(length == 2); 476 assert(opIndex("2").as!int == 2); 477 } 478 479 // Will be emitted as an unordered mapping (default for mappings) 480 auto map = Node([1, 2], ["a", "b"]); 481 // Will be emitted as an ordered map (overriden tag) 482 auto omap = Node([1, 2], ["a", "b"], "tag:yaml.org,2002:omap"); 483 // Will be emitted as pairs (overriden tag) 484 auto pairs = Node([1, 2], ["a", "b"], "tag:yaml.org,2002:pairs"); 485 } 486 487 /// Is this node valid (initialized)? 488 @property bool isValid() const @safe pure nothrow 489 { 490 return value_.hasValue; 491 } 492 493 /// Is this node a scalar value? 494 @property bool isScalar() const @safe nothrow 495 { 496 return !(isMapping || isSequence); 497 } 498 499 /// Is this node a sequence? 500 @property bool isSequence() const @safe nothrow 501 { 502 return isType!(Node[]); 503 } 504 505 /// Is this node a mapping? 506 @property bool isMapping() const @safe nothrow 507 { 508 return isType!(Pair[]); 509 } 510 511 /// Is this node a user defined type? 512 @property bool isUserType() const @safe nothrow 513 { 514 return isType!YAMLObject; 515 } 516 517 /// Is this node null? 518 @property bool isNull() const @safe nothrow 519 { 520 return isType!YAMLNull; 521 } 522 523 /// Return tag of the node. 524 @property string tag() const @safe nothrow {return tag_.get;} 525 526 /** Equality test. 527 * 528 * If T is Node, recursively compares all subnodes. 529 * This might be quite expensive if testing entire documents. 530 * 531 * If T is not Node, gets a value of type T from the node and tests 532 * equality with that. 533 * 534 * To test equality with a null YAML value, use YAMLNull. 535 * 536 * Params: rhs = Variable to test equality with. 537 * 538 * Returns: true if equal, false otherwise. 539 */ 540 bool opEquals(T)(const auto ref T rhs) const @safe 541 { 542 return equals!(Yes.useTag)(rhs); 543 } 544 /// 545 unittest 546 { 547 auto node = Node(42); 548 549 assert(node == 42); 550 assert(node != "42"); 551 assert(node != "43"); 552 553 auto node2 = Node(YAMLNull()); 554 assert(node2 == YAMLNull()); 555 } 556 557 /// Shortcut for get(). 558 alias get as; 559 560 /** Get the value of the node as specified type. 561 * 562 * If the specifed type does not match type in the node, 563 * conversion is attempted. The stringConversion template 564 * parameter can be used to disable conversion from non-string 565 * types to strings. 566 * 567 * Numeric values are range checked, throwing if out of range of 568 * requested type. 569 * 570 * Timestamps are stored as std.datetime.SysTime. 571 * Binary values are decoded and stored as ubyte[]. 572 * 573 * To get a null value, use get!YAMLNull . This is to 574 * prevent getting null values for types such as strings or classes. 575 * 576 * $(BR)$(B Mapping default values:) 577 * 578 * $(PBR 579 * The '=' key can be used to denote the default value of a mapping. 580 * This can be used when a node is scalar in early versions of a program, 581 * but is replaced by a mapping later. Even if the node is a mapping, the 582 * get method can be used as if it was a scalar if it has a default value. 583 * This way, new YAML files where the node is a mapping can still be read 584 * by old versions of the program, which expect the node to be a scalar. 585 * ) 586 * 587 * Examples: 588 * 589 * Automatic type conversion: 590 * -------------------- 591 * auto node = Node(42); 592 * 593 * assert(node.as!int == 42); 594 * assert(node.as!string == "42"); 595 * assert(node.as!double == 42.0); 596 * -------------------- 597 * 598 * Returns: Value of the node as specified type. 599 * 600 * Throws: NodeException if unable to convert to specified type, or if 601 * the value is out of range of requested type. 602 */ 603 @property T get(T, Flag!"stringConversion" stringConversion = Yes.stringConversion)() 604 @trusted if(!is(T == const)) 605 { 606 if(isType!T){return value_.get!T;} 607 608 /// Must go before others, as even string/int/etc could be stored in a YAMLObject. 609 static if(!allowed!T) if(isUserType) 610 { 611 auto object = as!YAMLObject; 612 if(object.type is typeid(T)) 613 { 614 return (cast(YAMLContainer!T)object).value_; 615 } 616 throw new Error("Node stores unexpected type: " ~ object.type.toString() ~ 617 ". Expected: " ~ typeid(T).toString, startMark_); 618 } 619 620 // If we're getting from a mapping and we're not getting Node.Pair[], 621 // we're getting the default value. 622 if(isMapping){return this["="].as!(T, stringConversion);} 623 624 static if(isSomeString!T) 625 { 626 static if(!stringConversion) 627 { 628 if(isString){return to!T(value_.get!string);} 629 throw new Error("Node stores unexpected type: " ~ type.toString() ~ 630 ". Expected: " ~ typeid(T).toString, startMark_); 631 } 632 else 633 { 634 // Try to convert to string. 635 try 636 { 637 return value_.coerce!T(); 638 } 639 catch(VariantException e) 640 { 641 throw new Error("Unable to convert node value to string", startMark_); 642 } 643 } 644 } 645 else 646 { 647 static if(isFloatingPoint!T) 648 { 649 /// Can convert int to float. 650 if(isInt()) {return to!T(value_.get!(const long));} 651 else if(isFloat()){return to!T(value_.get!(const real));} 652 } 653 else static if(is(Unqual!T == bool)) 654 { 655 const temp = value_.get!(const bool); 656 return to!bool(temp); 657 } 658 else static if(isIntegral!T) 659 { 660 if(isInt()) 661 { 662 const temp = value_.get!(const long); 663 enforce(temp >= T.min && temp <= T.max, 664 new Error("Integer value of type " ~ typeid(T).toString() ~ 665 " out of range. Value: " ~ to!string(temp), startMark_)); 666 return to!T(temp); 667 } 668 throw new Error("Node stores unexpected type: " ~ type.toString() ~ 669 ". Expected: " ~ typeid(T).toString(), startMark_); 670 } 671 else throw new Error("Node stores unexpected type: " ~ type.toString() ~ 672 ". Expected: " ~ typeid(T).toString(), startMark_); 673 } 674 assert(false, "This code should never be reached"); 675 } 676 unittest 677 { 678 assertThrown!NodeException(Node("42").get!int); 679 Node(YAMLNull()).get!YAMLNull; 680 } 681 682 /// Ditto. 683 @property T get(T, Flag!"stringConversion" stringConversion = Yes.stringConversion)() const 684 @trusted if(is(T == const)) 685 { 686 if(isType!(Unqual!T)){return value_.get!T;} 687 688 /// Must go before others, as even string/int/etc could be stored in a YAMLObject. 689 static if(!allowed!(Unqual!T)) if(isUserType) 690 { 691 auto object = as!(const YAMLObject); 692 if(object.type is typeid(T)) 693 { 694 return (cast(const YAMLContainer!(Unqual!T))object).value_; 695 } 696 throw new Error("Node has unexpected type: " ~ object.type.toString() ~ 697 ". Expected: " ~ typeid(T).toString, startMark_); 698 } 699 700 // If we're getting from a mapping and we're not getting Node.Pair[], 701 // we're getting the default value. 702 if(isMapping){return indexConst("=").as!( T, stringConversion);} 703 704 static if(isSomeString!T) 705 { 706 static if(!stringConversion) 707 { 708 if(isString){return to!T(value_.get!(const string));} 709 throw new Error("Node stores unexpected type: " ~ type.toString() ~ 710 ". Expected: " ~ typeid(T).toString(), startMark_); 711 } 712 else 713 { 714 // Try to convert to string. 715 try 716 { 717 // NOTE: We are casting away const here 718 return (cast(Value)value_).coerce!T(); 719 } 720 catch(VariantException e) 721 { 722 throw new Error("Unable to convert node value to string", startMark_); 723 } 724 } 725 } 726 else 727 { 728 static if(isFloatingPoint!T) 729 { 730 /// Can convert int to float. 731 if(isInt()) {return to!T(value_.get!(const long));} 732 else if(isFloat()){return to!T(value_.get!(const real));} 733 } 734 else static if(is(Unqual!T == bool)) 735 { 736 const temp = value_.get!(const bool); 737 return to!bool(temp); 738 } 739 else static if(isIntegral!T) if(isInt()) 740 { 741 const temp = value_.get!(const long); 742 enforce(temp >= T.min && temp <= T.max, 743 new Error("Integer value of type " ~ typeid(T).toString() ~ 744 " out of range. Value: " ~ to!string(temp), startMark_)); 745 return to!T(temp); 746 } 747 throw new Error("Node stores unexpected type: " ~ type.toString() ~ 748 ". Expected: " ~ typeid(T).toString, startMark_); 749 } 750 } 751 752 /** If this is a collection, return its _length. 753 * 754 * Otherwise, throw NodeException. 755 * 756 * Returns: Number of elements in a sequence or key-value pairs in a mapping. 757 * 758 * Throws: NodeException if this is not a sequence nor a mapping. 759 */ 760 @property size_t length() const @trusted 761 { 762 if(isSequence) {return value_.get!(const Node[]).length;} 763 else if(isMapping){return value_.get!(const Pair[]).length;} 764 throw new Error("Trying to get length of a " ~ nodeTypeString ~ " node", 765 startMark_); 766 } 767 768 /** Get the element at specified index. 769 * 770 * If the node is a sequence, index must be integral. 771 * 772 * 773 * If the node is a mapping, return the value corresponding to the first 774 * key equal to index. containsKey() can be used to determine if a mapping 775 * has a specific key. 776 * 777 * To get element at a null index, use YAMLNull for index. 778 * 779 * Params: index = Index to use. 780 * 781 * Returns: Value corresponding to the index. 782 * 783 * Throws: NodeException if the index could not be found, 784 * non-integral index is used with a sequence or the node is 785 * not a collection. 786 */ 787 ref Node opIndex(T)(T index) @trusted 788 { 789 if(isSequence) 790 { 791 checkSequenceIndex(index); 792 static if(isIntegral!T || is(Unqual!T == bool)) 793 { 794 return cast(Node)value_.get!(Node[])[index]; 795 } 796 assert(false); 797 } 798 else if(isMapping) 799 { 800 auto idx = findPair(index); 801 if(idx >= 0) 802 { 803 return cast(Node)value_.get!(Pair[])[idx].value; 804 } 805 806 string msg = "Mapping index not found" ~ (isSomeString!T ? ": " ~ to!string(index) : ""); 807 throw new Error(msg, startMark_); 808 } 809 throw new Error("Trying to index a " ~ nodeTypeString ~ " node", startMark_); 810 } 811 /// 812 unittest 813 { 814 writeln("D:YAML Node opIndex unittest"); 815 alias Node.Value Value; 816 alias Node.Pair Pair; 817 818 Node narray = Node([11, 12, 13, 14]); 819 Node nmap = Node(["11", "12", "13", "14"], [11, 12, 13, 14]); 820 821 assert(narray[0].as!int == 11); 822 assert(null !is collectException(narray[42])); 823 assert(nmap["11"].as!int == 11); 824 assert(nmap["14"].as!int == 14); 825 } 826 unittest 827 { 828 writeln("D:YAML Node opIndex unittest"); 829 alias Node.Value Value; 830 alias Node.Pair Pair; 831 832 Node narray = Node([11, 12, 13, 14]); 833 Node nmap = Node(["11", "12", "13", "14"], [11, 12, 13, 14]); 834 835 assert(narray[0].as!int == 11); 836 assert(null !is collectException(narray[42])); 837 assert(nmap["11"].as!int == 11); 838 assert(nmap["14"].as!int == 14); 839 assert(null !is collectException(nmap["42"])); 840 841 narray.add(YAMLNull()); 842 nmap.add(YAMLNull(), "Nothing"); 843 assert(narray[4].as!YAMLNull == YAMLNull()); 844 assert(nmap[YAMLNull()].as!string == "Nothing"); 845 846 assertThrown!NodeException(nmap[11]); 847 assertThrown!NodeException(nmap[14]); 848 } 849 850 /** Determine if a collection contains specified value. 851 * 852 * If the node is a sequence, check if it contains the specified value. 853 * If it's a mapping, check if it has a value that matches specified value. 854 * 855 * Params: rhs = Item to look for. Use YAMLNull to check for a null value. 856 * 857 * Returns: true if rhs was found, false otherwise. 858 * 859 * Throws: NodeException if the node is not a collection. 860 */ 861 bool contains(T)(T rhs) const @safe 862 { 863 return contains_!(T, No.key, "contains")(rhs); 864 } 865 866 867 /** Determine if a mapping contains specified key. 868 * 869 * Params: rhs = Key to look for. Use YAMLNull to check for a null key. 870 * 871 * Returns: true if rhs was found, false otherwise. 872 * 873 * Throws: NodeException if the node is not a mapping. 874 */ 875 bool containsKey(T)(T rhs) const @safe 876 { 877 return contains_!(T, Yes.key, "containsKey")(rhs); 878 } 879 880 // Unittest for contains() and containsKey(). 881 unittest 882 { 883 writeln("D:YAML Node contains/containsKey unittest"); 884 auto seq = Node([1, 2, 3, 4, 5]); 885 assert(seq.contains(3)); 886 assert(seq.contains(5)); 887 assert(!seq.contains("5")); 888 assert(!seq.contains(6)); 889 assert(!seq.contains(float.nan)); 890 assertThrown!NodeException(seq.containsKey(5)); 891 892 auto seq2 = Node(["1", "2"]); 893 assert(seq2.contains("1")); 894 assert(!seq2.contains(1)); 895 896 auto map = Node(["1", "2", "3", "4"], [1, 2, 3, 4]); 897 assert(map.contains(1)); 898 assert(!map.contains("1")); 899 assert(!map.contains(5)); 900 assert(!map.contains(float.nan)); 901 assert(map.containsKey("1")); 902 assert(map.containsKey("4")); 903 assert(!map.containsKey(1)); 904 assert(!map.containsKey("5")); 905 906 assert(!seq.contains(YAMLNull())); 907 assert(!map.contains(YAMLNull())); 908 assert(!map.containsKey(YAMLNull())); 909 seq.add(YAMLNull()); 910 map.add("Nothing", YAMLNull()); 911 assert(seq.contains(YAMLNull())); 912 assert(map.contains(YAMLNull())); 913 assert(!map.containsKey(YAMLNull())); 914 map.add(YAMLNull(), "Nothing"); 915 assert(map.containsKey(YAMLNull())); 916 917 auto map2 = Node([1, 2, 3, 4], [1, 2, 3, 4]); 918 assert(!map2.contains("1")); 919 assert(map2.contains(1)); 920 assert(!map2.containsKey("1")); 921 assert(map2.containsKey(1)); 922 923 // scalar 924 assertThrown!NodeException(Node(1).contains(4)); 925 assertThrown!NodeException(Node(1).containsKey(4)); 926 927 auto mapNan = Node([1.0, 2, double.nan], [1, double.nan, 5]); 928 929 assert(mapNan.contains(double.nan)); 930 assert(mapNan.containsKey(double.nan)); 931 } 932 933 /// Assignment (shallow copy) by value. 934 void opAssign(Node rhs) @safe nothrow 935 { 936 opAssign(rhs); 937 } 938 939 /// Assignment (shallow copy) by reference. 940 void opAssign(ref Node rhs) @trusted nothrow 941 { 942 // Value opAssign doesn't really throw, so force it to nothrow. 943 alias Value delegate(Value) nothrow valueAssignNothrow; 944 (cast(valueAssignNothrow)&value_.opAssign!Value)(rhs.value_); 945 startMark_ = rhs.startMark_; 946 tag_ = rhs.tag_; 947 scalarStyle = rhs.scalarStyle; 948 collectionStyle = rhs.collectionStyle; 949 } 950 // Unittest for opAssign(). 951 unittest 952 { 953 auto seq = Node([1, 2, 3, 4, 5]); 954 auto assigned = seq; 955 assert(seq == assigned, 956 "Node.opAssign() doesn't produce an equivalent copy"); 957 } 958 959 /** Set element at specified index in a collection. 960 * 961 * This method can only be called on collection nodes. 962 * 963 * If the node is a sequence, index must be integral. 964 * 965 * If the node is a mapping, sets the _value corresponding to the first 966 * key matching index (including conversion, so e.g. "42" matches 42). 967 * 968 * If the node is a mapping and no key matches index, a new key-value 969 * pair is added to the mapping. In sequences the index must be in 970 * range. This ensures behavior siilar to D arrays and associative 971 * arrays. 972 * 973 * To set element at a null index, use YAMLNull for index. 974 * 975 * Params: index = Index of the value to set. 976 * 977 * Throws: NodeException if the node is not a collection, index is out 978 * of range or if a non-integral index is used on a sequence node. 979 */ 980 void opIndexAssign(K, V)(V value, K index) @trusted 981 { 982 if(isSequence()) 983 { 984 // This ensures K is integral. 985 checkSequenceIndex(index); 986 static if(isIntegral!K || is(Unqual!K == bool)) 987 { 988 auto nodes = value_.get!(Node[]); 989 static if(is(Unqual!V == Node)){nodes[index] = value;} 990 else {nodes[index] = Node(value);} 991 value_ = Value(nodes); 992 return; 993 } 994 assert(false); 995 } 996 else if(isMapping()) 997 { 998 const idx = findPair(index); 999 if(idx < 0){add(index, value);} 1000 else 1001 { 1002 auto pairs = as!(Node.Pair[])(); 1003 static if(is(Unqual!V == Node)){pairs[idx].value = value;} 1004 else {pairs[idx].value = Node(value);} 1005 value_ = Value(pairs); 1006 } 1007 return; 1008 } 1009 1010 throw new Error("Trying to index a " ~ nodeTypeString ~ " node", startMark_); 1011 } 1012 unittest 1013 { 1014 writeln("D:YAML Node opIndexAssign unittest"); 1015 1016 with(Node([1, 2, 3, 4, 3])) 1017 { 1018 opIndexAssign(42, 3); 1019 assert(length == 5); 1020 assert(opIndex(3).as!int == 42); 1021 1022 opIndexAssign(YAMLNull(), 0); 1023 assert(opIndex(0) == YAMLNull()); 1024 } 1025 with(Node(["1", "2", "3"], [4, 5, 6])) 1026 { 1027 opIndexAssign(42, "3"); 1028 opIndexAssign(123, 456); 1029 assert(length == 4); 1030 assert(opIndex("3").as!int == 42); 1031 assert(opIndex(456).as!int == 123); 1032 1033 opIndexAssign(43, 3); 1034 //3 and "3" should be different 1035 assert(length == 5); 1036 assert(opIndex("3").as!int == 42); 1037 assert(opIndex(3).as!int == 43); 1038 1039 opIndexAssign(YAMLNull(), "2"); 1040 assert(opIndex("2") == YAMLNull()); 1041 } 1042 } 1043 1044 /** Return a range object iterating over a sequence, getting each 1045 * element as T. 1046 * 1047 * If T is Node, simply iterate over the nodes in the sequence. 1048 * Otherwise, convert each node to T during iteration. 1049 * 1050 * Throws: NodeException if the node is not a sequence or an element 1051 * could not be converted to specified type. 1052 */ 1053 auto sequence(T = Node)() @trusted 1054 { 1055 enforce(isSequence, 1056 new Error("Trying to 'sequence'-iterate over a " ~ nodeTypeString ~ " node", 1057 startMark_)); 1058 struct Range 1059 { 1060 Node[] subnodes; 1061 size_t position; 1062 1063 this(Node[] nodes) 1064 { 1065 subnodes = nodes; 1066 position = 0; 1067 } 1068 1069 /* Input range functionality. */ 1070 bool empty() @property { return position >= subnodes.length; } 1071 1072 void popFront() 1073 { 1074 enforce(!empty, "Attempted to popFront an empty sequence"); 1075 position++; 1076 } 1077 1078 T front() @property 1079 { 1080 enforce(!empty, "Attempted to take the front of an empty sequence"); 1081 static if (is(Unqual!T == Node)) 1082 return subnodes[position]; 1083 else 1084 return subnodes[position].as!T; 1085 } 1086 1087 /* Forward range functionality. */ 1088 Range save() { return this; } 1089 1090 /* Bidirectional range functionality. */ 1091 void popBack() 1092 { 1093 enforce(!empty, "Attempted to popBack an empty sequence"); 1094 subnodes = subnodes[0 .. $ - 1]; 1095 } 1096 1097 T back() 1098 { 1099 enforce(!empty, "Attempted to take the back of an empty sequence"); 1100 static if (is(Unqual!T == Node)) 1101 return subnodes[$ - 1]; 1102 else 1103 return subnodes[$ - 1].as!T; 1104 } 1105 1106 /* Random-access range functionality. */ 1107 size_t length() const @property { return subnodes.length; } 1108 T opIndex(size_t index) 1109 { 1110 static if (is(Unqual!T == Node)) 1111 return subnodes[index]; 1112 else 1113 return subnodes[index].as!T; 1114 } 1115 1116 static assert(isInputRange!Range); 1117 static assert(isForwardRange!Range); 1118 static assert(isBidirectionalRange!Range); 1119 static assert(isRandomAccessRange!Range); 1120 } 1121 return Range(get!(Node[])); 1122 } 1123 unittest 1124 { 1125 writeln("D:YAML Node sequence unittest"); 1126 1127 Node n1 = Node([1, 2, 3, 4]); 1128 int[int] array; 1129 Node n2 = Node(array); 1130 1131 auto r = n1.sequence!int.map!(x => x * 10); 1132 assert(r.equal([10, 20, 30, 40])); 1133 1134 assertThrown(n2.sequence); 1135 } 1136 1137 /** Return a range object iterating over mapping's pairs. 1138 * 1139 * Throws: NodeException if the node is not a mapping. 1140 * 1141 */ 1142 auto mapping() @trusted 1143 { 1144 enforce(isMapping, 1145 new Error("Trying to 'mapping'-iterate over a " 1146 ~ nodeTypeString ~ " node", startMark_)); 1147 struct Range 1148 { 1149 Node.Pair[] pairs; 1150 size_t position; 1151 1152 this(Node.Pair[] pairs) 1153 { 1154 this.pairs = pairs; 1155 position = 0; 1156 } 1157 1158 /* Input range functionality. */ 1159 bool empty() { return position >= pairs.length; } 1160 1161 void popFront() 1162 { 1163 enforce(!empty, "Attempted to popFront an empty mapping"); 1164 position++; 1165 } 1166 1167 Pair front() 1168 { 1169 enforce(!empty, "Attempted to take the front of an empty mapping"); 1170 return pairs[position]; 1171 } 1172 1173 /* Forward range functionality. */ 1174 Range save() { return this; } 1175 1176 /* Bidirectional range functionality. */ 1177 void popBack() 1178 { 1179 enforce(!empty, "Attempted to popBack an empty mapping"); 1180 pairs = pairs[0 .. $ - 1]; 1181 } 1182 1183 Pair back() 1184 { 1185 enforce(!empty, "Attempted to take the back of an empty mapping"); 1186 return pairs[$ - 1]; 1187 } 1188 1189 /* Random-access range functionality. */ 1190 size_t length() const @property { return pairs.length; } 1191 Pair opIndex(size_t index) { return pairs[index]; } 1192 1193 static assert(isInputRange!Range); 1194 static assert(isForwardRange!Range); 1195 static assert(isBidirectionalRange!Range); 1196 static assert(isRandomAccessRange!Range); 1197 } 1198 return Range(get!(Node.Pair[])); 1199 } 1200 unittest 1201 { 1202 writeln("D:YAML Node mapping unittest"); 1203 1204 int[int] array; 1205 Node n = Node(array); 1206 n[1] = "foo"; 1207 n[2] = "bar"; 1208 n[3] = "baz"; 1209 1210 string[int] test; 1211 foreach (pair; n.mapping) 1212 test[pair.key.as!int] = pair.value.as!string; 1213 1214 assert(test[1] == "foo"); 1215 assert(test[2] == "bar"); 1216 assert(test[3] == "baz"); 1217 } 1218 1219 /** Return a range object iterating over mapping's keys. 1220 * 1221 * If K is Node, simply iterate over the keys in the mapping. 1222 * Otherwise, convert each key to T during iteration. 1223 * 1224 * Throws: NodeException if the nodes is not a mapping or an element 1225 * could not be converted to specified type. 1226 */ 1227 auto mappingKeys(K = Node)() @trusted 1228 { 1229 enforce(isMapping, 1230 new Error("Trying to 'mappingKeys'-iterate over a " 1231 ~ nodeTypeString ~ " node", startMark_)); 1232 static if (is(Unqual!K == Node)) 1233 return mapping.map!(pair => pair.key); 1234 else 1235 return mapping.map!(pair => pair.key.as!K); 1236 } 1237 unittest 1238 { 1239 writeln("D:YAML Node mappingKeys unittest"); 1240 1241 int[int] array; 1242 Node m1 = Node(array); 1243 m1["foo"] = 2; 1244 m1["bar"] = 3; 1245 1246 assert(m1.mappingKeys.equal(["foo", "bar"])); 1247 } 1248 1249 /** Return a range object iterating over mapping's values. 1250 * 1251 * If V is Node, simply iterate over the values in the mapping. 1252 * Otherwise, convert each key to V during iteration. 1253 * 1254 * Throws: NodeException if the nodes is not a mapping or an element 1255 * could not be converted to specified type. 1256 */ 1257 auto mappingValues(V = Node)() @trusted 1258 { 1259 enforce(isMapping, 1260 new Error("Trying to 'mappingValues'-iterate over a " 1261 ~ nodeTypeString ~ " node", startMark_)); 1262 static if (is(Unqual!V == Node)) 1263 return mapping.map!(pair => pair.value); 1264 else 1265 return mapping.map!(pair => pair.value.as!V); 1266 } 1267 unittest 1268 { 1269 writeln("D:YAML Node mappingValues unittest"); 1270 1271 int[int] array; 1272 Node m1 = Node(array); 1273 m1["foo"] = 2; 1274 m1["bar"] = 3; 1275 1276 assert(m1.mappingValues.equal([2, 3])); 1277 } 1278 1279 /** Foreach over a sequence, getting each element as T. 1280 * 1281 * If T is Node, simply iterate over the nodes in the sequence. 1282 * Otherwise, convert each node to T during iteration. 1283 * 1284 * Throws: NodeException if the node is not a sequence or an 1285 * element could not be converted to specified type. 1286 */ 1287 int opApply(T)(int delegate(ref T) dg) @trusted 1288 { 1289 enforce(isSequence, 1290 new Error("Trying to sequence-foreach over a " ~ nodeTypeString ~ " node", 1291 startMark_)); 1292 1293 int result = 0; 1294 foreach(ref node; get!(Node[])) 1295 { 1296 static if(is(Unqual!T == Node)) 1297 { 1298 result = dg(node); 1299 } 1300 else 1301 { 1302 T temp = node.as!T; 1303 result = dg(temp); 1304 } 1305 if(result){break;} 1306 } 1307 return result; 1308 } 1309 unittest 1310 { 1311 writeln("D:YAML Node opApply unittest 1"); 1312 1313 alias Node.Value Value; 1314 alias Node.Pair Pair; 1315 1316 Node n1 = Node(Value(cast(long)11)); 1317 Node n2 = Node(Value(cast(long)12)); 1318 Node n3 = Node(Value(cast(long)13)); 1319 Node n4 = Node(Value(cast(long)14)); 1320 Node narray = Node([n1, n2, n3, n4]); 1321 1322 int[] array, array2; 1323 foreach(int value; narray) 1324 { 1325 array ~= value; 1326 } 1327 foreach(Node node; narray) 1328 { 1329 array2 ~= node.as!int; 1330 } 1331 assert(array == [11, 12, 13, 14]); 1332 assert(array2 == [11, 12, 13, 14]); 1333 } 1334 1335 /** Foreach over a mapping, getting each key/value as K/V. 1336 * 1337 * If the K and/or V is Node, simply iterate over the nodes in the mapping. 1338 * Otherwise, convert each key/value to T during iteration. 1339 * 1340 * Throws: NodeException if the node is not a mapping or an 1341 * element could not be converted to specified type. 1342 */ 1343 int opApply(K, V)(int delegate(ref K, ref V) dg) @trusted 1344 { 1345 enforce(isMapping, 1346 new Error("Trying to mapping-foreach over a " ~ nodeTypeString ~ " node", 1347 startMark_)); 1348 1349 int result = 0; 1350 foreach(ref pair; get!(Node.Pair[])) 1351 { 1352 static if(is(Unqual!K == Node) && is(Unqual!V == Node)) 1353 { 1354 result = dg(pair.key, pair.value); 1355 } 1356 else static if(is(Unqual!K == Node)) 1357 { 1358 V tempValue = pair.value.as!V; 1359 result = dg(pair.key, tempValue); 1360 } 1361 else static if(is(Unqual!V == Node)) 1362 { 1363 K tempKey = pair.key.as!K; 1364 result = dg(tempKey, pair.value); 1365 } 1366 else 1367 { 1368 K tempKey = pair.key.as!K; 1369 V tempValue = pair.value.as!V; 1370 result = dg(tempKey, tempValue); 1371 } 1372 1373 if(result){break;} 1374 } 1375 return result; 1376 } 1377 unittest 1378 { 1379 writeln("D:YAML Node opApply unittest 2"); 1380 1381 alias Node.Value Value; 1382 alias Node.Pair Pair; 1383 1384 Node n1 = Node(cast(long)11); 1385 Node n2 = Node(cast(long)12); 1386 Node n3 = Node(cast(long)13); 1387 Node n4 = Node(cast(long)14); 1388 1389 Node k1 = Node("11"); 1390 Node k2 = Node("12"); 1391 Node k3 = Node("13"); 1392 Node k4 = Node("14"); 1393 1394 Node nmap1 = Node([Pair(k1, n1), 1395 Pair(k2, n2), 1396 Pair(k3, n3), 1397 Pair(k4, n4)]); 1398 1399 int[string] expected = ["11" : 11, 1400 "12" : 12, 1401 "13" : 13, 1402 "14" : 14]; 1403 int[string] array; 1404 foreach(string key, int value; nmap1) 1405 { 1406 array[key] = value; 1407 } 1408 assert(array == expected); 1409 1410 Node nmap2 = Node([Pair(k1, Node(cast(long)5)), 1411 Pair(k2, Node(true)), 1412 Pair(k3, Node(cast(real)1.0)), 1413 Pair(k4, Node("yarly"))]); 1414 1415 foreach(string key, Node value; nmap2) 1416 { 1417 switch(key) 1418 { 1419 case "11": assert(value.as!int == 5 ); break; 1420 case "12": assert(value.as!bool == true ); break; 1421 case "13": assert(value.as!float == 1.0 ); break; 1422 case "14": assert(value.as!string == "yarly"); break; 1423 default: assert(false); 1424 } 1425 } 1426 } 1427 1428 /** Add an element to a sequence. 1429 * 1430 * This method can only be called on sequence nodes. 1431 * 1432 * If value is a node, it is copied to the sequence directly. Otherwise 1433 * value is converted to a node and then stored in the sequence. 1434 * 1435 * $(P When emitting, all values in the sequence will be emitted. When 1436 * using the !!set tag, the user needs to ensure that all elements in 1437 * the sequence are unique, otherwise $(B invalid) YAML code will be 1438 * emitted.) 1439 * 1440 * Params: value = Value to _add to the sequence. 1441 */ 1442 void add(T)(T value) @trusted 1443 { 1444 enforce(isSequence(), 1445 new Error("Trying to add an element to a " ~ nodeTypeString ~ " node", startMark_)); 1446 1447 auto nodes = get!(Node[])(); 1448 static if(is(Unqual!T == Node)){nodes ~= value;} 1449 else {nodes ~= Node(value);} 1450 value_ = Value(nodes); 1451 } 1452 unittest 1453 { 1454 writeln("D:YAML Node add unittest 1"); 1455 1456 with(Node([1, 2, 3, 4])) 1457 { 1458 add(5.0f); 1459 assert(opIndex(4).as!float == 5.0f); 1460 } 1461 } 1462 1463 /** Add a key-value pair to a mapping. 1464 * 1465 * This method can only be called on mapping nodes. 1466 * 1467 * If key and/or value is a node, it is copied to the mapping directly. 1468 * Otherwise it is converted to a node and then stored in the mapping. 1469 * 1470 * $(P It is possible for the same key to be present more than once in a 1471 * mapping. When emitting, all key-value pairs will be emitted. 1472 * This is useful with the "!!pairs" tag, but will result in 1473 * $(B invalid) YAML with "!!map" and "!!omap" tags.) 1474 * 1475 * Params: key = Key to _add. 1476 * value = Value to _add. 1477 */ 1478 void add(K, V)(K key, V value) @trusted 1479 { 1480 enforce(isMapping(), 1481 new Error("Trying to add a key-value pair to a " ~ 1482 nodeTypeString ~ " node", 1483 startMark_)); 1484 1485 auto pairs = get!(Node.Pair[])(); 1486 pairs ~= Pair(key, value); 1487 value_ = Value(pairs); 1488 } 1489 unittest 1490 { 1491 writeln("D:YAML Node add unittest 2"); 1492 with(Node([1, 2], [3, 4])) 1493 { 1494 add(5, "6"); 1495 assert(opIndex(5).as!string == "6"); 1496 } 1497 } 1498 1499 /** Determine whether a key is in a mapping, and access its value. 1500 * 1501 * This method can only be called on mapping nodes. 1502 * 1503 * Params: key = Key to search for. 1504 * 1505 * Returns: A pointer to the value (as a Node) corresponding to key, 1506 * or null if not found. 1507 * 1508 * Note: Any modification to the node can invalidate the returned 1509 * pointer. 1510 * 1511 * See_Also: contains 1512 */ 1513 Node* opBinaryRight(string op, K)(K key) @system 1514 if (op == "in") 1515 { 1516 enforce(isMapping, new Error("Trying to use 'in' on a " ~ 1517 nodeTypeString ~ " node", startMark_)); 1518 1519 auto idx = findPair(key); 1520 if(idx < 0) 1521 { 1522 return null; 1523 } 1524 else 1525 { 1526 return &(get!(Node.Pair[])[idx].value); 1527 } 1528 } 1529 unittest 1530 { 1531 writeln(`D:YAML Node opBinaryRight!"in" unittest`); 1532 auto mapping = Node(["foo", "baz"], ["bar", "qux"]); 1533 assert("bad" !in mapping && ("bad" in mapping) is null); 1534 Node* foo = "foo" in mapping; 1535 assert(foo !is null); 1536 assert(*foo == Node("bar")); 1537 assert(foo.get!string == "bar"); 1538 *foo = Node("newfoo"); 1539 assert(mapping["foo"] == Node("newfoo")); 1540 } 1541 1542 /** Remove first (if any) occurence of a value in a collection. 1543 * 1544 * This method can only be called on collection nodes. 1545 * 1546 * If the node is a sequence, the first node matching value is removed. 1547 * If the node is a mapping, the first key-value pair where _value 1548 * matches specified value is removed. 1549 * 1550 * Params: rhs = Value to _remove. 1551 * 1552 * Throws: NodeException if the node is not a collection. 1553 */ 1554 void remove(T)(T rhs) @trusted 1555 { 1556 remove_!(T, No.key, "remove")(rhs); 1557 } 1558 unittest 1559 { 1560 writeln("D:YAML Node remove unittest"); 1561 with(Node([1, 2, 3, 4, 3])) 1562 { 1563 remove(3); 1564 assert(length == 4); 1565 assert(opIndex(2).as!int == 4); 1566 assert(opIndex(3).as!int == 3); 1567 1568 add(YAMLNull()); 1569 assert(length == 5); 1570 remove(YAMLNull()); 1571 assert(length == 4); 1572 } 1573 with(Node(["1", "2", "3"], [4, 5, 6])) 1574 { 1575 remove(4); 1576 assert(length == 2); 1577 add("nullkey", YAMLNull()); 1578 assert(length == 3); 1579 remove(YAMLNull()); 1580 assert(length == 2); 1581 } 1582 } 1583 1584 /** Remove element at the specified index of a collection. 1585 * 1586 * This method can only be called on collection nodes. 1587 * 1588 * If the node is a sequence, index must be integral. 1589 * 1590 * If the node is a mapping, remove the first key-value pair where 1591 * key matches index. 1592 * 1593 * If the node is a mapping and no key matches index, nothing is removed 1594 * and no exception is thrown. This ensures behavior siilar to D arrays 1595 * and associative arrays. 1596 * 1597 * Params: index = Index to remove at. 1598 * 1599 * Throws: NodeException if the node is not a collection, index is out 1600 * of range or if a non-integral index is used on a sequence node. 1601 */ 1602 void removeAt(T)(T index) @trusted 1603 { 1604 remove_!(T, Yes.key, "removeAt")(index); 1605 } 1606 unittest 1607 { 1608 writeln("D:YAML Node removeAt unittest"); 1609 with(Node([1, 2, 3, 4, 3])) 1610 { 1611 removeAt(3); 1612 assertThrown!NodeException(removeAt("3")); 1613 assert(length == 4); 1614 assert(opIndex(3).as!int == 3); 1615 } 1616 with(Node(["1", "2", "3"], [4, 5, 6])) 1617 { 1618 // no integer 2 key, so don't remove anything 1619 removeAt(2); 1620 assert(length == 3); 1621 removeAt("2"); 1622 assert(length == 2); 1623 add(YAMLNull(), "nullval"); 1624 assert(length == 3); 1625 removeAt(YAMLNull()); 1626 assert(length == 2); 1627 } 1628 } 1629 1630 /// Compare with another _node. 1631 int opCmp(ref const Node node) const @safe 1632 { 1633 return cmp!(Yes.useTag)(node); 1634 } 1635 1636 // Compute hash of the node. 1637 hash_t toHash() @safe nothrow const 1638 { 1639 const tagHash = tag_.isNull ? 0 : tag_.toHash(); 1640 // Variant toHash is not const at the moment, so we need to const-cast. 1641 return tagHash + value_.toHash(); 1642 } 1643 unittest 1644 { 1645 writeln("Node(42).toHash(): ", Node(42).toHash()); 1646 } 1647 1648 package: 1649 // Construct a node from raw data. 1650 // 1651 // Params: value = Value of the node. 1652 // startMark = Start position of the node in file. 1653 // tag = Tag of the node. 1654 // scalarStyle = Scalar style of the node. 1655 // collectionStyle = Collection style of the node. 1656 // 1657 // Returns: Constructed node. 1658 static Node rawNode(Value value, const Mark startMark, const Tag tag, 1659 const ScalarStyle scalarStyle, 1660 const CollectionStyle collectionStyle) @trusted 1661 { 1662 Node node; 1663 node.value_ = value; 1664 node.startMark_ = startMark; 1665 node.tag_ = tag; 1666 node.scalarStyle = scalarStyle; 1667 node.collectionStyle = collectionStyle; 1668 1669 return node; 1670 } 1671 1672 // Construct Node.Value from user defined type. 1673 static Value userValue(T)(T value) @trusted nothrow 1674 { 1675 return Value(cast(YAMLObject)new YAMLContainer!T(value)); 1676 } 1677 1678 // Construct Node.Value from a type it can store directly (after casting if needed) 1679 static Value value(T)(T value) @system nothrow if(allowed!T) 1680 { 1681 static if(Value.allowed!T) 1682 { 1683 return Value(value); 1684 } 1685 else static if(isIntegral!T) 1686 { 1687 return Value(cast(long)(value)); 1688 } 1689 else static if (is(Unqual!T == bool)) 1690 { 1691 return Value(cast(bool)(value)); 1692 } 1693 else static if(isFloatingPoint!T) 1694 { 1695 return Value(cast(real)(value)); 1696 } 1697 else static if(isSomeString!T) 1698 { 1699 return Value(to!string(value)); 1700 } 1701 else static assert(false, "Unknown value type. Is value() in sync with allowed()?"); 1702 } 1703 1704 // Equality test with any value. 1705 // 1706 // useTag determines whether or not to consider tags in node-node comparisons. 1707 bool equals(Flag!"useTag" useTag, T)(ref T rhs) const @safe 1708 { 1709 static if(is(Unqual!T == Node)) 1710 { 1711 return cmp!useTag(rhs) == 0; 1712 } 1713 else 1714 { 1715 try 1716 { 1717 auto stored = get!(const(Unqual!T), No.stringConversion); 1718 // Need to handle NaNs separately. 1719 static if(isFloatingPoint!T) 1720 { 1721 return rhs == stored || (isNaN(rhs) && isNaN(stored)); 1722 } 1723 else 1724 { 1725 return rhs == get!(const(Unqual!T)); 1726 } 1727 } 1728 catch(NodeException e){return false;} 1729 } 1730 } 1731 1732 // Comparison with another node. 1733 // 1734 // Used for ordering in mappings and for opEquals. 1735 // 1736 // useTag determines whether or not to consider tags in the comparison. 1737 int cmp(Flag!"useTag" useTag)(const ref Node rhs) const @trusted 1738 { 1739 // Compare tags - if equal or both null, we need to compare further. 1740 static if(useTag) 1741 { 1742 const tagCmp = tag_.isNull ? rhs.tag_.isNull ? 0 : -1 1743 : rhs.tag_.isNull ? 1 : tag_.opCmp(rhs.tag_); 1744 if(tagCmp != 0){return tagCmp;} 1745 } 1746 1747 static int cmp(T1, T2)(T1 a, T2 b) 1748 { 1749 return a > b ? 1 : 1750 a < b ? -1 : 1751 0; 1752 } 1753 1754 // Compare validity: if both valid, we have to compare further. 1755 const v1 = isValid; 1756 const v2 = rhs.isValid; 1757 if(!v1){return v2 ? -1 : 0;} 1758 if(!v2){return 1;} 1759 1760 const typeCmp = type.opCmp(rhs.type); 1761 if(typeCmp != 0){return typeCmp;} 1762 1763 static int compareCollections(T)(const ref Node lhs, const ref Node rhs) 1764 { 1765 const c1 = lhs.value_.get!(const T); 1766 const c2 = rhs.value_.get!(const T); 1767 if(c1 is c2){return 0;} 1768 if(c1.length != c2.length) 1769 { 1770 return cmp(c1.length, c2.length); 1771 } 1772 // Equal lengths, compare items. 1773 foreach(i; 0 .. c1.length) 1774 { 1775 const itemCmp = c1[i].cmp!useTag(c2[i]); 1776 if(itemCmp != 0){return itemCmp;} 1777 } 1778 return 0; 1779 } 1780 1781 if(isSequence){return compareCollections!(Node[])(this, rhs);} 1782 if(isMapping) {return compareCollections!(Pair[])(this, rhs);} 1783 if(isString) 1784 { 1785 return std.algorithm.cmp(value_.get!(const string), 1786 rhs.value_.get!(const string)); 1787 } 1788 if(isInt) 1789 { 1790 return cmp(value_.get!(const long), rhs.value_.get!(const long)); 1791 } 1792 if(isBool) 1793 { 1794 const b1 = value_.get!(const bool); 1795 const b2 = rhs.value_.get!(const bool); 1796 return b1 ? b2 ? 0 : 1 1797 : b2 ? -1 : 0; 1798 } 1799 if(isBinary) 1800 { 1801 const b1 = value_.get!(const ubyte[]); 1802 const b2 = rhs.value_.get!(const ubyte[]); 1803 return std.algorithm.cmp(b1, b2); 1804 } 1805 if(isNull) 1806 { 1807 return 0; 1808 } 1809 // Floats need special handling for NaNs . 1810 // We consider NaN to be lower than any float. 1811 if(isFloat) 1812 { 1813 const r1 = value_.get!(const real); 1814 const r2 = rhs.value_.get!(const real); 1815 if(isNaN(r1)) 1816 { 1817 return isNaN(r2) ? 0 : -1; 1818 } 1819 if(isNaN(r2)) 1820 { 1821 return 1; 1822 } 1823 // Fuzzy equality. 1824 if(r1 <= r2 + real.epsilon && r1 >= r2 - real.epsilon) 1825 { 1826 return 0; 1827 } 1828 return cmp(r1, r2); 1829 } 1830 else if(isTime) 1831 { 1832 const t1 = value_.get!(const SysTime); 1833 const t2 = rhs.value_.get!(const SysTime); 1834 return cmp(t1, t2); 1835 } 1836 else if(isUserType) 1837 { 1838 return value_.get!(const YAMLObject).cmp(rhs.value_.get!(const YAMLObject)); 1839 } 1840 assert(false, "Unknown type of node for comparison : " ~ type.toString()); 1841 } 1842 1843 // Get a string representation of the node tree. Used for debugging. 1844 // 1845 // Params: level = Level of the node in the tree. 1846 // 1847 // Returns: String representing the node tree. 1848 @property string debugString(uint level = 0) @trusted 1849 { 1850 string indent; 1851 foreach(i; 0 .. level){indent ~= " ";} 1852 1853 if(!isValid){return indent ~ "invalid";} 1854 1855 if(isSequence) 1856 { 1857 string result = indent ~ "sequence:\n"; 1858 foreach(ref node; get!(Node[])) 1859 { 1860 result ~= node.debugString(level + 1); 1861 } 1862 return result; 1863 } 1864 if(isMapping) 1865 { 1866 string result = indent ~ "mapping:\n"; 1867 foreach(ref pair; get!(Node.Pair[])) 1868 { 1869 result ~= indent ~ " pair\n"; 1870 result ~= pair.key.debugString(level + 2); 1871 result ~= pair.value.debugString(level + 2); 1872 } 1873 return result; 1874 } 1875 if(isScalar) 1876 { 1877 return indent ~ "scalar(" ~ 1878 (convertsTo!string ? get!string : type.toString()) ~ ")\n"; 1879 } 1880 assert(false); 1881 } 1882 1883 // Get type of the node value (YAMLObject for user types). 1884 @property TypeInfo type() const @trusted nothrow 1885 { 1886 alias TypeInfo delegate() const nothrow nothrowType; 1887 return (cast(nothrowType)&value_.type)(); 1888 } 1889 1890 public: 1891 // Determine if the value stored by the node is of specified type. 1892 // 1893 // This only works for default YAML types, not for user defined types. 1894 @property bool isType(T)() const @safe nothrow 1895 { 1896 return this.type is typeid(Unqual!T); 1897 } 1898 1899 // Is the value a bool? 1900 alias isType!bool isBool; 1901 1902 // Is the value a raw binary buffer? 1903 alias isType!(ubyte[]) isBinary; 1904 1905 // Is the value an integer? 1906 alias isType!long isInt; 1907 1908 // Is the value a floating point number? 1909 alias isType!real isFloat; 1910 1911 // Is the value a string? 1912 alias isType!string isString; 1913 1914 // Is the value a timestamp? 1915 alias isType!SysTime isTime; 1916 1917 // Does given node have the same type as this node? 1918 bool hasEqualType(const ref Node node) const @safe 1919 { 1920 return this.type is node.type; 1921 } 1922 1923 // Return a string describing node type (sequence, mapping or scalar) 1924 @property string nodeTypeString() const @safe nothrow 1925 { 1926 assert(isScalar || isSequence || isMapping, "Unknown node type"); 1927 return isScalar ? "scalar" : 1928 isSequence ? "sequence" : 1929 isMapping ? "mapping" : ""; 1930 } 1931 1932 // Determine if the value can be converted to specified type. 1933 @property bool convertsTo(T)() const @safe nothrow 1934 { 1935 if(isType!T){return true;} 1936 1937 // Every type allowed in Value should be convertible to string. 1938 static if(isSomeString!T) {return true;} 1939 else static if(isFloatingPoint!T){return isInt() || isFloat();} 1940 else static if(isIntegral!T) {return isInt();} 1941 else static if(is(Unqual!T==bool)){return isBool();} 1942 else {return false;} 1943 } 1944 1945 private: 1946 // Implementation of contains() and containsKey(). 1947 bool contains_(T, Flag!"key" key, string func)(T rhs) const @trusted 1948 { 1949 static if(!key) if(isSequence) 1950 { 1951 foreach(ref node; value_.get!(const Node[])) 1952 { 1953 if(node == rhs){return true;} 1954 } 1955 return false; 1956 } 1957 1958 if(isMapping) 1959 { 1960 return findPair!(T, key)(rhs) >= 0; 1961 } 1962 1963 throw new Error("Trying to use " ~ func ~ "() on a " ~ nodeTypeString ~ " node", 1964 startMark_); 1965 } 1966 1967 // Implementation of remove() and removeAt() 1968 void remove_(T, Flag!"key" key, string func)(T rhs) @system 1969 { 1970 enforce(isSequence || isMapping, 1971 new Error("Trying to " ~ func ~ "() from a " ~ nodeTypeString ~ " node", 1972 startMark_)); 1973 1974 static void removeElem(E, I)(ref Node node, I index) 1975 { 1976 auto elems = node.value_.get!(E[]); 1977 moveAll(elems[cast(size_t)index + 1 .. $], elems[cast(size_t)index .. $ - 1]); 1978 elems.length = elems.length - 1; 1979 node.value_ = Value(elems); 1980 } 1981 1982 if(isSequence()) 1983 { 1984 static long getIndex(ref Node node, ref T rhs) 1985 { 1986 foreach(idx, ref elem; node.get!(Node[])) 1987 { 1988 if(elem.convertsTo!T && elem.as!(T, No.stringConversion) == rhs) 1989 { 1990 return idx; 1991 } 1992 } 1993 return -1; 1994 } 1995 1996 const index = select!key(rhs, getIndex(this, rhs)); 1997 1998 // This throws if the index is not integral. 1999 checkSequenceIndex(index); 2000 2001 static if(isIntegral!(typeof(index))){removeElem!Node(this, index);} 2002 else {assert(false, "Non-integral sequence index");} 2003 } 2004 else if(isMapping()) 2005 { 2006 const index = findPair!(T, key)(rhs); 2007 if(index >= 0){removeElem!Pair(this, index);} 2008 } 2009 } 2010 2011 // Get index of pair with key (or value, if key is false) matching index. 2012 sizediff_t findPair(T, Flag!"key" key = Yes.key)(const ref T index) const @trusted 2013 { 2014 const pairs = value_.get!(const Pair[])(); 2015 const(Node)* node; 2016 foreach(idx, ref const(Pair) pair; pairs) 2017 { 2018 static if(key){node = &pair.key;} 2019 else {node = &pair.value;} 2020 2021 2022 bool typeMatch = (isFloatingPoint!T && (node.isInt || node.isFloat)) || 2023 (isIntegral!T && node.isInt) || 2024 (is(Unqual!T==bool) && node.isBool) || 2025 (isSomeString!T && node.isString) || 2026 (node.isType!T); 2027 if(typeMatch && *node == index) 2028 { 2029 return idx; 2030 } 2031 } 2032 return -1; 2033 } 2034 2035 // Check if index is integral and in range. 2036 void checkSequenceIndex(T)(T index) const @trusted 2037 { 2038 assert(isSequence, 2039 "checkSequenceIndex() called on a " ~ nodeTypeString ~ " node"); 2040 2041 static if(!isIntegral!T) 2042 { 2043 throw new Error("Indexing a sequence with a non-integral type.", startMark_); 2044 } 2045 else 2046 { 2047 enforce(index >= 0 && index < value_.get!(const Node[]).length, 2048 new Error("Sequence index out of range: " ~ to!string(index), 2049 startMark_)); 2050 } 2051 } 2052 2053 // Const version of opIndex. 2054 ref const(Node) indexConst(T)(T index) const @trusted 2055 { 2056 if(isSequence) 2057 { 2058 checkSequenceIndex(index); 2059 static if(isIntegral!T) 2060 { 2061 return value_.get!(const Node[])[index]; 2062 } 2063 assert(false); 2064 } 2065 else if(isMapping) 2066 { 2067 auto idx = findPair(index); 2068 if(idx >= 0) 2069 { 2070 return value_.get!(const Pair[])[idx].value; 2071 } 2072 2073 string msg = "Mapping index not found" ~ (isSomeString!T ? ": " ~ to!string(index) : ""); 2074 throw new Error(msg, startMark_); 2075 } 2076 throw new Error("Trying to index a " ~ nodeTypeString ~ " node", startMark_); 2077 } 2078 } 2079 2080 package: 2081 // Merge a pair into an array of pairs based on merge rules in the YAML spec. 2082 // 2083 // The new pair will only be added if there is not already a pair 2084 // with the same key. 2085 // 2086 // Params: pairs = Appender managing the array of pairs to merge into. 2087 // toMerge = Pair to merge. 2088 void merge(ref Appender!(Node.Pair[]) pairs, ref Node.Pair toMerge) @trusted 2089 { 2090 foreach(ref pair; pairs.data) 2091 { 2092 if(pair.key == toMerge.key){return;} 2093 } 2094 pairs.put(toMerge); 2095 } 2096 2097 // Merge pairs into an array of pairs based on merge rules in the YAML spec. 2098 // 2099 // Any new pair will only be added if there is not already a pair 2100 // with the same key. 2101 // 2102 // Params: pairs = Appender managing the array of pairs to merge into. 2103 // toMerge = Pairs to merge. 2104 void merge(ref Appender!(Node.Pair[]) pairs, Node.Pair[] toMerge) @trusted 2105 { 2106 bool eq(ref Node.Pair a, ref Node.Pair b){return a.key == b.key;} 2107 2108 foreach(ref pair; toMerge) if(!canFind!eq(pairs.data, pair)) 2109 { 2110 pairs.put(pair); 2111 } 2112 }