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