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