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