Added various node manipulation functions so e.g. new elements
can be added to collections before emitting. The code is somewhat ugly, might be revised later.
This commit is contained in:
parent
b0f2ce201a
commit
967fe8c48b
483
dyaml/node.d
483
dyaml/node.d
|
@ -5,7 +5,8 @@
|
|||
// http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
/**
|
||||
* Node of a YAML document. Used to read YAML data once it's loaded.
|
||||
* Node of a YAML document. Used to read YAML data once it's loaded,
|
||||
* and to prepare data to emit.
|
||||
*/
|
||||
module dyaml.node;
|
||||
|
||||
|
@ -109,6 +110,15 @@ struct Node
|
|||
Node value;
|
||||
|
||||
public:
|
||||
///Construct a Pair from two values. Will be converted to Nodes if needed.
|
||||
this(K, V)(K key, V value)
|
||||
{
|
||||
static if(is(K == Node)){this.key = key;}
|
||||
else {this.key = Node(key);}
|
||||
static if(is(V == Node)){this.value = value;}
|
||||
else {this.value = Node(value);}
|
||||
}
|
||||
|
||||
///Equality test with another Pair.
|
||||
bool equals(ref Pair rhs)
|
||||
{
|
||||
|
@ -160,37 +170,34 @@ struct Node
|
|||
* what Representer determines. Can be used when a
|
||||
* single D data type needs to use multiple YAML tags.
|
||||
*/
|
||||
this(T)(T value, string tag = null) if (!isArray!T && !isAssociativeArray!T)
|
||||
this(T)(T value, in string tag = null) if (isSomeString!T ||
|
||||
(!isArray!T && !isAssociativeArray!T))
|
||||
{
|
||||
tag_ = Tag(tag);
|
||||
|
||||
//No copyconstruction.
|
||||
static if(is(T == Node))
|
||||
{
|
||||
static assert(false);
|
||||
}
|
||||
static assert(!is(T == Node));
|
||||
|
||||
//We can easily convert ints, floats, strings.
|
||||
else static if(isIntegral!T)
|
||||
{
|
||||
value_ = Value(cast(long) value);
|
||||
}
|
||||
else static if(isFloatingPoint!T)
|
||||
{
|
||||
value_ = Value(cast(real) value);
|
||||
}
|
||||
else static if(isSomeString!T)
|
||||
{
|
||||
value_ = Value(to!string(value));
|
||||
}
|
||||
//Directly supported type.
|
||||
else static if(Value.allowed!T)
|
||||
{
|
||||
value_ = Value(value);
|
||||
}
|
||||
static if(isIntegral!T) {value_ = Value(cast(long) value);}
|
||||
else static if(isFloatingPoint!T){value_ = Value(cast(real) value);}
|
||||
else static if(isSomeString!T) {value_ = Value(to!string(value));}
|
||||
//Other directly supported type.
|
||||
else static if(Value.allowed!T) {value_ = Value(value);}
|
||||
//User defined type.
|
||||
else
|
||||
else {value_ = userValue(value);}
|
||||
}
|
||||
unittest
|
||||
{
|
||||
with(Node(42))
|
||||
{
|
||||
value_ = userValue(value);
|
||||
assert(isScalar() && !isSequence && !isMapping && !isUserType);
|
||||
assert(get!int == 42 && get!float == 42.0f && get!string == "42");
|
||||
assert(!isUserType());
|
||||
}
|
||||
with(Node(new class{int a = 5;}))
|
||||
{
|
||||
assert(isUserType());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,7 +218,7 @@ struct Node
|
|||
* which both are internally represented as an array_
|
||||
* of nodes.
|
||||
*/
|
||||
this(T)(T[] array, string tag = null)
|
||||
this(T)(T[] array, in string tag = null) if (!isSomeString!(T[]))
|
||||
{
|
||||
tag_ = Tag(tag);
|
||||
|
||||
|
@ -222,13 +229,19 @@ struct Node
|
|||
else
|
||||
{
|
||||
Node[] nodes;
|
||||
foreach(ref value; array)
|
||||
{
|
||||
nodes ~= Node(value);
|
||||
}
|
||||
foreach(ref value; array){nodes ~= Node(value);}
|
||||
value_ = Value(nodes);
|
||||
}
|
||||
}
|
||||
unittest
|
||||
{
|
||||
with(Node([1, 2, 3]))
|
||||
{
|
||||
assert(!isScalar() && isSequence && !isMapping && !isUserType);
|
||||
assert(length == 3);
|
||||
assert(opIndex(2).get!int == 3);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a node from an associative array_.
|
||||
|
@ -246,24 +259,26 @@ struct Node
|
|||
* (!!omap) and pairs (!!pairs), which are all
|
||||
* internally represented as an array_ of node pairs.
|
||||
*/
|
||||
this(K, V)(V[K] array, string tag = null)
|
||||
this(K, V)(V[K] array, in string tag = null)
|
||||
{
|
||||
tag_ = Tag(tag);
|
||||
|
||||
Node.Pair[] pairs;
|
||||
|
||||
Node.Pair pair;
|
||||
foreach(ref key, ref value; array)
|
||||
{
|
||||
static if(is(K == Node)){pair.key = key;}
|
||||
else{pair.key = Node(key);}
|
||||
static if(is(V == Node)){pair.value = value;}
|
||||
else{pair.value = Node(value);}
|
||||
pairs ~= pair;
|
||||
}
|
||||
|
||||
foreach(ref key, ref value; array){pairs ~= Pair(key, value);}
|
||||
value_ = Value(pairs);
|
||||
}
|
||||
unittest
|
||||
{
|
||||
int[string] aa;
|
||||
aa["1"] = 1;
|
||||
aa["2"] = 2;
|
||||
with(Node(aa))
|
||||
{
|
||||
assert(!isScalar() && !isSequence && isMapping && !isUserType);
|
||||
assert(length == 2);
|
||||
assert(opIndex("2").get!int == 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a node from arrays of keys_ and values_.
|
||||
|
@ -288,7 +303,7 @@ struct Node
|
|||
* (!!omap) and pairs (!!pairs), which are all
|
||||
* internally represented as an array_ of node pairs.
|
||||
*/
|
||||
this(K, V)(K[] keys, V[] values, string tag = null)
|
||||
this(K, V)(K[] keys, V[] values, in string tag = null)
|
||||
in
|
||||
{
|
||||
assert(keys.length == values.length,
|
||||
|
@ -300,19 +315,18 @@ struct Node
|
|||
tag_ = Tag(tag);
|
||||
|
||||
Node.Pair[] pairs;
|
||||
|
||||
Node.Pair pair;
|
||||
foreach(i; 0 .. keys.length)
|
||||
{
|
||||
static if(is(K == Node)){pair.key = keys[i];}
|
||||
else{pair.key = Node(keys[i]);}
|
||||
static if(is(V == Node)){pair.value = values[i];}
|
||||
else{pair.value = Node(values[i]);}
|
||||
pairs ~= pair;
|
||||
}
|
||||
|
||||
foreach(i; 0 .. keys.length){pairs ~= Pair(keys[i], values[i]);}
|
||||
value_ = Value(pairs);
|
||||
}
|
||||
unittest
|
||||
{
|
||||
with(Node(["1", "2"], [1, 2]))
|
||||
{
|
||||
assert(!isScalar() && !isSequence && isMapping && !isUserType);
|
||||
assert(length == 2);
|
||||
assert(opIndex("2").get!int == 2);
|
||||
}
|
||||
}
|
||||
|
||||
///Is this node valid (initialized)?
|
||||
@property bool isValid() const {return value_.hasValue;}
|
||||
|
@ -509,47 +523,23 @@ struct Node
|
|||
*
|
||||
* Returns: Value corresponding to the index.
|
||||
*
|
||||
* Throws: NodeException if the index could not be found.
|
||||
* Throws: NodeException if the index could not be found,
|
||||
* non-integral index is used with a sequence or the node is
|
||||
* not a collection.
|
||||
*/
|
||||
Node opIndex(T)(in T index)
|
||||
Node opIndex(T)(T index)
|
||||
{
|
||||
if(isSequence)
|
||||
{
|
||||
//Sequence, index must be integral.
|
||||
static if(isIntegral!T)
|
||||
{
|
||||
auto nodes = value_.get!(Node[]);
|
||||
enforce(index >= 0 && index < nodes.length,
|
||||
new NodeException("Index to a sequence out of range: "
|
||||
~ to!string(index), startMark_));
|
||||
return nodes[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NodeException("Indexing a sequence with a non-integer type.",
|
||||
startMark_);
|
||||
}
|
||||
checkSequenceIndex(index);
|
||||
static if(isIntegral!T){return value_.get!(Node[])[index];}
|
||||
assert(false);
|
||||
}
|
||||
else if(isMapping)
|
||||
{
|
||||
//Mapping, look for keys convertible to T with value of index.
|
||||
foreach(ref pair; get!(Pair[]))
|
||||
{
|
||||
//Handle NaN.
|
||||
static if(isFloatingPoint!T)
|
||||
{
|
||||
if(isFloat && isNaN(index) && isNaN(pair.key.get!real))
|
||||
{
|
||||
return pair.value;
|
||||
}
|
||||
}
|
||||
//If we can get the key as type T, get it and compare to
|
||||
//index, and return value if the key matches.
|
||||
if(pair.key.convertsTo!T && pair.key.get!T == index)
|
||||
{
|
||||
return pair.value;
|
||||
}
|
||||
}
|
||||
auto idx = findPair(index);
|
||||
if(idx >= 0){return get!(Pair[])[idx].value;}
|
||||
|
||||
throw new NodeException("Mapping index not found" ~
|
||||
isSomeString!T ? ": " ~ to!string(index) : "",
|
||||
startMark_);
|
||||
|
@ -586,6 +576,77 @@ struct Node
|
|||
assert(null !is collectException(nmap["42"]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set element at the specified index of a collection.
|
||||
*
|
||||
* This method can only be called on collection nodes.
|
||||
*
|
||||
* If the node is a sequence, index must be integral.
|
||||
*
|
||||
* If the node is a mapping, set the value_ corresponding to the first
|
||||
* key matching index (including conversion, so e.g. "42" matches 42).
|
||||
*
|
||||
* If the node is a mapping and no key matches index, a new key-value
|
||||
* pair is added to the mapping. With sequences the index must be in
|
||||
* range. This ensures behavior siilar to D arrays and associative
|
||||
* arrays.
|
||||
*
|
||||
* Params: index = Index of the value to set.
|
||||
*
|
||||
* Throws: NodeException if the node is not a collection, index is out
|
||||
* of range or if a non-integral index is used on a sequence node.
|
||||
*/
|
||||
void opIndexAssign(K, V)(V value, K index)
|
||||
{
|
||||
if(isSequence())
|
||||
{
|
||||
//This ensures K is integral.
|
||||
checkSequenceIndex(index);
|
||||
static if(isIntegral!K)
|
||||
{
|
||||
auto nodes = value_.get!(Node[]);
|
||||
static if(is(V == Node)){nodes[index] = value;}
|
||||
else {nodes[index] = Node(value);}
|
||||
value_ = Value(nodes);
|
||||
return;
|
||||
}
|
||||
assert(false);
|
||||
}
|
||||
else if(isMapping())
|
||||
{
|
||||
auto idx = findPair(index);
|
||||
if(idx < 0){add(index, value);}
|
||||
else
|
||||
{
|
||||
auto pairs = get!(Node.Pair[])();
|
||||
static if(is(V == Node)){pairs[idx].value = value;}
|
||||
else {pairs[idx].value = Node(value);}
|
||||
value_ = Value(pairs);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
throw new NodeException("Trying to index a YAML node that is not a collection.",
|
||||
startMark_);
|
||||
}
|
||||
unittest
|
||||
{
|
||||
with(Node([1, 2, 3, 4, 3]))
|
||||
{
|
||||
opIndexAssign(42, 3);
|
||||
assert(length == 5);
|
||||
assert(opIndex(3).get!int == 42);
|
||||
}
|
||||
with(Node(["1", "2", "3"], [4, 5, 6]))
|
||||
{
|
||||
opIndexAssign(42, "3");
|
||||
opIndexAssign(123, 456);
|
||||
assert(length == 4);
|
||||
assert(opIndex("3").get!int == 42);
|
||||
assert(opIndex(456).get!int == 123);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over a sequence, getting each element as T.
|
||||
*
|
||||
|
@ -737,6 +798,199 @@ struct Node
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an element to a sequence node.
|
||||
*
|
||||
* This method can only be called on sequence nodes.
|
||||
*
|
||||
* If value is a node, it is copied to the sequence directly. Otherwise
|
||||
* value is converted to a node and then stored in the sequence.
|
||||
*
|
||||
* When emitting, all values in the sequence will be emitted. If using
|
||||
* the !!set tag, the user needs to ensure that all elements in the
|
||||
* sequence are unique, otherwise invalid YAML code will be emitted.
|
||||
*
|
||||
* Params: value = Value to add to the sequence.
|
||||
*/
|
||||
void add(T)(T value)
|
||||
{
|
||||
enforce(isSequence(),
|
||||
new NodeException("Trying to add an element to a "
|
||||
"non-sequence YAML node", startMark_));
|
||||
|
||||
auto nodes = get!(Node[])();
|
||||
static if(is(T == Node)){nodes ~= value;}
|
||||
else {nodes ~= Node(value);}
|
||||
value_ = Value(nodes);
|
||||
}
|
||||
unittest
|
||||
{
|
||||
with(Node([1, 2, 3, 4]))
|
||||
{
|
||||
add(5.0f);
|
||||
assert(opIndex(4).get!float == 5.0f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a key-value pair to a mapping node.
|
||||
*
|
||||
* This method can only be called on mapping nodes.
|
||||
*
|
||||
* If key and/or value is a node, it is copied to the mapping directly.
|
||||
* Otherwise it is converted to a node and then stored in the mapping.
|
||||
*
|
||||
* It is possible to for the same key to be present more than once in a
|
||||
* mapping. When emitting, all key-value pairs will be emitted.
|
||||
* This is useful with the !!pairs tag, but will result in invalid YAML
|
||||
* with !!map and !!omap tags.
|
||||
*
|
||||
* Params: key = Key to add.
|
||||
* value = Value to add.
|
||||
*/
|
||||
void add(K, V)(K key, V value)
|
||||
{
|
||||
enforce(isMapping(),
|
||||
new NodeException("Trying to add a key-value pair to a "
|
||||
"non-mapping YAML node", startMark_));
|
||||
|
||||
auto pairs = get!(Node.Pair[])();
|
||||
pairs ~= Pair(key, value);
|
||||
value_ = Value(pairs);
|
||||
}
|
||||
unittest
|
||||
{
|
||||
with(Node([1, 2], [3, 4]))
|
||||
{
|
||||
add(5, "6");
|
||||
assert(opIndex(5).get!string == "6");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove first (if any) occurence of a value in a collection.
|
||||
*
|
||||
* This method can only be called on collection nodes.
|
||||
*
|
||||
* If the node is a sequence, the first node matching value (including
|
||||
* conversion, so e.g. "42" matches 42) is removed.
|
||||
* If the node is a mapping, the first key-value pair where value_
|
||||
* matches value is removed.
|
||||
*
|
||||
* Params: value = Value to remove.
|
||||
*
|
||||
* Throws: NodeException if the node is not a collection.
|
||||
*/
|
||||
void remove(T)(T value)
|
||||
{
|
||||
if(isSequence())
|
||||
{
|
||||
foreach(idx, ref elem; get!(Node[]))
|
||||
{
|
||||
if(elem.convertsTo!T && elem.get!T == value)
|
||||
{
|
||||
removeAt(idx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if(isMapping())
|
||||
{
|
||||
auto idx = findPair!(T, true)(value);
|
||||
if(idx >= 0)
|
||||
{
|
||||
auto pairs = get!(Node.Pair[])();
|
||||
copy(pairs[idx + 1 .. $], pairs[idx .. $ - 1]);
|
||||
pairs.length = pairs.length - 1;
|
||||
value_ = Value(pairs);
|
||||
}
|
||||
return;
|
||||
}
|
||||
throw new NodeException("Trying to remove an element from a YAML node that "
|
||||
"is not a collection.", startMark_);
|
||||
}
|
||||
unittest
|
||||
{
|
||||
with(Node([1, 2, 3, 4, 3]))
|
||||
{
|
||||
remove(3);
|
||||
assert(length == 4);
|
||||
assert(opIndex(2).get!int == 4);
|
||||
assert(opIndex(3).get!int == 3);
|
||||
}
|
||||
with(Node(["1", "2", "3"], [4, 5, 6]))
|
||||
{
|
||||
remove(4);
|
||||
assert(length == 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove element at the specified index of a collection.
|
||||
*
|
||||
* This method can only be called on collection nodes.
|
||||
*
|
||||
* If the node is a sequence, index must be integral.
|
||||
*
|
||||
* If the node is a mapping, remove the first key-value pair where
|
||||
* key matches index (including conversion, so e.g. "42" matches 42).
|
||||
*
|
||||
* If the node is a mapping and no key matches index, nothing is removed
|
||||
* and no exception is thrown. This ensures behavior siilar to D arrays
|
||||
* and associative arrays.
|
||||
*
|
||||
* Params: index = Index to remove at.
|
||||
*
|
||||
* Throws: NodeException if the node is not a collection, index is out
|
||||
* of range or if a non-integral index is used on a sequence node.
|
||||
*/
|
||||
void removeAt(T)(T index)
|
||||
{
|
||||
if(isSequence())
|
||||
{
|
||||
//This ensures T is integral.
|
||||
checkSequenceIndex(index);
|
||||
static if(isIntegral!T)
|
||||
{
|
||||
auto nodes = value_.get!(Node[]);
|
||||
copy(nodes[index + 1 .. $], nodes[index .. $ - 1]);
|
||||
nodes.length = nodes.length - 1;
|
||||
value_ = Value(nodes);
|
||||
return;
|
||||
}
|
||||
assert(false);
|
||||
}
|
||||
else if(isMapping())
|
||||
{
|
||||
auto idx = findPair(index);
|
||||
if(idx >= 0)
|
||||
{
|
||||
auto pairs = get!(Node.Pair[])();
|
||||
copy(pairs[idx + 1 .. $], pairs[idx .. $ - 1]);
|
||||
pairs.length = pairs.length - 1;
|
||||
value_ = Value(pairs);
|
||||
}
|
||||
return;
|
||||
}
|
||||
throw new NodeException("Trying to remove an element from a YAML node that "
|
||||
"is not a collection.", startMark_);
|
||||
}
|
||||
unittest
|
||||
{
|
||||
with(Node([1, 2, 3, 4, 3]))
|
||||
{
|
||||
removeAt(3);
|
||||
assert(length == 4);
|
||||
assert(opIndex(3).get!int == 3);
|
||||
}
|
||||
with(Node(["1", "2", "3"], [4, 5, 6]))
|
||||
{
|
||||
removeAt("2");
|
||||
assert(length == 2);
|
||||
}
|
||||
}
|
||||
|
||||
package:
|
||||
/*
|
||||
* Construct a node from raw data.
|
||||
|
@ -883,13 +1137,13 @@ struct Node
|
|||
*/
|
||||
@property bool isType(T)() const {return value_.type is typeid(T);}
|
||||
|
||||
///Is the value an integer of some kind?
|
||||
//Is the value an integer of some kind?
|
||||
alias isType!long isInt;
|
||||
|
||||
///Is the value a floating point number of some kind?
|
||||
//Is the value a floating point number of some kind?
|
||||
alias isType!real isFloat;
|
||||
|
||||
///Does given node have the same type as this node?
|
||||
//Does given node have the same type as this node?
|
||||
bool hasEqualType(ref Node node)
|
||||
{
|
||||
return value_.type is node.value_.type;
|
||||
|
@ -913,6 +1167,53 @@ struct Node
|
|||
else static if(isIntegral!T) {return isInt();}
|
||||
else {return false;}
|
||||
}
|
||||
|
||||
//Get index of pair with key (or value, if value is true) matching index.
|
||||
long findPair(T, bool value = false)(const ref T index)
|
||||
{
|
||||
auto pairs = get!(Node.Pair[])();
|
||||
Node* node;
|
||||
foreach(idx, ref pair; pairs)
|
||||
{
|
||||
static if(value){node = &pair.value;}
|
||||
else{node = &pair.key;}
|
||||
|
||||
static if(is(T == Node))
|
||||
{
|
||||
if(*node == index){return idx;}
|
||||
}
|
||||
else static if(isFloatingPoint!T)
|
||||
{
|
||||
//Need to handle NaNs separately.
|
||||
if((node.get!T == index) ||
|
||||
(isFloat && isNaN(index) && isNaN(node.get!real)))
|
||||
{
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(node.get!T == index){return idx;}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
///Check if index is integral and in range.
|
||||
void checkSequenceIndex(T)(T index)
|
||||
{
|
||||
static if(!isIntegral!T)
|
||||
{
|
||||
throw new NodeException("Indexing a YAML sequence with a non-integral type.",
|
||||
startMark_);
|
||||
}
|
||||
else
|
||||
{
|
||||
enforce(index >= 0 && index < value_.get!(Node[]).length,
|
||||
new NodeException("Index to a YAML sequence out of range: "
|
||||
~ to!string(index), startMark_));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
package:
|
||||
|
|
Loading…
Reference in a new issue