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