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 /**
8  * YAML serializer.
9  * Code based on PyYAML: http://www.pyyaml.org
10  */
11 module dyaml.serializer;
12 
13 
14 import std.array;
15 import std.format;
16 import std.typecons;
17 
18 import dyaml.anchor;
19 import dyaml.emitter;
20 import dyaml.encoding;
21 import dyaml.event;
22 import dyaml.exception;
23 import dyaml.node;
24 import dyaml.resolver;
25 import dyaml.tag;
26 import dyaml.tagdirective;
27 import dyaml.token;
28 
29 
30 package:
31 
32 ///Serializes represented YAML nodes, generating events which are then emitted by Emitter.
33 struct Serializer
34 {
35     private:
36         ///Emitter to emit events produced.
37         Emitter* emitter_;
38         ///Resolver used to determine which tags are automaticaly resolvable.
39         Resolver resolver_;
40 
41         ///Do all document starts have to be specified explicitly?
42         Flag!"explicitStart" explicitStart_;
43         ///Do all document ends have to be specified explicitly?
44         Flag!"explicitEnd" explicitEnd_;
45         ///YAML version string.
46         string YAMLVersion_;
47 
48         ///Tag directives to emit.
49         TagDirective[] tagDirectives_;
50 
51         //TODO Use something with more deterministic memory usage.
52         ///Nodes with assigned anchors.
53         Anchor[Node] anchors_;
54         ///Nodes with assigned anchors that are already serialized.
55         bool[Node] serializedNodes_;
56         ///ID of the last anchor generated.
57         uint lastAnchorID_ = 0;
58 
59     public:
60         /**
61          * Construct a Serializer.
62          *
63          * Params:  emitter       = Emitter to emit events produced.
64          *          resolver      = Resolver used to determine which tags are automaticaly resolvable. 
65          *          encoding      = Character encoding to use.
66          *          explicitStart = Do all document starts have to be specified explicitly? 
67          *          explicitEnd   = Do all document ends have to be specified explicitly? 
68          *          YAMLVersion   = YAML version string. 
69          *          tagDirectives = Tag directives to emit. 
70          */
71         this(ref Emitter emitter, Resolver resolver, Encoding encoding,
72              const Flag!"explicitStart" explicitStart, 
73              const Flag!"explicitEnd" explicitEnd, string YAMLVersion, 
74              TagDirective[] tagDirectives) @trusted
75         {
76             emitter_       = &emitter;
77             resolver_      = resolver;
78             explicitStart_ = explicitStart;
79             explicitEnd_   = explicitEnd;
80             YAMLVersion_   = YAMLVersion;
81             tagDirectives_ = tagDirectives;
82 
83             emitter_.emit(streamStartEvent(Mark(), Mark(), encoding));
84         }
85 
86         ///Destroy the Serializer.
87         @safe ~this()
88         {
89             emitter_.emit(streamEndEvent(Mark(), Mark()));
90             YAMLVersion_.destroy();
91             YAMLVersion_ = null;
92             serializedNodes_.destroy();
93             serializedNodes_ = null;
94             anchors_.destroy();
95             anchors_ = null;
96         }
97 
98         ///Serialize a node, emitting it in the process.
99         void serialize(ref Node node) @safe
100         {
101             emitter_.emit(documentStartEvent(Mark(), Mark(), explicitStart_, 
102                                              YAMLVersion_, tagDirectives_));
103             anchorNode(node);
104             serializeNode(node);
105             emitter_.emit(documentEndEvent(Mark(), Mark(), explicitEnd_));
106             serializedNodes_.destroy();
107             anchors_.destroy();
108             Anchor[Node] emptyAnchors;
109             anchors_ = emptyAnchors;
110             lastAnchorID_ = 0;
111         }
112 
113     private:
114         /**
115          * Determine if it's a good idea to add an anchor to a node.
116          *
117          * Used to prevent associating every single repeating scalar with an 
118          * anchor/alias - only nodes long enough can use anchors.
119          *
120          * Params:  node = Node to check for anchorability.
121          *
122          * Returns: True if the node is anchorable, false otherwise.
123          */
124         static bool anchorable(ref Node node) @safe 
125         {
126             if(node.isScalar)
127             {
128                 return node.isType!string    ? node.as!string.length > 64 :
129                        node.isType!(ubyte[]) ? node.as!(ubyte[]).length > 64:
130                                                false;
131             }
132             return node.length > 2;
133         }
134 
135         ///Add an anchor to the node if it's anchorable and not anchored yet.
136         void anchorNode(ref Node node) @safe
137         {
138             if(!anchorable(node)){return;}
139 
140             if((node in anchors_) !is null)
141             {
142                 if(anchors_[node].isNull())
143                 {
144                     anchors_[node] = generateAnchor();
145                 }
146                 return;
147             }
148 
149             anchors_[node] = Anchor(null);
150             if(node.isSequence) foreach(ref Node item; node)
151             {
152                 anchorNode(item);
153             }
154             else if(node.isMapping) foreach(ref Node key, ref Node value; node)
155             {
156                 anchorNode(key);
157                 anchorNode(value);
158             }
159         }
160 
161         ///Generate and return a new anchor.
162         Anchor generateAnchor() @trusted
163         {
164             ++lastAnchorID_;
165             auto appender = appender!string();
166             formattedWrite(appender, "id%03d", lastAnchorID_);
167             return Anchor(appender.data);
168         }
169 
170         ///Serialize a node and all its subnodes.
171         void serializeNode(ref Node node) @trusted
172         {
173             //If the node has an anchor, emit an anchor (as aliasEvent) on the 
174             //first occurrence, save it in serializedNodes_, and emit an alias 
175             //if it reappears.
176             Anchor aliased = Anchor(null);
177             if(anchorable(node) && (node in anchors_) !is null)
178             {
179                 aliased = anchors_[node];
180                 if((node in serializedNodes_) !is null)
181                 {
182                     emitter_.emit(aliasEvent(Mark(), Mark(), aliased));
183                     return;
184                 }
185                 serializedNodes_[node] = true;
186             }
187 
188             if(node.isScalar)
189             {
190                 assert(node.isType!string, "Scalar node type must be string before serialized");
191                 auto value = node.as!string;
192                 const detectedTag = resolver_.resolve(NodeID.Scalar, Tag(null), value, true);
193                 const defaultTag = resolver_.resolve(NodeID.Scalar, Tag(null), value, false);
194                 bool isDetected = node.tag_ == detectedTag;
195                 bool isDefault = node.tag_ == defaultTag;
196 
197                 emitter_.emit(scalarEvent(Mark(), Mark(), aliased, node.tag_,
198                               tuple(isDetected, isDefault), value, node.scalarStyle));
199                 return;
200             }
201             if(node.isSequence)
202             {
203                 const defaultTag = resolver_.defaultSequenceTag;
204                 const implicit = node.tag_ == defaultTag;
205                 emitter_.emit(sequenceStartEvent(Mark(), Mark(), aliased, node.tag_,
206                                                  implicit, node.collectionStyle));
207                 foreach(ref Node item; node)
208                 {
209                     serializeNode(item);
210                 }
211                 emitter_.emit(sequenceEndEvent(Mark(), Mark()));
212                 return;
213             }
214             if(node.isMapping)
215             {
216                 const defaultTag = resolver_.defaultMappingTag; 
217                 const implicit = node.tag_ == defaultTag;
218                 emitter_.emit(mappingStartEvent(Mark(), Mark(), aliased, node.tag_,
219                                                 implicit, node.collectionStyle));
220                 foreach(ref Node key, ref Node value; node)
221                 { 
222                     serializeNode(key);
223                     serializeNode(value);
224                 }
225                 emitter_.emit(mappingEndEvent(Mark(), Mark()));
226                 return;
227             }
228             assert(false, "This code should never be reached");
229         }
230 }