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 import dyaml.tag; 33 34 35 ///Exception thrown on Representer errors. 36 class RepresenterException : YAMLException 37 { 38 mixin ExceptionCtors; 39 } 40 41 /** 42 * Represents YAML nodes as scalar, sequence and mapping nodes ready for output. 43 * 44 * This class is used to add support for dumping of custom data types. 45 * 46 * It can also override default node formatting styles for output. 47 */ 48 final class Representer 49 { 50 private: 51 // Representer functions indexed by types. 52 Node function(ref Node, Representer)[TypeInfo] representers_; 53 // Default style for scalar nodes. 54 ScalarStyle defaultScalarStyle_ = ScalarStyle.Invalid; 55 // Default style for collection nodes. 56 CollectionStyle defaultCollectionStyle_ = CollectionStyle.Invalid; 57 58 public: 59 @disable bool opEquals(ref Representer); 60 @disable int opCmp(ref Representer); 61 62 /** 63 * Construct a Representer. 64 * 65 * Params: useDefaultRepresenters = Use default representer functions 66 * for default YAML types? This can be 67 * disabled to use custom representer 68 * functions for default types. 69 */ 70 this(const Flag!"useDefaultRepresenters" useDefaultRepresenters = Yes.useDefaultRepresenters) 71 @safe pure 72 { 73 if(!useDefaultRepresenters){return;} 74 addRepresenter!YAMLNull(&representNull); 75 addRepresenter!string(&representString); 76 addRepresenter!(ubyte[])(&representBytes); 77 addRepresenter!bool(&representBool); 78 addRepresenter!long(&representLong); 79 addRepresenter!real(&representReal); 80 addRepresenter!(Node[])(&representNodes); 81 addRepresenter!(Node.Pair[])(&representPairs); 82 addRepresenter!SysTime(&representSysTime); 83 } 84 85 ///Destroy the Representer. 86 pure @safe nothrow ~this() 87 { 88 representers_.destroy(); 89 representers_ = null; 90 } 91 92 ///Set default _style for scalars. If style is $(D ScalarStyle.Invalid), the _style is chosen automatically. 93 @property void defaultScalarStyle(ScalarStyle style) pure @safe nothrow 94 { 95 defaultScalarStyle_ = style; 96 } 97 98 ///Set default _style for collections. If style is $(D CollectionStyle.Invalid), the _style is chosen automatically. 99 @property void defaultCollectionStyle(CollectionStyle style) pure @safe nothrow 100 { 101 defaultCollectionStyle_ = style; 102 } 103 104 /** 105 * Add a function to represent nodes with a specific data type. 106 * 107 * The representer function takes references to a $(D Node) storing the data 108 * type and to the $(D Representer). It returns the represented node and may 109 * throw a $(D RepresenterException). See the example for more information. 110 * 111 * 112 * Only one function may be specified for one data type. Default data 113 * types already have representer functions unless disabled in the 114 * $(D Representer) constructor. 115 * 116 * 117 * Structs and classes must implement the $(D opCmp()) operator for D:YAML 118 * support. The signature of the operator that must be implemented 119 * is $(D const int opCmp(ref const MyStruct s)) for structs where 120 * $(I MyStruct) is the struct type, and $(D int opCmp(Object o)) for 121 * classes. Note that the class $(D opCmp()) should not alter the compared 122 * values - it is not const for compatibility reasons. 123 * 124 * Params: representer = Representer function to add. 125 * 126 * Examples: 127 * 128 * Representing a simple struct: 129 * -------------------- 130 * import std.string; 131 * 132 * import dyaml.all; 133 * 134 * struct MyStruct 135 * { 136 * int x, y, z; 137 * 138 * //Any D:YAML type must have a custom opCmp operator. 139 * //This is used for ordering in mappings. 140 * const int opCmp(ref const MyStruct s) 141 * { 142 * if(x != s.x){return x - s.x;} 143 * if(y != s.y){return y - s.y;} 144 * if(z != s.z){return z - s.z;} 145 * return 0; 146 * } 147 * } 148 * 149 * Node representMyStruct(ref Node node, Representer representer) 150 * { 151 * //The node is guaranteed to be MyStruct as we add representer for MyStruct. 152 * auto value = node.as!MyStruct; 153 * //Using custom scalar format, x:y:z. 154 * auto scalar = format("%s:%s:%s", value.x, value.y, value.z); 155 * //Representing as a scalar, with custom tag to specify this data type. 156 * return representer.representScalar("!mystruct.tag", scalar); 157 * } 158 * 159 * void main() 160 * { 161 * auto dumper = Dumper("file.yaml"); 162 * auto representer = new Representer; 163 * representer.addRepresenter!MyStruct(&representMyStruct); 164 * dumper.representer = representer; 165 * dumper.dump(Node(MyStruct(1,2,3))); 166 * } 167 * -------------------- 168 * 169 * Representing a class: 170 * -------------------- 171 * import std.string; 172 * 173 * import dyaml.all; 174 * 175 * class MyClass 176 * { 177 * int x, y, z; 178 * 179 * this(int x, int y, int z) 180 * { 181 * this.x = x; 182 * this.y = y; 183 * this.z = z; 184 * } 185 * 186 * //Any D:YAML type must have a custom opCmp operator. 187 * //This is used for ordering in mappings. 188 * override int opCmp(Object o) 189 * { 190 * MyClass s = cast(MyClass)o; 191 * if(s is null){return -1;} 192 * if(x != s.x){return x - s.x;} 193 * if(y != s.y){return y - s.y;} 194 * if(z != s.z){return z - s.z;} 195 * return 0; 196 * } 197 * 198 * ///Useful for Node.as!string . 199 * override string toString() 200 * { 201 * return format("MyClass(%s, %s, %s)", x, y, z); 202 * } 203 * } 204 * 205 * //Same as representMyStruct. 206 * Node representMyClass(ref Node node, Representer representer) 207 * { 208 * //The node is guaranteed to be MyClass as we add representer for MyClass. 209 * auto value = node.as!MyClass; 210 * //Using custom scalar format, x:y:z. 211 * auto scalar = format("%s:%s:%s", value.x, value.y, value.z); 212 * //Representing as a scalar, with custom tag to specify this data type. 213 * return representer.representScalar("!myclass.tag", scalar); 214 * } 215 * 216 * void main() 217 * { 218 * auto dumper = Dumper("file.yaml"); 219 * auto representer = new Representer; 220 * representer.addRepresenter!MyClass(&representMyClass); 221 * dumper.representer = representer; 222 * dumper.dump(Node(new MyClass(1,2,3))); 223 * } 224 * -------------------- 225 */ 226 void addRepresenter(T)(Node function(ref Node, Representer) representer) 227 @trusted pure 228 { 229 assert((typeid(T) in representers_) is null, 230 "Representer function for data type " ~ T.stringof ~ 231 " already specified. Can't specify another one"); 232 representers_[typeid(T)] = representer; 233 } 234 235 //If profiling shows a bottleneck on tag construction in these 3 methods, 236 //we'll need to take Tag directly and have string based wrappers for 237 //user code. 238 239 /** 240 * Represent a _scalar with specified _tag. 241 * 242 * This is used by representer functions that produce scalars. 243 * 244 * Params: tag = Tag of the _scalar. 245 * scalar = Scalar value. 246 * style = Style of the _scalar. If invalid, default _style will be used. 247 * If the node was loaded before, previous _style will always be used. 248 * 249 * Returns: The represented node. 250 * 251 * Example: 252 * -------------------- 253 * struct MyStruct 254 * { 255 * int x, y, z; 256 * 257 * //Any D:YAML type must have a custom opCmp operator. 258 * //This is used for ordering in mappings. 259 * const int opCmp(ref const MyStruct s) 260 * { 261 * if(x != s.x){return x - s.x;} 262 * if(y != s.y){return y - s.y;} 263 * if(z != s.z){return z - s.z;} 264 * return 0; 265 * } 266 * } 267 * 268 * Node representMyStruct(ref Node node, Representer representer) 269 * { 270 * auto value = node.as!MyStruct; 271 * auto scalar = format("%s:%s:%s", value.x, value.y, value.z); 272 * return representer.representScalar("!mystruct.tag", scalar); 273 * } 274 * -------------------- 275 */ 276 Node representScalar(string tag, string scalar, 277 ScalarStyle style = ScalarStyle.Invalid) @trusted 278 { 279 if(style == ScalarStyle.Invalid){style = defaultScalarStyle_;} 280 return Node.rawNode(Node.Value(scalar), Mark(), Tag(tag), style, 281 CollectionStyle.Invalid); 282 } 283 284 /** 285 * Represent a _sequence with specified _tag, representing children first. 286 * 287 * This is used by representer functions that produce sequences. 288 * 289 * Params: tag = Tag of the _sequence. 290 * sequence = Sequence of nodes. 291 * style = Style of the _sequence. If invalid, default _style will be used. 292 * If the node was loaded before, previous _style will always be used. 293 * 294 * Returns: The represented node. 295 * 296 * Throws: $(D RepresenterException) if a child could not be represented. 297 * 298 * Example: 299 * -------------------- 300 * struct MyStruct 301 * { 302 * int x, y, z; 303 * 304 * //Any D:YAML type must have a custom opCmp operator. 305 * //This is used for ordering in mappings. 306 * const int opCmp(ref const MyStruct s) 307 * { 308 * if(x != s.x){return x - s.x;} 309 * if(y != s.y){return y - s.y;} 310 * if(z != s.z){return z - s.z;} 311 * return 0; 312 * } 313 * } 314 * 315 * Node representMyStruct(ref Node node, Representer representer) 316 * { 317 * auto value = node.as!MyStruct; 318 * auto nodes = [Node(value.x), Node(value.y), Node(value.z)]; 319 * //use flow style 320 * return representer.representSequence("!mystruct.tag", nodes, 321 * CollectionStyle.Flow); 322 * } 323 * -------------------- 324 */ 325 Node representSequence(string tag, Node[] sequence, 326 CollectionStyle style = CollectionStyle.Invalid) @trusted 327 { 328 Node[] value; 329 value.length = sequence.length; 330 331 auto bestStyle = CollectionStyle.Flow; 332 foreach(idx, ref item; sequence) 333 { 334 value[idx] = representData(item); 335 const isScalar = value[idx].isScalar; 336 const s = value[idx].scalarStyle; 337 if(!isScalar || (s != ScalarStyle.Invalid && s != ScalarStyle.Plain)) 338 { 339 bestStyle = CollectionStyle.Block; 340 } 341 } 342 343 if(style == CollectionStyle.Invalid) 344 { 345 style = defaultCollectionStyle_ != CollectionStyle.Invalid 346 ? defaultCollectionStyle_ 347 : bestStyle; 348 } 349 return Node.rawNode(Node.Value(value), Mark(), Tag(tag), 350 ScalarStyle.Invalid, style); 351 } 352 353 /** 354 * Represent a mapping with specified _tag, representing children first. 355 * 356 * This is used by representer functions that produce mappings. 357 * 358 * Params: tag = Tag of the mapping. 359 * pairs = Key-value _pairs of the mapping. 360 * style = Style of the mapping. If invalid, default _style will be used. 361 * If the node was loaded before, previous _style will always be used. 362 * 363 * Returns: The represented node. 364 * 365 * Throws: $(D RepresenterException) if a child could not be represented. 366 * 367 * Example: 368 * -------------------- 369 * struct MyStruct 370 * { 371 * int x, y, z; 372 * 373 * //Any D:YAML type must have a custom opCmp operator. 374 * //This is used for ordering in mappings. 375 * const int opCmp(ref const MyStruct s) 376 * { 377 * if(x != s.x){return x - s.x;} 378 * if(y != s.y){return y - s.y;} 379 * if(z != s.z){return z - s.z;} 380 * return 0; 381 * } 382 * } 383 * 384 * Node representMyStruct(ref Node node, Representer representer) 385 * { 386 * auto value = node.as!MyStruct; 387 * auto pairs = [Node.Pair("x", value.x), 388 * Node.Pair("y", value.y), 389 * Node.Pair("z", value.z)]; 390 * return representer.representMapping("!mystruct.tag", pairs); 391 * } 392 * -------------------- 393 */ 394 Node representMapping(string tag, Node.Pair[] pairs, 395 CollectionStyle style = CollectionStyle.Invalid) @trusted 396 { 397 Node.Pair[] value; 398 value.length = pairs.length; 399 400 auto bestStyle = CollectionStyle.Flow; 401 foreach(idx, ref pair; pairs) 402 { 403 value[idx] = Node.Pair(representData(pair.key), representData(pair.value)); 404 const keyScalar = value[idx].key.isScalar; 405 const valScalar = value[idx].value.isScalar; 406 const keyStyle = value[idx].key.scalarStyle; 407 const valStyle = value[idx].value.scalarStyle; 408 if(!keyScalar || 409 (keyStyle != ScalarStyle.Invalid && keyStyle != ScalarStyle.Plain)) 410 { 411 bestStyle = CollectionStyle.Block; 412 } 413 if(!valScalar || 414 (valStyle != ScalarStyle.Invalid && valStyle != ScalarStyle.Plain)) 415 { 416 bestStyle = CollectionStyle.Block; 417 } 418 } 419 420 if(style == CollectionStyle.Invalid) 421 { 422 style = defaultCollectionStyle_ != CollectionStyle.Invalid 423 ? defaultCollectionStyle_ 424 : bestStyle; 425 } 426 return Node.rawNode(Node.Value(value), Mark(), Tag(tag), 427 ScalarStyle.Invalid, style); 428 } 429 430 package: 431 //Represent a node based on its type, and return the represented result. 432 Node representData(ref Node data) @system 433 { 434 //User types are wrapped in YAMLObject. 435 auto type = data.isUserType ? data.as!YAMLObject.type : data.type; 436 437 enforce((type in representers_) !is null, 438 new RepresenterException("No representer function for type " 439 ~ type.toString() ~ " , cannot represent.")); 440 Node result = representers_[type](data, this); 441 442 //Override tag if specified. 443 if(!data.tag_.isNull()){result.tag_ = data.tag_;} 444 445 //Remember style if this was loaded before. 446 if(data.scalarStyle != ScalarStyle.Invalid) 447 { 448 result.scalarStyle = data.scalarStyle; 449 } 450 if(data.collectionStyle != CollectionStyle.Invalid) 451 { 452 result.collectionStyle = data.collectionStyle; 453 } 454 return result; 455 } 456 457 //Represent a node, serializing with specified Serializer. 458 void represent(ref Serializer serializer, ref Node node) @trusted 459 { 460 auto data = representData(node); 461 serializer.serialize(data); 462 } 463 } 464 465 466 ///Represent a _null _node as a _null YAML value. 467 Node representNull(ref Node node, Representer representer) @safe 468 { 469 return representer.representScalar("tag:yaml.org,2002:null", "null"); 470 } 471 472 ///Represent a string _node as a string scalar. 473 Node representString(ref Node node, Representer representer) @safe 474 { 475 string value = node.as!string; 476 return value is null 477 ? representNull(node, representer) 478 : representer.representScalar("tag:yaml.org,2002:str", value); 479 } 480 481 ///Represent a bytes _node as a binary scalar. 482 Node representBytes(ref Node node, Representer representer) @system 483 { 484 const ubyte[] value = node.as!(ubyte[]); 485 if(value is null){return representNull(node, representer);} 486 return representer.representScalar("tag:yaml.org,2002:binary", 487 cast(string)Base64.encode(value), 488 ScalarStyle.Literal); 489 } 490 491 ///Represent a bool _node as a bool scalar. 492 Node representBool(ref Node node, Representer representer) @safe 493 { 494 return representer.representScalar("tag:yaml.org,2002:bool", 495 node.as!bool ? "true" : "false"); 496 } 497 498 ///Represent a long _node as an integer scalar. 499 Node representLong(ref Node node, Representer representer) @system 500 { 501 return representer.representScalar("tag:yaml.org,2002:int", 502 to!string(node.as!long)); 503 } 504 505 ///Represent a real _node as a floating point scalar. 506 Node representReal(ref Node node, Representer representer) @system 507 { 508 real f = node.as!real; 509 string value = isNaN(f) ? ".nan": 510 f == real.infinity ? ".inf": 511 f == -1.0 * real.infinity ? "-.inf": 512 {auto a = appender!string(); 513 formattedWrite(a, "%12f", f); 514 return a.data.strip();}(); 515 516 return representer.representScalar("tag:yaml.org,2002:float", value); 517 } 518 519 ///Represent a SysTime _node as a timestamp. 520 Node representSysTime(ref Node node, Representer representer) @system 521 { 522 return representer.representScalar("tag:yaml.org,2002:timestamp", 523 node.as!SysTime.toISOExtString()); 524 } 525 526 ///Represent a sequence _node as sequence/set. 527 Node representNodes(ref Node node, Representer representer) @safe 528 { 529 auto nodes = node.as!(Node[]); 530 if(node.tag_ == Tag("tag:yaml.org,2002:set")) 531 { 532 ///YAML sets are mapping with null values. 533 Node.Pair[] pairs; 534 pairs.length = nodes.length; 535 Node dummy; 536 foreach(idx, ref key; nodes) 537 { 538 pairs[idx] = Node.Pair(key, representNull(dummy, representer)); 539 } 540 return representer.representMapping(node.tag_.get, pairs); 541 } 542 else 543 { 544 return representer.representSequence("tag:yaml.org,2002:seq", nodes); 545 } 546 } 547 548 ///Represent a mapping _node as map/ordered map/pairs. 549 Node representPairs(ref Node node, Representer representer) @system 550 { 551 auto pairs = node.as!(Node.Pair[]); 552 553 bool hasDuplicates(Node.Pair[] pairs) 554 { 555 //TODO this should be replaced by something with deterministic memory allocation. 556 auto keys = redBlackTree!Node(); 557 scope(exit){keys.destroy();} 558 foreach(ref pair; pairs) 559 { 560 if(pair.key in keys){return true;} 561 keys.insert(pair.key); 562 } 563 return false; 564 } 565 566 Node[] mapToSequence(Node.Pair[] pairs) 567 { 568 Node[] nodes; 569 nodes.length = pairs.length; 570 foreach(idx, ref pair; pairs) 571 { 572 nodes[idx] = representer.representMapping("tag:yaml.org,2002:map", [pair]); 573 } 574 return nodes; 575 } 576 577 if(node.tag_ == Tag("tag:yaml.org,2002:omap")) 578 { 579 enforce(!hasDuplicates(pairs), 580 new RepresenterException("Duplicate entry in an ordered map")); 581 return representer.representSequence(node.tag_.get, mapToSequence(pairs)); 582 } 583 else if(node.tag_ == Tag("tag:yaml.org,2002:pairs")) 584 { 585 return representer.representSequence(node.tag_.get, mapToSequence(pairs)); 586 } 587 else 588 { 589 enforce(!hasDuplicates(pairs), 590 new RepresenterException("Duplicate entry in an unordered map")); 591 return representer.representMapping("tag:yaml.org,2002:map", pairs); 592 } 593 } 594 595 //Unittests 596 //These should really all be encapsulated in unittests. 597 private: 598 599 import dyaml.dumper; 600 601 struct MyStruct 602 { 603 int x, y, z; 604 605 int opCmp(ref const MyStruct s) const pure @safe nothrow 606 { 607 if(x != s.x){return x - s.x;} 608 if(y != s.y){return y - s.y;} 609 if(z != s.z){return z - s.z;} 610 return 0; 611 } 612 } 613 614 Node representMyStruct(ref Node node, Representer representer) @system 615 { 616 //The node is guaranteed to be MyStruct as we add representer for MyStruct. 617 auto value = node.as!MyStruct; 618 //Using custom scalar format, x:y:z. 619 auto scalar = format("%s:%s:%s", value.x, value.y, value.z); 620 //Representing as a scalar, with custom tag to specify this data type. 621 return representer.representScalar("!mystruct.tag", scalar); 622 } 623 624 Node representMyStructSeq(ref Node node, Representer representer) @safe 625 { 626 auto value = node.as!MyStruct; 627 auto nodes = [Node(value.x), Node(value.y), Node(value.z)]; 628 return representer.representSequence("!mystruct.tag", nodes); 629 } 630 631 Node representMyStructMap(ref Node node, Representer representer) @safe 632 { 633 auto value = node.as!MyStruct; 634 auto pairs = [Node.Pair("x", value.x), 635 Node.Pair("y", value.y), 636 Node.Pair("z", value.z)]; 637 return representer.representMapping("!mystruct.tag", pairs); 638 } 639 640 class MyClass 641 { 642 int x, y, z; 643 644 this(int x, int y, int z) pure @safe nothrow 645 { 646 this.x = x; 647 this.y = y; 648 this.z = z; 649 } 650 651 override int opCmp(Object o) pure @safe nothrow 652 { 653 MyClass s = cast(MyClass)o; 654 if(s is null){return -1;} 655 if(x != s.x){return x - s.x;} 656 if(y != s.y){return y - s.y;} 657 if(z != s.z){return z - s.z;} 658 return 0; 659 } 660 661 ///Useful for Node.as!string . 662 override string toString() @trusted 663 { 664 return format("MyClass(%s, %s, %s)", x, y, z); 665 } 666 } 667 668 //Same as representMyStruct. 669 Node representMyClass(ref Node node, Representer representer) @system 670 { 671 //The node is guaranteed to be MyClass as we add representer for MyClass. 672 auto value = node.as!MyClass; 673 //Using custom scalar format, x:y:z. 674 auto scalar = format("%s:%s:%s", value.x, value.y, value.z); 675 //Representing as a scalar, with custom tag to specify this data type. 676 return representer.representScalar("!myclass.tag", scalar); 677 } 678 679 import dyaml.stream; 680 681 unittest 682 { 683 foreach(r; [&representMyStruct, 684 &representMyStructSeq, 685 &representMyStructMap]) 686 { 687 auto dumper = Dumper(new YMemoryStream()); 688 auto representer = new Representer; 689 representer.addRepresenter!MyStruct(r); 690 dumper.representer = representer; 691 dumper.dump(Node(MyStruct(1,2,3))); 692 } 693 } 694 695 unittest 696 { 697 auto dumper = Dumper(new YMemoryStream()); 698 auto representer = new Representer; 699 representer.addRepresenter!MyClass(&representMyClass); 700 dumper.representer = representer; 701 dumper.dump(Node(new MyClass(1,2,3))); 702 }