231 lines
8.1 KiB
D
231 lines
8.1 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.anchor;
|
|
import dyaml.emitter;
|
|
import dyaml.encoding;
|
|
import dyaml.event;
|
|
import dyaml.exception;
|
|
import dyaml.node;
|
|
import dyaml.resolver;
|
|
import dyaml.tag;
|
|
import dyaml.tagdirective;
|
|
import dyaml.token;
|
|
|
|
|
|
package:
|
|
|
|
///Serializes represented YAML nodes, generating events which are then emitted by Emitter.
|
|
struct Serializer
|
|
{
|
|
private:
|
|
///Emitter to emit events produced.
|
|
Emitter* emitter_;
|
|
///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.
|
|
Anchor[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: emitter = Emitter to emit events produced.
|
|
* resolver = Resolver used to determine which tags are automaticaly resolvable.
|
|
* encoding = Character encoding to use.
|
|
* 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(ref Emitter emitter, Resolver resolver, Encoding encoding,
|
|
const Flag!"explicitStart" explicitStart,
|
|
const Flag!"explicitEnd" explicitEnd, string YAMLVersion,
|
|
TagDirective[] tagDirectives) @trusted
|
|
{
|
|
emitter_ = &emitter;
|
|
resolver_ = resolver;
|
|
explicitStart_ = explicitStart;
|
|
explicitEnd_ = explicitEnd;
|
|
YAMLVersion_ = YAMLVersion;
|
|
tagDirectives_ = tagDirectives;
|
|
|
|
emitter_.emit(streamStartEvent(Mark(), Mark(), encoding));
|
|
}
|
|
|
|
///Destroy the Serializer.
|
|
@safe ~this()
|
|
{
|
|
emitter_.emit(streamEndEvent(Mark(), Mark()));
|
|
YAMLVersion_.destroy();
|
|
YAMLVersion_ = null;
|
|
serializedNodes_.destroy();
|
|
serializedNodes_ = null;
|
|
anchors_.destroy();
|
|
anchors_ = null;
|
|
}
|
|
|
|
///Serialize a node, emitting it in the process.
|
|
void serialize(ref Node node) @safe
|
|
{
|
|
emitter_.emit(documentStartEvent(Mark(), Mark(), explicitStart_,
|
|
YAMLVersion_, tagDirectives_));
|
|
anchorNode(node);
|
|
serializeNode(node);
|
|
emitter_.emit(documentEndEvent(Mark(), Mark(), explicitEnd_));
|
|
serializedNodes_.destroy();
|
|
anchors_.destroy();
|
|
Anchor[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.isScalar)
|
|
{
|
|
return node.isType!string ? node.as!string.length > 64 :
|
|
node.isType!(ubyte[]) ? node.as!(ubyte[]).length > 64:
|
|
false;
|
|
}
|
|
return node.length > 2;
|
|
}
|
|
|
|
///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].isNull())
|
|
{
|
|
anchors_[node] = generateAnchor();
|
|
}
|
|
return;
|
|
}
|
|
|
|
anchors_[node] = Anchor(null);
|
|
if(node.isSequence) foreach(ref Node item; node)
|
|
{
|
|
anchorNode(item);
|
|
}
|
|
else if(node.isMapping) foreach(ref Node key, ref Node value; node)
|
|
{
|
|
anchorNode(key);
|
|
anchorNode(value);
|
|
}
|
|
}
|
|
|
|
///Generate and return a new anchor.
|
|
Anchor generateAnchor() @trusted
|
|
{
|
|
++lastAnchorID_;
|
|
auto appender = appender!string();
|
|
formattedWrite(appender, "id%03d", lastAnchorID_);
|
|
return Anchor(appender.data);
|
|
}
|
|
|
|
///Serialize a node and all its subnodes.
|
|
void serializeNode(ref Node node) @trusted
|
|
{
|
|
//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.
|
|
Anchor aliased = Anchor(null);
|
|
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;
|
|
}
|
|
|
|
if(node.isScalar)
|
|
{
|
|
assert(node.isType!string, "Scalar node type must be string before serialized");
|
|
auto value = node.as!string;
|
|
const detectedTag = resolver_.resolve(NodeID.Scalar, Tag(null), value, true);
|
|
const defaultTag = resolver_.resolve(NodeID.Scalar, Tag(null), value, false);
|
|
bool isDetected = node.tag_ == detectedTag;
|
|
bool isDefault = node.tag_ == defaultTag;
|
|
|
|
emitter_.emit(scalarEvent(Mark(), Mark(), aliased, node.tag_,
|
|
tuple(isDetected, isDefault), value, node.scalarStyle));
|
|
return;
|
|
}
|
|
if(node.isSequence)
|
|
{
|
|
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(item);
|
|
}
|
|
emitter_.emit(sequenceEndEvent(Mark(), Mark()));
|
|
return;
|
|
}
|
|
if(node.isMapping)
|
|
{
|
|
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(key);
|
|
serializeNode(value);
|
|
}
|
|
emitter_.emit(mappingEndEvent(Mark(), Mark()));
|
|
return;
|
|
}
|
|
assert(false, "This code should never be reached");
|
|
}
|
|
}
|