dyaml/source/dyaml/serializer.d

323 lines
11 KiB
D

// Copyright Ferdinand Majerech 2011.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
/**
* YAML serializer.
* Code based on PyYAML: http://www.pyyaml.org
*/
module dyaml.serializer;
import std.array;
import std.format;
import std.typecons;
import dyaml.emitter;
import dyaml.event;
import dyaml.exception;
import dyaml.node;
import dyaml.resolver;
import dyaml.tagdirective;
import dyaml.token;
package:
///Serializes represented YAML nodes, generating events which are then emitted by Emitter.
struct Serializer
{
private:
///Resolver used to determine which tags are automaticaly resolvable.
Resolver resolver_;
///Do all document starts have to be specified explicitly?
Flag!"explicitStart" explicitStart_;
///Do all document ends have to be specified explicitly?
Flag!"explicitEnd" explicitEnd_;
///YAML version string.
string YAMLVersion_;
///Tag directives to emit.
TagDirective[] tagDirectives_;
//TODO Use something with more deterministic memory usage.
///Nodes with assigned anchors.
string[Node] anchors_;
///Nodes with assigned anchors that are already serialized.
bool[Node] serializedNodes_;
///ID of the last anchor generated.
uint lastAnchorID_ = 0;
public:
/**
* Construct a Serializer.
*
* Params:
* resolver = Resolver used to determine which tags are automaticaly resolvable.
* explicitStart = Do all document starts have to be specified explicitly?
* explicitEnd = Do all document ends have to be specified explicitly?
* YAMLVersion = YAML version string.
* tagDirectives = Tag directives to emit.
*/
this(Resolver resolver,
const Flag!"explicitStart" explicitStart,
const Flag!"explicitEnd" explicitEnd, string YAMLVersion,
TagDirective[] tagDirectives) @safe
{
resolver_ = resolver;
explicitStart_ = explicitStart;
explicitEnd_ = explicitEnd;
YAMLVersion_ = YAMLVersion;
tagDirectives_ = tagDirectives;
}
///Begin the stream.
void startStream(EmitterT)(ref EmitterT emitter) @safe
{
emitter.emit(streamStartEvent(Mark(), Mark()));
}
///End the stream.
void endStream(EmitterT)(ref EmitterT emitter) @safe
{
emitter.emit(streamEndEvent(Mark(), Mark()));
}
///Serialize a node, emitting it in the process.
void serialize(EmitterT)(ref EmitterT emitter, ref Node node) @safe
{
emitter.emit(documentStartEvent(Mark(), Mark(), explicitStart_,
YAMLVersion_, tagDirectives_));
anchorNode(node);
serializeNode(emitter, node);
emitter.emit(documentEndEvent(Mark(), Mark(), explicitEnd_));
serializedNodes_.destroy();
anchors_.destroy();
string[Node] emptyAnchors;
anchors_ = emptyAnchors;
lastAnchorID_ = 0;
}
private:
/**
* Determine if it's a good idea to add an anchor to a node.
*
* Used to prevent associating every single repeating scalar with an
* anchor/alias - only nodes long enough can use anchors.
*
* Params: node = Node to check for anchorability.
*
* Returns: True if the node is anchorable, false otherwise.
*/
static bool anchorable(ref Node node) @safe
{
if(node.nodeID == NodeID.scalar)
{
return (node.type == NodeType.string) ? node.as!string.length > 64 :
(node.type == NodeType.binary) ? node.as!(ubyte[]).length > 64 :
false;
}
return node.length > 2;
}
@safe unittest
{
import std.string : representation;
auto shortString = "not much";
auto longString = "A fairly long string that would be a good idea to add an anchor to";
auto node1 = Node(shortString);
auto node2 = Node(shortString.representation.dup);
auto node3 = Node(longString);
auto node4 = Node(longString.representation.dup);
auto node5 = Node([node1]);
auto node6 = Node([node1, node2, node3, node4]);
assert(!anchorable(node1));
assert(!anchorable(node2));
assert(anchorable(node3));
assert(anchorable(node4));
assert(!anchorable(node5));
assert(anchorable(node6));
}
///Add an anchor to the node if it's anchorable and not anchored yet.
void anchorNode(ref Node node) @safe
{
if(!anchorable(node)){return;}
if((node in anchors_) !is null)
{
if(anchors_[node] is null)
{
anchors_[node] = generateAnchor();
}
return;
}
anchors_.remove(node);
final switch (node.nodeID)
{
case NodeID.mapping:
foreach(ref Node key, ref Node value; node)
{
anchorNode(key);
anchorNode(value);
}
break;
case NodeID.sequence:
foreach(ref Node item; node)
{
anchorNode(item);
}
break;
case NodeID.invalid:
assert(0);
case NodeID.scalar:
}
}
///Generate and return a new anchor.
string generateAnchor() @safe
{
++lastAnchorID_;
auto appender = appender!string();
formattedWrite(appender, "id%03d", lastAnchorID_);
return appender.data;
}
///Serialize a node and all its subnodes.
void serializeNode(EmitterT)(ref EmitterT emitter, ref Node node) @safe
{
//If the node has an anchor, emit an anchor (as aliasEvent) on the
//first occurrence, save it in serializedNodes_, and emit an alias
//if it reappears.
string aliased;
if(anchorable(node) && (node in anchors_) !is null)
{
aliased = anchors_[node];
if((node in serializedNodes_) !is null)
{
emitter.emit(aliasEvent(Mark(), Mark(), aliased));
return;
}
serializedNodes_[node] = true;
}
final switch (node.nodeID)
{
case NodeID.mapping:
const defaultTag = resolver_.defaultMappingTag;
const implicit = node.tag_ == defaultTag;
emitter.emit(mappingStartEvent(Mark(), Mark(), aliased, node.tag_,
implicit, node.collectionStyle));
foreach(ref Node key, ref Node value; node)
{
serializeNode(emitter, key);
serializeNode(emitter, value);
}
emitter.emit(mappingEndEvent(Mark(), Mark()));
return;
case NodeID.sequence:
const defaultTag = resolver_.defaultSequenceTag;
const implicit = node.tag_ == defaultTag;
emitter.emit(sequenceStartEvent(Mark(), Mark(), aliased, node.tag_,
implicit, node.collectionStyle));
foreach(ref Node item; node)
{
serializeNode(emitter, item);
}
emitter.emit(sequenceEndEvent(Mark(), Mark()));
return;
case NodeID.scalar:
assert(node.type == NodeType.string, "Scalar node type must be string before serialized");
auto value = node.as!string;
const detectedTag = resolver_.resolve(NodeID.scalar, null, value, true);
const bool isDetected = node.tag_ == detectedTag;
emitter.emit(scalarEvent(Mark(), Mark(), aliased, node.tag_,
isDetected, value, node.scalarStyle));
return;
case NodeID.invalid:
assert(0);
}
}
}
// Issue #244
@safe unittest
{
import dyaml.dumper : dumper;
auto node = Node([
Node.Pair(
Node(""),
Node([
Node([
Node.Pair(
Node("d"),
Node([
Node([
Node.Pair(
Node("c"),
Node("")
),
Node.Pair(
Node("b"),
Node("")
),
Node.Pair(
Node(""),
Node("")
)
])
])
),
]),
Node([
Node.Pair(
Node("d"),
Node([
Node(""),
Node(""),
Node([
Node.Pair(
Node("c"),
Node("")
),
Node.Pair(
Node("b"),
Node("")
),
Node.Pair(
Node(""),
Node("")
)
])
])
),
Node.Pair(
Node("z"),
Node("")
),
Node.Pair(
Node(""),
Node("")
)
]),
Node("")
])
),
Node.Pair(
Node("g"),
Node("")
),
Node.Pair(
Node("h"),
Node("")
),
]);
auto stream = appender!string();
dumper().dump(stream, node);
}