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 }