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