1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
|
// 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);
}
|