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.idup, 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 }