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