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