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