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