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