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