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 case NodeID.invalid: 95 } 96 97 98 //Override tag if specified. 99 if(data.tag_ !is null){result.tag_ = data.tag_;} 100 101 //Remember style if this was loaded before. 102 if(data.scalarStyle != ScalarStyle.invalid) 103 { 104 result.scalarStyle = data.scalarStyle; 105 } 106 if(data.collectionStyle != CollectionStyle.invalid) 107 { 108 result.collectionStyle = data.collectionStyle; 109 } 110 return result; 111 } 112 113 @safe unittest 114 { 115 // We don't emit yaml merge nodes. 116 assert(representData(Node(YAMLMerge()), ScalarStyle.invalid, CollectionStyle.invalid) == Node.init); 117 } 118 119 @safe unittest 120 { 121 assert(representData(Node(YAMLNull()), ScalarStyle.invalid, CollectionStyle.invalid) == Node("null", "tag:yaml.org,2002:null")); 122 } 123 124 @safe unittest 125 { 126 assert(representData(Node(cast(string)null), ScalarStyle.invalid, CollectionStyle.invalid) == Node("null", "tag:yaml.org,2002:null")); 127 assert(representData(Node("Hello world!"), ScalarStyle.invalid, CollectionStyle.invalid) == Node("Hello world!", "tag:yaml.org,2002:str")); 128 } 129 130 @safe unittest 131 { 132 assert(representData(Node(64), ScalarStyle.invalid, CollectionStyle.invalid) == Node("64", "tag:yaml.org,2002:int")); 133 } 134 135 @safe unittest 136 { 137 assert(representData(Node(true), ScalarStyle.invalid, CollectionStyle.invalid) == Node("true", "tag:yaml.org,2002:bool")); 138 assert(representData(Node(false), ScalarStyle.invalid, CollectionStyle.invalid) == Node("false", "tag:yaml.org,2002:bool")); 139 } 140 141 @safe unittest 142 { 143 // Float comparison is pretty unreliable... 144 auto result = representData(Node(1.0), ScalarStyle.invalid, CollectionStyle.invalid); 145 assert(isClose(result.as!string.to!real, 1.0)); 146 assert(result.tag == "tag:yaml.org,2002:float"); 147 148 assert(representData(Node(real.nan), ScalarStyle.invalid, CollectionStyle.invalid) == Node(".nan", "tag:yaml.org,2002:float")); 149 assert(representData(Node(real.infinity), ScalarStyle.invalid, CollectionStyle.invalid) == Node(".inf", "tag:yaml.org,2002:float")); 150 assert(representData(Node(-real.infinity), ScalarStyle.invalid, CollectionStyle.invalid) == Node("-.inf", "tag:yaml.org,2002:float")); 151 } 152 153 @safe unittest 154 { 155 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")); 156 } 157 158 @safe unittest 159 { 160 assert(representData(Node(Node[].init, "tag:yaml.org,2002:set"), ScalarStyle.invalid, CollectionStyle.invalid) == Node(Node.Pair[].init, "tag:yaml.org,2002:set")); 161 assert(representData(Node(Node[].init, "tag:yaml.org,2002:seq"), ScalarStyle.invalid, CollectionStyle.invalid) == Node(Node[].init, "tag:yaml.org,2002:seq")); 162 { 163 auto nodes = [ 164 Node("a"), 165 Node("b"), 166 Node("c"), 167 ]; 168 assert(representData(Node(nodes, "tag:yaml.org,2002:set"), ScalarStyle.invalid, CollectionStyle.invalid) == 169 Node([ 170 Node.Pair( 171 Node("a", "tag:yaml.org,2002:str"), 172 Node("null", "tag:yaml.org,2002:null") 173 ), 174 Node.Pair( 175 Node("b", "tag:yaml.org,2002:str"), 176 Node("null", "tag:yaml.org,2002:null") 177 ), 178 Node.Pair( 179 Node("c", "tag:yaml.org,2002:str"), 180 Node("null", "tag:yaml.org,2002:null") 181 ) 182 ], "tag:yaml.org,2002:set")); 183 } 184 { 185 auto nodes = [ 186 Node("a"), 187 Node("b"), 188 Node("c"), 189 ]; 190 assert(representData(Node(nodes, "tag:yaml.org,2002:seq"), ScalarStyle.invalid, CollectionStyle.invalid) == 191 Node([ 192 Node("a", "tag:yaml.org,2002:str"), 193 Node("b", "tag:yaml.org,2002:str"), 194 Node("c", "tag:yaml.org,2002:str") 195 ], "tag:yaml.org,2002:seq")); 196 } 197 } 198 199 @safe unittest 200 { 201 assert(representData(Node(Node.Pair[].init, "tag:yaml.org,2002:omap"), ScalarStyle.invalid, CollectionStyle.invalid) == Node(Node[].init, "tag:yaml.org,2002:omap")); 202 assert(representData(Node(Node.Pair[].init, "tag:yaml.org,2002:pairs"), ScalarStyle.invalid, CollectionStyle.invalid) == Node(Node[].init, "tag:yaml.org,2002:pairs")); 203 assert(representData(Node(Node.Pair[].init, "tag:yaml.org,2002:map"), ScalarStyle.invalid, CollectionStyle.invalid) == Node(Node.Pair[].init, "tag:yaml.org,2002:map")); 204 { 205 auto nodes = [ 206 Node.Pair("a", "b"), 207 Node.Pair("a", "c") 208 ]; 209 assertThrown(representData(Node(nodes, "tag:yaml.org,2002:omap"), ScalarStyle.invalid, CollectionStyle.invalid)); 210 } 211 // Yeah, this gets ugly really fast. 212 { 213 auto nodes = [ 214 Node.Pair("a", "b"), 215 Node.Pair("a", "c") 216 ]; 217 assert(representData(Node(nodes, "tag:yaml.org,2002:pairs"), ScalarStyle.invalid, CollectionStyle.invalid) == 218 Node([ 219 Node( 220 [Node.Pair( 221 Node("a", "tag:yaml.org,2002:str"), 222 Node("b", "tag:yaml.org,2002:str") 223 )], 224 "tag:yaml.org,2002:map"), 225 Node( 226 [Node.Pair( 227 Node("a", "tag:yaml.org,2002:str"), 228 Node("c", "tag:yaml.org,2002:str") 229 )], 230 "tag:yaml.org,2002:map"), 231 ], "tag:yaml.org,2002:pairs")); 232 } 233 { 234 auto nodes = [ 235 Node.Pair("a", "b"), 236 Node.Pair("a", "c") 237 ]; 238 assertThrown(representData(Node(nodes, "tag:yaml.org,2002:map"), ScalarStyle.invalid, CollectionStyle.invalid)); 239 } 240 { 241 auto nodes = [ 242 Node.Pair("a", "b"), 243 Node.Pair("c", "d") 244 ]; 245 assert(representData(Node(nodes, "tag:yaml.org,2002:omap"), ScalarStyle.invalid, CollectionStyle.invalid) == 246 Node([ 247 Node([ 248 Node.Pair( 249 Node("a", "tag:yaml.org,2002:str"), 250 Node("b", "tag:yaml.org,2002:str") 251 ) 252 ], "tag:yaml.org,2002:map"), 253 Node([ 254 Node.Pair( 255 Node("c", "tag:yaml.org,2002:str"), 256 Node("d", "tag:yaml.org,2002:str") 257 ) 258 ], "tag:yaml.org,2002:map" 259 )], "tag:yaml.org,2002:omap")); 260 } 261 { 262 auto nodes = [ 263 Node.Pair("a", "b"), 264 Node.Pair("c", "d") 265 ]; 266 assert(representData(Node(nodes, "tag:yaml.org,2002:map"), ScalarStyle.invalid, CollectionStyle.invalid) == 267 Node([ 268 Node.Pair( 269 Node("a", "tag:yaml.org,2002:str"), 270 Node("b", "tag:yaml.org,2002:str") 271 ), 272 Node.Pair( 273 Node("c", "tag:yaml.org,2002:str"), 274 Node("d", "tag:yaml.org,2002:str") 275 ), 276 ], "tag:yaml.org,2002:map")); 277 } 278 } 279 280 private: 281 282 //Represent a _null _node as a _null YAML value. 283 Node representNull() @safe 284 { 285 return Node("null", "tag:yaml.org,2002:null"); 286 } 287 288 //Represent a string _node as a string scalar. 289 Node representString(const Node node) @safe 290 { 291 string value = node.as!string; 292 return value is null 293 ? Node("null", "tag:yaml.org,2002:null") 294 : 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 }