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 node _representer. Prepares YAML nodes for output. A tutorial can be 9 * found $(LINK2 ../tutorials/custom_types.html, here). 10 * 11 * Code based on $(LINK2 http://www.pyyaml.org, PyYAML). 12 */ 13 module dyaml.representer; 14 15 16 import mir.conv; 17 import mir.math; 18 import mir.timestamp; 19 import std.algorithm; 20 import std.array; 21 import std.base64; 22 import std.container; 23 import std.exception; 24 import std.format; 25 import std..string; 26 27 import dyaml.exception; 28 import dyaml.node; 29 import dyaml.serializer; 30 import dyaml.style; 31 32 package: 33 ///Exception thrown on Representer errors. 34 class RepresenterException : YAMLException 35 { 36 mixin ExceptionCtors; 37 } 38 39 /** 40 * Represents YAML nodes as scalar, sequence and mapping nodes ready for output. 41 */ 42 Node representData(const Node data, ScalarStyle defaultScalarStyle, CollectionStyle defaultCollectionStyle) @safe 43 { 44 Node result; 45 final switch(data.type) 46 { 47 case NodeType.null_: 48 result = representNull(); 49 break; 50 case NodeType.merge: 51 break; 52 case NodeType.boolean: 53 result = representBool(data); 54 break; 55 case NodeType.integer: 56 result = representLong(data); 57 break; 58 case NodeType.decimal: 59 result = representReal(data); 60 break; 61 case NodeType.binary: 62 result = representBytes(data); 63 break; 64 case NodeType.timestamp: 65 result = representTimestamp(data); 66 break; 67 case NodeType..string: 68 result = representString(data); 69 break; 70 case NodeType.mapping: 71 result = representPairs(data, defaultScalarStyle, defaultCollectionStyle); 72 break; 73 case NodeType.sequence: 74 result = representNodes(data, defaultScalarStyle, defaultCollectionStyle); 75 break; 76 } 77 78 final switch (result.nodeID) 79 { 80 case NodeID.scalar: 81 if (result.scalarStyle == ScalarStyle.invalid) 82 { 83 result.scalarStyle = defaultScalarStyle; 84 } 85 break; 86 case NodeID.sequence, NodeID.mapping: 87 if (defaultCollectionStyle != CollectionStyle.invalid) 88 { 89 result.collectionStyle = defaultCollectionStyle; 90 } 91 case NodeID.invalid: 92 } 93 94 95 //Override tag if specified. 96 if(data.tag_ !is null){result.tag_ = data.tag_;} 97 98 //Remember style if this was loaded before. 99 if(data.scalarStyle != ScalarStyle.invalid) 100 { 101 result.scalarStyle = data.scalarStyle; 102 } 103 if(data.collectionStyle != CollectionStyle.invalid) 104 { 105 result.collectionStyle = data.collectionStyle; 106 } 107 return result; 108 } 109 110 @safe unittest 111 { 112 // We don't emit yaml merge nodes. 113 assert(representData(Node(YAMLMerge()), ScalarStyle.invalid, CollectionStyle.invalid) == Node.init); 114 } 115 116 @safe unittest 117 { 118 assert(representData(Node(null), ScalarStyle.invalid, CollectionStyle.invalid) == Node("null", "tag:yaml.org,2002:null")); 119 } 120 121 @safe unittest 122 { 123 assert(representData(Node(cast(string)null), ScalarStyle.invalid, CollectionStyle.invalid) == Node("null", "tag:yaml.org,2002:null")); 124 assert(representData(Node("Hello world!"), ScalarStyle.invalid, CollectionStyle.invalid) == Node("Hello world!", "tag:yaml.org,2002:str")); 125 } 126 127 @safe unittest 128 { 129 assert(representData(Node(64), ScalarStyle.invalid, CollectionStyle.invalid) == Node("64", "tag:yaml.org,2002:int")); 130 } 131 132 @safe unittest 133 { 134 assert(representData(Node(true), ScalarStyle.invalid, CollectionStyle.invalid) == Node("true", "tag:yaml.org,2002:bool")); 135 assert(representData(Node(false), ScalarStyle.invalid, CollectionStyle.invalid) == Node("false", "tag:yaml.org,2002:bool")); 136 } 137 138 @safe unittest 139 { 140 // Float comparison is pretty unreliable... 141 auto result = representData(Node(1.0), ScalarStyle.invalid, CollectionStyle.invalid); 142 assert(approxEqual(result.as!string.to!double, 1.0)); 143 assert(result.tag == "tag:yaml.org,2002:float"); 144 145 assert(representData(Node(double.nan), ScalarStyle.invalid, CollectionStyle.invalid) == Node(".nan", "tag:yaml.org,2002:float")); 146 assert(representData(Node(double.infinity), ScalarStyle.invalid, CollectionStyle.invalid) == Node(".inf", "tag:yaml.org,2002:float")); 147 assert(representData(Node(-double.infinity), ScalarStyle.invalid, CollectionStyle.invalid) == Node("-.inf", "tag:yaml.org,2002:float")); 148 } 149 150 unittest 151 { 152 import mir.conv; 153 assert(representData(Node(Timestamp(2000, 3, 14, 12, 34, 56)), ScalarStyle.invalid, CollectionStyle.invalid) == Node("2000-03-14T12:34:56Z", "tag:yaml.org,2002:timestamp")); 154 } 155 156 @safe unittest 157 { 158 assert(representData(Node(Node[].init, "tag:yaml.org,2002:set"), ScalarStyle.invalid, CollectionStyle.invalid) == Node(Node.Pair[].init, "tag:yaml.org,2002:set")); 159 assert(representData(Node(Node[].init, "tag:yaml.org,2002:seq"), ScalarStyle.invalid, CollectionStyle.invalid) == Node(Node[].init, "tag:yaml.org,2002:seq")); 160 { 161 auto nodes = [ 162 Node("a"), 163 Node("b"), 164 Node("c"), 165 ]; 166 assert(representData(Node(nodes, "tag:yaml.org,2002:set"), ScalarStyle.invalid, CollectionStyle.invalid) == 167 Node([ 168 Node.Pair( 169 Node("a", "tag:yaml.org,2002:str"), 170 Node("null", "tag:yaml.org,2002:null") 171 ), 172 Node.Pair( 173 Node("b", "tag:yaml.org,2002:str"), 174 Node("null", "tag:yaml.org,2002:null") 175 ), 176 Node.Pair( 177 Node("c", "tag:yaml.org,2002:str"), 178 Node("null", "tag:yaml.org,2002:null") 179 ) 180 ], "tag:yaml.org,2002:set")); 181 } 182 { 183 auto nodes = [ 184 Node("a"), 185 Node("b"), 186 Node("c"), 187 ]; 188 assert(representData(Node(nodes, "tag:yaml.org,2002:seq"), ScalarStyle.invalid, CollectionStyle.invalid) == 189 Node([ 190 Node("a", "tag:yaml.org,2002:str"), 191 Node("b", "tag:yaml.org,2002:str"), 192 Node("c", "tag:yaml.org,2002:str") 193 ], "tag:yaml.org,2002:seq")); 194 } 195 } 196 197 @safe unittest 198 { 199 assert(representData(Node(Node.Pair[].init, "tag:yaml.org,2002:omap"), ScalarStyle.invalid, CollectionStyle.invalid) == Node(Node[].init, "tag:yaml.org,2002:omap")); 200 assert(representData(Node(Node.Pair[].init, "tag:yaml.org,2002:pairs"), ScalarStyle.invalid, CollectionStyle.invalid) == Node(Node[].init, "tag:yaml.org,2002:pairs")); 201 assert(representData(Node(Node.Pair[].init, "tag:yaml.org,2002:map"), ScalarStyle.invalid, CollectionStyle.invalid) == Node(Node.Pair[].init, "tag:yaml.org,2002:map")); 202 { 203 auto nodes = [ 204 Node.Pair("a", "b"), 205 Node.Pair("a", "c") 206 ]; 207 assertThrown(representData(Node(nodes, "tag:yaml.org,2002:omap"), ScalarStyle.invalid, CollectionStyle.invalid)); 208 } 209 // Yeah, this gets ugly really fast. 210 { 211 auto nodes = [ 212 Node.Pair("a", "b"), 213 Node.Pair("a", "c") 214 ]; 215 assert(representData(Node(nodes, "tag:yaml.org,2002:pairs"), ScalarStyle.invalid, CollectionStyle.invalid) == 216 Node([ 217 Node( 218 [Node.Pair( 219 Node("a", "tag:yaml.org,2002:str"), 220 Node("b", "tag:yaml.org,2002:str") 221 )], 222 "tag:yaml.org,2002:map"), 223 Node( 224 [Node.Pair( 225 Node("a", "tag:yaml.org,2002:str"), 226 Node("c", "tag:yaml.org,2002:str") 227 )], 228 "tag:yaml.org,2002:map"), 229 ], "tag:yaml.org,2002:pairs")); 230 } 231 { 232 auto nodes = [ 233 Node.Pair("a", "b"), 234 Node.Pair("a", "c") 235 ]; 236 assertThrown(representData(Node(nodes, "tag:yaml.org,2002:map"), ScalarStyle.invalid, CollectionStyle.invalid)); 237 } 238 { 239 auto nodes = [ 240 Node.Pair("a", "b"), 241 Node.Pair("c", "d") 242 ]; 243 assert(representData(Node(nodes, "tag:yaml.org,2002:omap"), ScalarStyle.invalid, CollectionStyle.invalid) == 244 Node([ 245 Node([ 246 Node.Pair( 247 Node("a", "tag:yaml.org,2002:str"), 248 Node("b", "tag:yaml.org,2002:str") 249 ) 250 ], "tag:yaml.org,2002:map"), 251 Node([ 252 Node.Pair( 253 Node("c", "tag:yaml.org,2002:str"), 254 Node("d", "tag:yaml.org,2002:str") 255 ) 256 ], "tag:yaml.org,2002:map" 257 )], "tag:yaml.org,2002:omap")); 258 } 259 { 260 auto nodes = [ 261 Node.Pair("a", "b"), 262 Node.Pair("c", "d") 263 ]; 264 assert(representData(Node(nodes, "tag:yaml.org,2002:map"), ScalarStyle.invalid, CollectionStyle.invalid) == 265 Node([ 266 Node.Pair( 267 Node("a", "tag:yaml.org,2002:str"), 268 Node("b", "tag:yaml.org,2002:str") 269 ), 270 Node.Pair( 271 Node("c", "tag:yaml.org,2002:str"), 272 Node("d", "tag:yaml.org,2002:str") 273 ), 274 ], "tag:yaml.org,2002:map")); 275 } 276 } 277 278 private: 279 280 //Represent a _null _node as a _null YAML value. 281 Node representNull() @safe 282 { 283 return Node("null", "tag:yaml.org,2002:null"); 284 } 285 286 //Represent a string _node as a string scalar. 287 Node representString(const Node node) @safe 288 { 289 string value = node.as!string; 290 return value is null 291 ? Node("null", "tag:yaml.org,2002:null") 292 : Node(value, "tag:yaml.org,2002:str"); 293 } 294 295 //Represent a bytes _node as a binary scalar. 296 Node representBytes(const Node node) @safe 297 { 298 const ubyte[] value = node.as!(ubyte[]); 299 if(value is null){return Node("null", "tag:yaml.org,2002:null");} 300 301 auto newNode = Node(Base64.encode(value).idup, "tag:yaml.org,2002:binary"); 302 newNode.scalarStyle = ScalarStyle.literal; 303 return newNode; 304 } 305 306 //Represent a bool _node as a bool scalar. 307 Node representBool(const Node node) @safe 308 { 309 return Node(node.as!bool ? "true" : "false", "tag:yaml.org,2002:bool"); 310 } 311 312 //Represent a long _node as an integer scalar. 313 Node representLong(const Node node) @safe 314 { 315 return Node(node.as!long.to!string, "tag:yaml.org,2002:int"); 316 } 317 318 //Represent a double _node as a floating point scalar. 319 Node representReal(const Node node) @safe 320 { 321 import mir.conv: to; 322 double f = node.as!double; 323 string value = f != f ? ".nan": 324 f == double.infinity ? ".inf": 325 f == -1.0 * double.infinity ? "-.inf": 326 f.to!string; 327 return Node(value, "tag:yaml.org,2002:float"); 328 } 329 330 //Represent a _node as a timestamp. 331 Node representTimestamp(const Node node) @safe 332 { 333 return Node(node.as!Timestamp.toISOExtString(), "tag:yaml.org,2002:timestamp"); 334 } 335 336 //Represent a sequence _node as sequence/set. 337 Node representNodes(const Node node, ScalarStyle defaultScalarStyle, CollectionStyle defaultCollectionStyle) @safe 338 { 339 auto nodes = node.as!(Node[]); 340 if(node.tag_ == "tag:yaml.org,2002:set") 341 { 342 //YAML sets are mapping with null values. 343 Node.Pair[] pairs; 344 pairs.length = nodes.length; 345 346 foreach(idx, key; nodes) 347 { 348 pairs[idx] = Node.Pair(key, Node("null", "tag:yaml.org,2002:null")); 349 } 350 Node.Pair[] value; 351 value.length = pairs.length; 352 353 auto bestStyle = CollectionStyle.flow; 354 foreach(idx, pair; pairs) 355 { 356 value[idx] = Node.Pair(representData(pair.key, defaultScalarStyle, defaultCollectionStyle), representData(pair.value, defaultScalarStyle, defaultCollectionStyle)); 357 if(value[idx].shouldUseBlockStyle) 358 { 359 bestStyle = CollectionStyle.block; 360 } 361 } 362 363 auto newNode = Node(value, node.tag_); 364 newNode.collectionStyle = bestStyle; 365 return newNode; 366 } 367 else 368 { 369 Node[] value; 370 value.length = nodes.length; 371 372 auto bestStyle = CollectionStyle.flow; 373 foreach(idx, item; nodes) 374 { 375 value[idx] = representData(item, defaultScalarStyle, defaultCollectionStyle); 376 const isScalar = value[idx].nodeID == NodeID.scalar; 377 const s = value[idx].scalarStyle; 378 if(!isScalar || (s != ScalarStyle.invalid && s != ScalarStyle.plain)) 379 { 380 bestStyle = CollectionStyle.block; 381 } 382 } 383 384 auto newNode = Node(value, "tag:yaml.org,2002:seq"); 385 newNode.collectionStyle = bestStyle; 386 return newNode; 387 } 388 } 389 390 bool shouldUseBlockStyle(const Node value) @safe 391 { 392 const isScalar = value.nodeID == NodeID.scalar; 393 const s = value.scalarStyle; 394 return (!isScalar || (s != ScalarStyle.invalid && s != ScalarStyle.plain)); 395 } 396 bool shouldUseBlockStyle(const Node.Pair value) @safe 397 { 398 const keyScalar = value.key.nodeID == NodeID.scalar; 399 const valScalar = value.value.nodeID == NodeID.scalar; 400 const keyStyle = value.key.scalarStyle; 401 const valStyle = value.value.scalarStyle; 402 if(!keyScalar || 403 (keyStyle != ScalarStyle.invalid && keyStyle != ScalarStyle.plain)) 404 { 405 return true; 406 } 407 if(!valScalar || 408 (valStyle != ScalarStyle.invalid && valStyle != ScalarStyle.plain)) 409 { 410 return true; 411 } 412 return false; 413 } 414 415 //Represent a mapping _node as map/ordered map/pairs. 416 Node representPairs(const Node node, ScalarStyle defaultScalarStyle, CollectionStyle defaultCollectionStyle) @safe 417 { 418 auto pairs = node.as!(Node.Pair[]); 419 420 bool hasDuplicates(const Node.Pair[] pairs) @safe 421 { 422 //TODO this should be replaced by something with deterministic memory allocation. 423 auto keys = redBlackTree!Node(); 424 foreach(pair; pairs) 425 { 426 if(pair.key in keys){return true;} 427 keys.insert(pair.key); 428 } 429 return false; 430 } 431 432 Node[] mapToSequence(const Node.Pair[] pairs) @safe 433 { 434 Node[] nodes; 435 nodes.length = pairs.length; 436 foreach(idx, pair; pairs) 437 { 438 Node.Pair value; 439 440 auto bestStyle = value.shouldUseBlockStyle ? CollectionStyle.block : CollectionStyle.flow; 441 value = Node.Pair(representData(pair.key, defaultScalarStyle, defaultCollectionStyle), representData(pair.value, defaultScalarStyle, defaultCollectionStyle)); 442 443 auto newNode = Node([value], "tag:yaml.org,2002:map"); 444 newNode.collectionStyle = bestStyle; 445 nodes[idx] = newNode; 446 } 447 return nodes; 448 } 449 450 if(node.tag_ == "tag:yaml.org,2002:omap") 451 { 452 enforce(!hasDuplicates(pairs), 453 new RepresenterException("Duplicate entry in an ordered map")); 454 auto sequence = mapToSequence(pairs); 455 Node[] value; 456 value.length = sequence.length; 457 458 auto bestStyle = CollectionStyle.flow; 459 foreach(idx, item; sequence) 460 { 461 value[idx] = representData(item, defaultScalarStyle, defaultCollectionStyle); 462 if(value[idx].shouldUseBlockStyle) 463 { 464 bestStyle = CollectionStyle.block; 465 } 466 } 467 468 auto newNode = Node(value, node.tag_); 469 newNode.collectionStyle = bestStyle; 470 return newNode; 471 } 472 else if(node.tag_ == "tag:yaml.org,2002:pairs") 473 { 474 auto sequence = mapToSequence(pairs); 475 Node[] value; 476 value.length = sequence.length; 477 478 auto bestStyle = CollectionStyle.flow; 479 foreach(idx, item; sequence) 480 { 481 value[idx] = representData(item, defaultScalarStyle, defaultCollectionStyle); 482 if(value[idx].shouldUseBlockStyle) 483 { 484 bestStyle = CollectionStyle.block; 485 } 486 } 487 488 auto newNode = Node(value, node.tag_); 489 newNode.collectionStyle = bestStyle; 490 return newNode; 491 } 492 else 493 { 494 enforce(!hasDuplicates(pairs), 495 new RepresenterException("Duplicate entry in an unordered map")); 496 Node.Pair[] value; 497 value.length = pairs.length; 498 499 auto bestStyle = CollectionStyle.flow; 500 foreach(idx, pair; pairs) 501 { 502 value[idx] = Node.Pair(representData(pair.key, defaultScalarStyle, defaultCollectionStyle), representData(pair.value, defaultScalarStyle, defaultCollectionStyle)); 503 if(value[idx].shouldUseBlockStyle) 504 { 505 bestStyle = CollectionStyle.block; 506 } 507 } 508 509 auto newNode = Node(value, "tag:yaml.org,2002:map"); 510 newNode.collectionStyle = bestStyle; 511 return newNode; 512 } 513 }