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.emitter;
19 import dyaml.event;
20 import dyaml.exception;
21 import dyaml.node;
22 import dyaml.resolver;
23 import dyaml.tagdirective;
24 import dyaml.token;
25 
26 
27 package:
28 
29 ///Serializes represented YAML nodes, generating events which are then emitted by Emitter.
30 struct Serializer
31 {
32     private:
33         ///Resolver used to determine which tags are automaticaly resolvable.
34         Resolver resolver_;
35 
36         ///Do all document starts have to be specified explicitly?
37         Flag!"explicitStart" explicitStart_;
38         ///Do all document ends have to be specified explicitly?
39         Flag!"explicitEnd" explicitEnd_;
40         ///YAML version string.
41         string YAMLVersion_;
42 
43         ///Tag directives to emit.
44         TagDirective[] tagDirectives_;
45 
46         //TODO Use something with more deterministic memory usage.
47         ///Nodes with assigned anchors.
48         string[Node] anchors_;
49         ///Nodes with assigned anchors that are already serialized.
50         bool[Node] serializedNodes_;
51         ///ID of the last anchor generated.
52         uint lastAnchorID_ = 0;
53 
54     public:
55         /**
56          * Construct a Serializer.
57          *
58          * Params:
59          *          resolver      = Resolver used to determine which tags are automaticaly resolvable.
60          *          explicitStart = Do all document starts have to be specified explicitly?
61          *          explicitEnd   = Do all document ends have to be specified explicitly?
62          *          YAMLVersion   = YAML version string.
63          *          tagDirectives = Tag directives to emit.
64          */
65         this(Resolver resolver,
66              const Flag!"explicitStart" explicitStart,
67              const Flag!"explicitEnd" explicitEnd, string YAMLVersion,
68              TagDirective[] tagDirectives) @safe
69         {
70             resolver_      = resolver;
71             explicitStart_ = explicitStart;
72             explicitEnd_   = explicitEnd;
73             YAMLVersion_   = YAMLVersion;
74             tagDirectives_ = tagDirectives;
75         }
76 
77         ///Begin the stream.
78         void startStream(EmitterT)(ref EmitterT emitter) @safe
79         {
80             emitter.emit(streamStartEvent(Mark(), Mark()));
81         }
82 
83         ///End the stream.
84         void endStream(EmitterT)(ref EmitterT emitter) @safe
85         {
86             emitter.emit(streamEndEvent(Mark(), Mark()));
87         }
88 
89         ///Serialize a node, emitting it in the process.
90         void serialize(EmitterT)(ref EmitterT emitter, ref Node node) @safe
91         {
92             emitter.emit(documentStartEvent(Mark(), Mark(), explicitStart_,
93                                              YAMLVersion_, tagDirectives_));
94             anchorNode(node);
95             serializeNode(emitter, node);
96             emitter.emit(documentEndEvent(Mark(), Mark(), explicitEnd_));
97             serializedNodes_.destroy();
98             anchors_.destroy();
99             string[Node] emptyAnchors;
100             anchors_ = emptyAnchors;
101             lastAnchorID_ = 0;
102         }
103 
104     private:
105         /**
106          * Determine if it's a good idea to add an anchor to a node.
107          *
108          * Used to prevent associating every single repeating scalar with an
109          * anchor/alias - only nodes long enough can use anchors.
110          *
111          * Params:  node = Node to check for anchorability.
112          *
113          * Returns: True if the node is anchorable, false otherwise.
114          */
115         static bool anchorable(ref Node node) @safe
116         {
117             if(node.nodeID == NodeID.scalar)
118             {
119                 return (node.type == NodeType..string) ? node.as!string.length > 64 :
120                        (node.type == NodeType.binary) ? node.as!(ubyte[]).length > 64 :
121                                                false;
122             }
123             return node.length > 2;
124         }
125 
126         @safe unittest
127         {
128             import std.string : representation;
129             auto shortString = "not much";
130             auto longString = "A fairly long string that would be a good idea to add an anchor to";
131             auto node1 = Node(shortString);
132             auto node2 = Node(shortString.representation.dup);
133             auto node3 = Node(longString);
134             auto node4 = Node(longString.representation.dup);
135             auto node5 = Node([node1]);
136             auto node6 = Node([node1, node2, node3, node4]);
137             assert(!anchorable(node1));
138             assert(!anchorable(node2));
139             assert(anchorable(node3));
140             assert(anchorable(node4));
141             assert(!anchorable(node5));
142             assert(anchorable(node6));
143         }
144 
145         ///Add an anchor to the node if it's anchorable and not anchored yet.
146         void anchorNode(ref Node node) @safe
147         {
148             if(!anchorable(node)){return;}
149 
150             if((node in anchors_) !is null)
151             {
152                 if(anchors_[node] is null)
153                 {
154                     anchors_[node] = generateAnchor();
155                 }
156                 return;
157             }
158 
159             anchors_.remove(node);
160             final switch (node.nodeID)
161             {
162                 case NodeID.mapping:
163                     foreach(ref Node key, ref Node value; node)
164                     {
165                         anchorNode(key);
166                         anchorNode(value);
167                     }
168                     break;
169                 case NodeID.sequence:
170                     foreach(ref Node item; node)
171                     {
172                         anchorNode(item);
173                     }
174                     break;
175                 case NodeID.invalid:
176                     assert(0);
177                 case NodeID.scalar:
178             }
179         }
180 
181         ///Generate and return a new anchor.
182         string generateAnchor() @safe
183         {
184             ++lastAnchorID_;
185             auto appender = appender!string();
186             formattedWrite(appender, "id%03d", lastAnchorID_);
187             return appender.data;
188         }
189 
190         ///Serialize a node and all its subnodes.
191         void serializeNode(EmitterT)(ref EmitterT emitter, ref Node node) @safe
192         {
193             //If the node has an anchor, emit an anchor (as aliasEvent) on the
194             //first occurrence, save it in serializedNodes_, and emit an alias
195             //if it reappears.
196             string aliased;
197             if(anchorable(node) && (node in anchors_) !is null)
198             {
199                 aliased = anchors_[node];
200                 if((node in serializedNodes_) !is null)
201                 {
202                     emitter.emit(aliasEvent(Mark(), Mark(), aliased));
203                     return;
204                 }
205                 serializedNodes_[node] = true;
206             }
207             final switch (node.nodeID)
208             {
209                 case NodeID.mapping:
210                     const defaultTag = resolver_.defaultMappingTag;
211                     const implicit = node.tag_ == defaultTag;
212                     emitter.emit(mappingStartEvent(Mark(), Mark(), aliased, node.tag_,
213                                                     implicit, node.collectionStyle));
214                     foreach(ref Node key, ref Node value; node)
215                     {
216                         serializeNode(emitter, key);
217                         serializeNode(emitter, value);
218                     }
219                     emitter.emit(mappingEndEvent(Mark(), Mark()));
220                     return;
221                 case NodeID.sequence:
222                     const defaultTag = resolver_.defaultSequenceTag;
223                     const implicit = node.tag_ == defaultTag;
224                     emitter.emit(sequenceStartEvent(Mark(), Mark(), aliased, node.tag_,
225                                                      implicit, node.collectionStyle));
226                     foreach(ref Node item; node)
227                     {
228                         serializeNode(emitter, item);
229                     }
230                     emitter.emit(sequenceEndEvent(Mark(), Mark()));
231                     return;
232                 case NodeID.scalar:
233                     assert(node.type == NodeType..string, "Scalar node type must be string before serialized");
234                     auto value = node.as!string;
235                     const detectedTag = resolver_.resolve(NodeID.scalar, null, value, true);
236                     const bool isDetected = node.tag_ == detectedTag;
237 
238                     emitter.emit(scalarEvent(Mark(), Mark(), aliased, node.tag_,
239                                   isDetected, value, node.scalarStyle));
240                     return;
241                 case NodeID.invalid:
242                     assert(0);
243             }
244         }
245 }
246 
247 // Issue #244
248 @safe unittest
249 {
250     import dyaml.dumper : dumper;
251     auto node = Node([
252         Node.Pair(
253             Node(""),
254             Node([
255                 Node([
256                     Node.Pair(
257                         Node("d"),
258                         Node([
259                             Node([
260                                 Node.Pair(
261                                     Node("c"),
262                                     Node("")
263                                 ),
264                                 Node.Pair(
265                                     Node("b"),
266                                     Node("")
267                                 ),
268                                 Node.Pair(
269                                     Node(""),
270                                     Node("")
271                                 )
272                             ])
273                         ])
274                     ),
275                 ]),
276                 Node([
277                     Node.Pair(
278                         Node("d"),
279                         Node([
280                             Node(""),
281                             Node(""),
282                             Node([
283                                 Node.Pair(
284                                     Node("c"),
285                                     Node("")
286                                 ),
287                                 Node.Pair(
288                                     Node("b"),
289                                     Node("")
290                                 ),
291                                 Node.Pair(
292                                     Node(""),
293                                     Node("")
294                                 )
295                             ])
296                         ])
297                     ),
298                     Node.Pair(
299                         Node("z"),
300                         Node("")
301                     ),
302                     Node.Pair(
303                         Node(""),
304                         Node("")
305                     )
306                 ]),
307                 Node("")
308             ])
309         ),
310         Node.Pair(
311             Node("g"),
312             Node("")
313         ),
314         Node.Pair(
315             Node("h"),
316             Node("")
317         ),
318     ]);
319 
320     auto stream = appender!string();
321     dumper().dump(stream, node);
322 }