1 module hunt.xml.XmlSerializer; 2 3 import hunt.xml.Attribute; 4 import hunt.xml.Common; 5 import hunt.xml.Document; 6 import hunt.xml.Element; 7 import hunt.xml.Node; 8 import hunt.xml.Writer; 9 10 import hunt.serialization.Common; 11 import hunt.logging.ConsoleLogger; 12 13 import std.algorithm : map, each; 14 import std.array; 15 import std.conv; 16 import std.datetime; 17 import std.stdio; 18 import std.traits; 19 20 /* -------------------------------------------------------------------------- */ 21 /* Annotations */ 22 /* -------------------------------------------------------------------------- */ 23 // https://howtodoinjava.com/jaxb/jaxb-annotations/ 24 25 /** 26 * Excludes the field from both encoding and decoding. 27 */ 28 enum XmlIgnore; 29 30 /** 31 * 32 */ 33 struct XmlRootElement { 34 string name; 35 } 36 37 /** 38 * 39 */ 40 struct XmlAttribute { 41 string name; 42 } 43 44 /** 45 * 46 */ 47 struct XmlElement { 48 string name; 49 } 50 51 52 enum MetaTypeName = "__metatype__"; 53 54 /** 55 * 56 */ 57 interface XmlSerializable { 58 59 Element xmlSerialize(); 60 61 void xmlDeserialize(Element value); 62 } 63 64 65 /** 66 * 67 */ 68 final class XmlSerializer { 69 70 static T toObject(T, TraverseBase traverseBase = TraverseBase.yes, bool canThrow = false) 71 (string xml, T defaultValue = T.init) if (is(T == class)) { 72 return toObject!(T, traverseBase, canThrow)(Document.parse(xml), defaultValue); 73 } 74 75 static T toObject(T, bool canThrow = false) 76 (string xml, T defaultValue = T.init) if (!is(T == class)) { 77 return toObject!(T, canThrow)(Document.parse(xml), defaultValue); 78 } 79 80 /** 81 * Converts a `Document` to an object of type `T` by filling its fields with the Document's elements. 82 */ 83 static T toObject(T, TraverseBase traverseBase = TraverseBase.yes, bool canThrow = false) 84 (Document doc, T defaultValue = T.init) if (is(T == class) && __traits(compiles, new T())) { // is(typeof(new T())) 85 86 assert(doc !is null); 87 Element element = doc.firstNode(); 88 return toObject!(T, traverseBase, canThrow)(element); 89 } 90 91 92 static T toObject(T, TraverseBase traverseBase = TraverseBase.yes, bool canThrow = false) 93 (Element element, T defaultValue = T.init) if (is(T == class) && __traits(compiles, new T())) { // is(typeof(new T())) 94 95 debug(HUNT_DEBUG_MORE) { 96 tracef("Element name: %s, text: %s", element.getName(), element.getText()); 97 } 98 99 auto result = new T(); 100 if(element is null) 101 return result; 102 103 static if(is(T : XmlSerializable)) { 104 result.xmlDeserialize(element); 105 } else { 106 try { 107 deserializeObject!(T, traverseBase)(result, element); 108 } catch (XmlException e) { 109 return handleException!(T, canThrow)(element, e.msg, defaultValue); 110 } 111 } 112 113 return result; 114 } 115 116 /** 117 * Struct 118 */ 119 static T toObject(T, bool canThrow = false)(Document doc, T defaultValue = T.init) 120 if (is(T == struct) && !is(T == SysTime)) { 121 122 auto result = T(); 123 Element element = doc.firstNode(); 124 if(element is null) 125 return result; 126 127 try { 128 static foreach (string member; FieldNameTuple!T) { 129 deserializeMember!(member)(result, element); 130 } 131 } catch (XmlException e) { 132 return handleException!(T, canThrow)(element, e.msg, defaultValue); 133 } 134 135 return result; 136 } 137 138 /** 139 * struct 140 */ 141 static void deserializeObject(T)(ref T target, Element element) if(is(T == struct)) { 142 static foreach (string member; FieldNameTuple!T) { 143 // current fields 144 deserializeMember!(member)(target, element); 145 } 146 } 147 148 /** 149 * class 150 */ 151 static void deserializeObject(T, TraverseBase traverseBase = TraverseBase.yes) 152 (T target, Element element) if(is(T == class)) { 153 154 static foreach (string member; FieldNameTuple!T) { 155 // current fields 156 deserializeMember!(member)(target, element); 157 } 158 159 // super fields 160 static if(traverseBase) { 161 alias baseClasses = BaseClassesTuple!T; 162 alias BaseType = baseClasses[0]; 163 164 static if(baseClasses.length >= 1 && !is(BaseType == Object)) { 165 debug(HUNT_DEBUG_MORE) { 166 infof("deserializing fields in base %s for %s", BaseType.stringof, T.stringof); 167 } 168 169 alias xmlRootUDAs = getUDAs!(BaseType, XmlRootElement); 170 static if(xmlRootUDAs.length > 0) { 171 enum RootNodeName = xmlRootUDAs[0].name; 172 } else { 173 enum RootNodeName = BaseType.stringof; 174 } 175 176 Element baseClassElement = element.firstNode(RootNodeName); 177 if(baseClassElement !is null) { 178 deserializeObject!(BaseType, traverseBase)(target, baseClassElement); 179 } 180 } 181 } 182 } 183 184 private static void deserializeMember(string member, T, TraverseBase traverseBase = TraverseBase.yes) 185 (ref T target, Element element) { 186 187 alias currentMember = __traits(getMember, T, member); 188 alias memberType = typeof(currentMember); 189 190 debug(HUNT_DEBUG_MORE) { 191 infof("deserializing member in %s: %s %s", T.stringof, memberType.stringof, member); 192 } 193 194 static if(hasUDA!(currentMember, Ignore) || hasUDA!(__traits(getMember, T, member), XmlIgnore)) { 195 version(HUNT_DEBUG) { 196 infof("Ignore a member: %s %s", memberType.stringof, member); 197 } 198 } else { 199 static if(is(memberType == interface) && !is(memberType : XmlSerializable)) { 200 version(HUNT_DEBUG) warning("skipped a interface member (not XmlSerializable): " ~ member); 201 } else { 202 alias xmlAttributeUDAs = getUDAs!(currentMember, XmlAttribute); 203 alias xmlElementUDAs = getUDAs!(currentMember, XmlElement); 204 static if(xmlAttributeUDAs.length > 0 && xmlElementUDAs.length > 0) { 205 static assert(false, "Can't use both XmlAttribute and XmlElement at the same time"); 206 } 207 208 static if(xmlAttributeUDAs.length > 0) { 209 enum ElementName = xmlAttributeUDAs[0].name; 210 enum elementName = (ElementName.length == 0) ? member : ElementName; 211 static if(isAssociativeArray!(memberType)) { 212 // TODO: Tasks pending completion -@zhangxueping at 2019-12-04T15:15:54+08:00 213 // 214 __traits(getMember, target, member) = toObject!(memberType, false)(element); 215 } else { 216 Attribute att = element.firstAttribute(elementName); 217 if(att is null) { 218 version(HUNT_DEBUG) warningf("No data available for member: %s", member); 219 } else { 220 __traits(getMember, target, member) = fromAttribute!(memberType, false)(att); 221 } 222 } 223 224 } else static if(xmlElementUDAs.length > 0) { 225 enum ElementName = xmlElementUDAs[0].name; 226 enum elementName = (ElementName.length == 0) ? member : ElementName; 227 Element ele = element.firstNode(elementName); 228 if(ele is null) { 229 version(HUNT_DEBUG) warningf("No data available for member: %s", member); 230 } else { 231 __traits(getMember, target, member) = toObject!(memberType, false)(ele); 232 } 233 } else { 234 enum elementName = member; 235 Element ele = element.firstNode(elementName); 236 if(ele is null) { 237 version(HUNT_DEBUG) { 238 warningf("No data available for member: %s, type: %s", member, memberType.stringof); 239 } 240 } else { 241 static if(is(memberType == class) || (is(memberType == struct) && !is(memberType == SysTime))) { 242 ele = ele.firstNode(memberType.stringof); 243 } 244 245 debug(HUNT_DEBUG_MORE) { 246 if(ele is null) { 247 warningf("No data available for member: %s, type: %s", member, memberType.stringof); 248 } else { 249 tracef("Element name: %s, text: %s, elementName: %s", 250 ele.getName(), ele.getText(), elementName); 251 } 252 } 253 __traits(getMember, target, member) = toObject!(memberType)(ele); 254 } 255 } 256 } 257 } 258 } 259 260 private static T fromAttribute(T, bool canThrow = false)(Attribute attribute) { 261 debug(HUNT_DEBUG_MORE) info(attribute.toString()); 262 string value = attribute.getValue(); 263 static if(isSomeString!T) { 264 return value; 265 } else static if(isBasicType!T) { 266 return to!T(value); 267 } else { 268 warning("TODO: " ~ T.stringof); 269 return T.init; 270 } 271 } 272 273 274 /** 275 * XmlSerializable 276 * 277 * Params: 278 * element = 279 * T.init = 280 * Returns: 281 */ 282 static T toObject(T, bool canThrow = false)(Element element, T defaultValue = T.init) 283 if(is(T == interface) && is(T : XmlSerializable)) { 284 285 Attribute attribute = element.firstAttribute(MetaTypeName); 286 if(attribute is null) { 287 warningf("Can't find the attribute '%s' in %s", MetaTypeName, element.getName()); 288 return T.init; 289 } 290 291 string typeId = attribute.getValue(); 292 T t = cast(T) Object.factory(typeId); 293 if(t is null) { 294 warningf("Can't create instance for %s", T.stringof); 295 } 296 t.xmlDeserialize(element); 297 return t; 298 } 299 300 /** 301 * 302 */ 303 static T toObject(T, bool canThrow = false)(Element element, T defaultValue = T.init) 304 if(is(T == SysTime)) { 305 306 Attribute attribute = element.firstAttribute("format"); 307 if(attribute is null) { 308 warningf("Can't find the attribute 'format' in %s", element.getName()); 309 return T.init; 310 } 311 312 Element txtElement = element.firstNode(); 313 string value = txtElement.getText(); 314 debug(HUNT_DEBUG_MORE) trace(txtElement.toString()); 315 316 string name = attribute.getValue(); 317 try { 318 if(name == "std") { 319 return SysTime(value.to!long()); // STD time 320 } else { 321 return SysTime.fromSimpleString(value); 322 } 323 } catch(Exception ex ){ 324 handleException!(T, canThrow)(element, "Wrong SysTime type", defaultValue); 325 } 326 327 return T.init; 328 } 329 330 /** 331 * string, int, long etc. 332 * 333 * Params: 334 * element = 335 * T.init = 336 * Returns: 337 */ 338 static T toObject(T, bool canThrow = false)(Element element, T defaultValue = T.init) 339 if (isNumeric!T || isSomeString!T) { 340 341 Element txtElement = element; 342 if(element.getType() != NodeType.Text) { 343 txtElement = element.firstNode(); 344 } 345 346 debug(HUNT_DEBUG_MORE) { 347 trace(element.toString()); 348 } 349 350 try { 351 352 if(txtElement is null) { 353 version(HUNT_DEBUG) warningf("No text element for [%s], so use its default.", element.toString()); 354 return T.init; 355 } else { 356 debug(HUNT_DEBUG_MORE) trace(txtElement.toString()); 357 string text = txtElement.getText(); 358 static if (isSomeString!T) { 359 return text; 360 } else { 361 return text.to!T; 362 } 363 } 364 } catch(Exception ex) { 365 return handleException!(T, canThrow)(element, ex.msg, defaultValue); 366 } 367 } 368 369 /** 370 * string[], byte[], int[] etc. 371 * 372 * Params: 373 * element = 374 * 375 * T.init = 376 * Returns: 377 */ 378 static T toObject(T : U[], bool canThrow = false, U) 379 (Element element, U defaultValue = U.init) 380 if (isSomeString!U || (isBasicType!U && !isSomeString!T)) { 381 382 Appender!T appender; 383 debug(HUNT_DEBUG_MORE) { 384 trace(element.toString()); 385 } 386 387 Element currentElement = element.firstNode(); 388 while(currentElement !is null) { 389 Element txtElement = currentElement.firstNode(); 390 debug(HUNT_DEBUG_MORE) { 391 trace(currentElement.toString()); 392 if(txtElement is null) { 393 warning("TxtElement is null"); 394 } else { 395 infof(txtElement.toString()); 396 } 397 } 398 399 string v = txtElement.getText(); 400 static if(isSomeString!U) { 401 appender.put(v); 402 } else { 403 try { 404 appender.put(v.to!U()); 405 } catch(Exception ex) { 406 handleException(txtElement, ex.msg, defaultValue); 407 } 408 } 409 410 currentElement = currentElement.nextSibling(); 411 } 412 413 return appender.data; 414 } 415 416 /** 417 * class[] or struct[] 418 * 419 * Params: 420 * element = 421 * U.init = 422 * Returns: 423 */ 424 static T toObject(T : U[], bool canThrow = false, U)(Element element, U defaultValue = U.init) 425 if (is(U == class) || is(U==struct)) { 426 427 enum TraverseBase traverseBase = TraverseBase.yes; 428 429 debug(HUNT_DEBUG_MORE) trace(element.toString()); 430 Appender!T appender; 431 Element currentElement = element.firstNode(); 432 433 while(currentElement !is null) { 434 debug(HUNT_DEBUG_MORE) trace(currentElement.toString()); 435 static if(is(U == SysTime)) { 436 U v = toObject!(SysTime)(currentElement); 437 appender.put(v); 438 } else { 439 U v = toObject!(U, traverseBase, canThrow)(currentElement); 440 appender.put(v); 441 } 442 currentElement = currentElement.nextSibling(); 443 } 444 445 return appender.data; 446 } 447 448 /** 449 * V[K] 450 * 451 * Params: 452 * element = 453 * T.init = 454 * Returns: 455 */ 456 static T toObject(T : V[K], bool childNodeStyle = true, bool canThrow = false, V, K)( 457 Element element, T defaultValue = T.init) if (isAssociativeArray!T) { 458 459 T result; 460 461 static if(is(V == class) || is(V == struct)) { 462 warning("TODO: " ~ T.stringof); 463 } 464 debug(HUNT_DEBUG_MORE) trace(element.toString()); 465 466 static if(childNodeStyle) { 467 Element currentElement = element.firstNode(); 468 while(currentElement !is null) { 469 debug(HUNT_DEBUG_MORE) trace(currentElement.toString()); 470 string key = currentElement.getName(); 471 472 static if(is(V == class) || is(V == struct)) { 473 // TODO: Tasks pending completion -@zhangxueping at 2019-12-04T15:01:09+08:00 474 // 475 } else { 476 Element txtElement = currentElement.firstNode(); 477 debug(HUNT_DEBUG_MORE) { 478 if(txtElement is null) { 479 warning("TxtElement is null"); 480 } else { 481 infof(txtElement.toString()); 482 } 483 } 484 485 static if(isSomeString!V) { 486 string v = txtElement.getText(); 487 static if(isSomeString!V) { 488 result[key] = v; 489 } else { 490 try { 491 result[key] = v.to!V(); 492 } catch(Exception ex) { 493 handleException(txtElement, ex.msg, defaultValue); 494 } 495 } 496 } 497 } 498 499 currentElement = currentElement.nextSibling(); 500 } 501 502 } else { 503 Attribute attr = element.firstAttribute(); 504 while(attr !is null) { 505 debug(HUNT_DEBUG_MORE) trace(attr.toString()); 506 string key = attr.getName(); 507 string v = attr.getValue(); 508 509 static if(is(V == class) || is(V == struct)) { 510 // TODO: Tasks pending completion -@zhangxueping at 2019-12-04T15:01:09+08:00 511 // 512 } else static if(isSomeString!V) { 513 static if(isSomeString!V) { 514 result[key] = v; 515 } else { 516 try { 517 result[key] = v.to!V(); 518 } catch(Exception ex) { 519 handleException(txtElement, ex.msg, defaultValue); 520 } 521 } 522 } 523 attr = attr.nextAttribute(); 524 } 525 } 526 527 return result; 528 } 529 530 private static T handleException(T, bool canThrow = false) (Element element, 531 string message, T defaultValue = T.init) { 532 static if (canThrow) { 533 throw new XmlException(element.toString() ~ " is not a " ~ T.stringof ~ " type"); 534 } else { 535 version (HUNT_DEBUG) 536 warningf(" %s is not a %s type. Using the defaults instead! \n Exception: %s", 537 element.toString(), T.stringof, message); 538 return defaultValue; 539 } 540 } 541 542 543 /* -------------------------------------------------------------------------- */ 544 /* toDocument */ 545 /* -------------------------------------------------------------------------- */ 546 547 548 /** 549 * class 550 */ 551 static Document toDocument(int depth=-1, T)(T value) if (is(T == class)) { 552 enum options = SerializationOptions().depth(depth); 553 return toDocument!(options)(value); 554 } 555 556 /// ditto 557 static Document toDocument(SerializationOptions options, T) 558 (T value) if (is(T == class)) { 559 560 debug(HUNT_DEBUG_MORE) { 561 info("======== current type: class " ~ T.stringof); 562 tracef("%s, T: %s", 563 options, T.stringof); 564 } 565 566 Document doc = new Document(); 567 Element rootNode; 568 static if(is(T : XmlSerializable)) { 569 // Using XmlSerializable first 570 rootNode = toXmlElement!(XmlSerializable, IncludeMeta.no)("", value); 571 } else { 572 rootNode = serializeObject!(options, T)(value); 573 } 574 575 doc.appendNode(rootNode); 576 return doc; 577 } 578 579 580 /** 581 * XmlSerializable 582 */ 583 static Document toDocument(IncludeMeta includeMeta = IncludeMeta.yes, T) 584 (T value) if (is(T == interface) && is(T : XmlSerializable)) { 585 586 debug(HUNT_DEBUG_MORE) { 587 info("======== current type: interface " ~ T.stringof); 588 } 589 590 Document doc = new Document(); 591 Element rootNode = toXmlElement!(XmlSerializable, includeMeta)("", value); 592 doc.appendNode(rootNode); 593 return doc; 594 } 595 596 597 /** 598 * class object 599 */ 600 static Element serializeObject(SerializationOptions options = SerializationOptions.Full, T) 601 (T value) if (is(T == class)) { 602 import std.traits : isSomeFunction, isType; 603 604 debug(HUNT_DEBUG_MORE) { 605 info("======== current type: class " ~ T.stringof); 606 tracef("%s, T: %s", options, T.stringof); 607 // tracef("traverseBase = %s, onlyPublic = %s, includeMeta = %s, T: %s", 608 // traverseBase, onlyPublic, includeMeta, T.stringof); 609 } 610 611 if (value is null) { 612 version(HUNT_DEBUG) warning("value is null"); 613 return new Document(); 614 } 615 616 alias xmlRootUDAs = getUDAs!(T, XmlRootElement); 617 static if(xmlRootUDAs.length > 0) { 618 enum RootNodeName = xmlRootUDAs[0].name; 619 } else { 620 enum RootNodeName = T.stringof; 621 } 622 623 Element rootNode = new Element(RootNodeName); 624 static if(options.includeMeta) { 625 Attribute attribute = new Attribute(MetaTypeName, typeid(T).name); 626 rootNode.appendAttribute(attribute); 627 } 628 // debug(HUNT_DEBUG_MORE) pragma(msg, "======== current type: class " ~ T.stringof); 629 630 // super fields 631 static if(options.traverseBase) { 632 alias baseClasses = BaseClassesTuple!T; 633 static if(baseClasses.length >= 1) { 634 debug(HUNT_DEBUG_MORE) { 635 tracef("baseClasses[0]: %s", baseClasses[0].stringof); 636 } 637 static if(!is(baseClasses[0] == Object)) { 638 Element superResult = serializeObject!(options, baseClasses[0])(value); 639 if(superResult !is null) { 640 rootNode.appendNode(superResult); 641 } 642 } 643 } 644 } 645 646 // current fields 647 static foreach (string member; FieldNameTuple!T) { 648 serializeMember!(member, options)(value, rootNode); 649 } 650 651 return rootNode; 652 } 653 654 655 /** 656 * struct 657 */ 658 static Document toDocument(SerializationOptions options = SerializationOptions(), T)(T value) 659 if (is(T == struct) && !is(T == SysTime)) { 660 661 auto result = new Document(); 662 debug(HUNT_DEBUG_MORE) info("======== current type: struct " ~ T.stringof); 663 664 static foreach (string member; FieldNameTuple!T) { 665 serializeMember!(member, options)(value, result); 666 } 667 668 return result; 669 } 670 671 /** 672 * Object's memeber 673 */ 674 private static void serializeMember(string member, 675 SerializationOptions options = SerializationOptions.Default, T) 676 (T obj, Element parent) { 677 678 // debug(HUNT_DEBUG_MORE) pragma(msg, "\tfield=" ~ member); 679 680 alias currentMember = __traits(getMember, T, member); 681 682 static if(options.onlyPublic) { 683 static if (__traits(getProtection, currentMember) == "public") { 684 enum canSerialize = true; 685 } else { 686 enum canSerialize = false; 687 } 688 } else static if(hasUDA!(currentMember, Ignore) || hasUDA!(currentMember, XmlIgnore)) { 689 enum canSerialize = false; 690 } else { 691 enum canSerialize = true; 692 } 693 694 debug(HUNT_DEBUG_MORE) { 695 tracef("name: %s, %s", member, options); 696 } 697 698 static if(canSerialize) { 699 alias memberType = typeof(currentMember); 700 debug(HUNT_DEBUG_MORE) infof("memberType: %s in %s", memberType.stringof, T.stringof); 701 702 static if(is(memberType == interface) && !is(memberType : XmlSerializable)) { 703 version(HUNT_DEBUG) warning("skipped a interface member(not XmlSerializable): " ~ member); 704 } else { 705 auto m = __traits(getMember, obj, member); 706 alias xmlAttributeUDAs = getUDAs!(currentMember, XmlAttribute); 707 alias xmlElementUDAs = getUDAs!(currentMember, XmlElement); 708 static if(xmlAttributeUDAs.length > 0 && xmlElementUDAs.length > 0) { 709 static assert(false, "Cna't use both XmlAttribute and XmlElement at one time"); 710 } 711 712 static if(xmlAttributeUDAs.length > 0) { 713 enum ElementName = xmlAttributeUDAs[0].name; 714 enum elementName = (ElementName.length == 0) ? member : ElementName; 715 serializeMemberAsAttribute!(options, member, elementName)(m, parent); 716 } else static if(xmlElementUDAs.length > 0) { 717 enum ElementName = xmlElementUDAs[0].name; 718 enum elementName = (ElementName.length == 0) ? member : ElementName; 719 serializeMemberAsElement!(options, member, elementName)(m, parent); 720 } else { 721 enum elementName = member; 722 serializeMemberAsElement!(options, member, elementName)(m, parent); 723 } 724 } 725 } else { 726 debug(HUNT_DEBUG_MORE) tracef("skipped member, name: %s", member); 727 } 728 } 729 730 /** 731 * Object member 732 * 733 * Params: 734 * name = 735 * m = 736 * Returns: 737 */ 738 private static Element serializeObjectMember(SerializationOptions options = 739 SerializationOptions.Default, T)(string name, ref T m) { 740 enum depth = options.depth; 741 static if(depth > 0) { 742 enum SerializationOptions memeberOptions = options.depth(options.depth-1); 743 return toXmlElement!(memeberOptions)(name, m); 744 } else static if(depth == -1) { 745 return toXmlElement!(options)(name, m); 746 } else { 747 warningf("Reach at the specified depth: %d", depth); 748 return null; 749 } 750 } 751 752 private static void serializeMemberAsAttribute(SerializationOptions options, 753 string member, string elementName, T)(T m, Element parent) { 754 // 755 Node node; 756 static if(isSomeString!T) { 757 Attribute attribute = new Attribute(elementName, m); 758 parent.appendAttribute(attribute); 759 node = attribute; 760 } else static if (isBasicType!(T)) { 761 Attribute attribute = new Attribute(elementName, m.to!string()); 762 parent.appendAttribute(attribute); 763 node = attribute; 764 } else static if (is(T : V[K], V, K)) { 765 Element element = toXmlElement!(options, false)(elementName, m); 766 parent.appendNode(element); 767 node = element; 768 } else { 769 static assert(false, "Only basic type or string can be set as an attribute: " ~ T.stringof); 770 } 771 772 debug(HUNT_DEBUG_MORE) { 773 if(node is null) 774 tracef("member: %s, node: null", member); 775 else 776 tracef("member: %s, node: { %s }", member, node.toString()); 777 } 778 } 779 780 private static void serializeMemberAsElement(SerializationOptions options, 781 string member, string elementName, T)(T m, Element parent) { 782 783 assert(parent !is null, "The parent can't be null"); 784 785 Element element; 786 enum depth = options.depth; 787 788 static if(is(T == interface) && is(T : XmlSerializable)) { 789 static if(depth == -1 || depth > 0) { element = toXmlElement!(XmlSerializable)(elementName, m); } 790 } else static if(is(T == SysTime)) { 791 element = toXmlElement(elementName, m); 792 } else static if(isSomeString!T) { 793 element = toXmlElement(elementName, m); 794 } else static if(is(T == class)) { 795 if(m !is null) { 796 element = serializeObjectMember!(options)(elementName, m); 797 } 798 } else static if(is(T == struct)) { 799 element = serializeObjectMember!(options)(elementName, m); 800 } else static if(is(T : U[], U)) { 801 if(m is null) { 802 static if(!options.ignoreNull) { 803 element = toXmlElement(elementName, m); 804 } 805 } else { 806 static if (is(U == class) || is(U == struct) || is(U == interface)) { 807 // class[] obj; struct[] obj; 808 element = serializeObjectMember!(options)(elementName, m); 809 } else { 810 element = toXmlElement(elementName, m); 811 } 812 } 813 } else { 814 element = toXmlElement(elementName, m); 815 } 816 817 debug(HUNT_DEBUG_MORE) { 818 if(element is null) 819 tracef("member: %s, element: null", member); 820 else 821 tracef("member: %s, element: { %s }", member, element.toString()); 822 } 823 824 bool canSetValue = true; 825 if(element is null) { 826 static if(options.ignoreNull) { 827 canSetValue = false; 828 } 829 } 830 831 if (canSetValue) { 832 auto existNode = parent.firstNode(elementName); 833 if(existNode !is null) { 834 version(HUNT_DEBUG) warning("overrided field: " ~ member); 835 } 836 837 if(element !is null) { 838 parent.appendNode(element); 839 } else { 840 warningf("skipping null element for: %s %s", T.stringof, member); 841 } 842 } 843 } 844 845 /** 846 * SysTime 847 */ 848 static Element toXmlElement(string name, ref SysTime value, bool asInteger=true) { 849 if(name.empty) name = SysTime.stringof; 850 Element result = new Element(name); 851 Element txt = new Element(NodeType.Text); 852 853 string timeFormat = "std"; 854 855 if(asInteger) { 856 txt.setText(value.stdTime().to!string()); // STD time 857 } else { 858 timeFormat = "simple"; 859 txt.setText(value.toString()); 860 } 861 862 Attribute attribute = new Attribute("format", timeFormat); 863 result.appendAttribute(attribute); 864 865 result.appendNode(txt); 866 return result; 867 } 868 869 870 /** 871 * XmlSerializable 872 */ 873 static Element toXmlElement(T, IncludeMeta includeMeta = IncludeMeta.yes) 874 (string name, T value) if (is(T == interface) && is(T : XmlSerializable)) { 875 debug(HUNT_DEBUG_MORE) { 876 infof("======== current type: interface = %s, Object = %s", 877 T.stringof, typeid(cast(Object)value).name); 878 } 879 880 Element result = value.xmlSerialize(); 881 if(result is null) { 882 return null; 883 } 884 885 if(!name.empty()) 886 result.setName(name); 887 888 static if(includeMeta) { 889 Attribute attribute = new Attribute(MetaTypeName, typeid(cast(Object)value).name); 890 result.appendAttribute(attribute); 891 // auto itemPtr = MetaTypeName in v; 892 // FIXME: Needing refactor or cleanup -@zhangxueping at 2019-12-02T15:32:27+08:00 893 // 894 // if(itemPtr is null) 895 // v[MetaTypeName] = typeid(cast(Object)value).name; 896 } 897 898 return result; 899 } 900 901 /** 902 * Basic types or string 903 * 904 * Params: 905 * value = 906 * Returns: 907 */ 908 static Element toXmlElement(T)(string name, T value) if (isBasicType!T || isSomeString!(T)) { 909 static if(isSomeString!(T)) { 910 string v = value; 911 } else { 912 string v = to!string(value); 913 } 914 915 Element result = new Element(name); 916 if(!v.empty) { 917 Element txt = new Element(NodeType.Text); 918 txt.setText(v); 919 result.appendNode(txt); 920 } 921 922 return result; 923 } 924 925 /** 926 * 927 */ 928 static Element toXmlElement(SerializationOptions options = SerializationOptions.Normal, T) 929 (string name, T value) if (is(T == class)) { 930 931 Element result = new Element(name); 932 if(value !is null) { 933 Element c = serializeObject!(options)(value); 934 result.appendNode(c); 935 } 936 937 return result; 938 } 939 940 /** 941 * 942 */ 943 static Element toXmlElement(SerializationOptions options = SerializationOptions.Normal, T) 944 (string name, T value) if (is(T == struct)) { 945 946 Element result = new Element(name); 947 Element o = serializeObject!(options)(value); 948 result.appendNode(o); 949 950 return result; 951 } 952 953 /** 954 * string[], byte[], int[] etc. 955 */ 956 static Element toXmlElement(T : U[], U)(string name, T value) 957 if ((isBasicType!U && !isSomeString!T) || isSomeString!U) { 958 959 Element roolElement = new Element(name); 960 961 if(value !is null) { 962 value.map!(item => toXmlElement(U.stringof, item)) 963 .each!((item) { 964 if(item !is null) roolElement.appendNode(item); 965 })(); 966 } 967 968 return roolElement; 969 } 970 971 /** 972 * class[] 973 */ 974 static Element toXmlElement(SerializationOptions options = SerializationOptions.Normal, 975 T : U[], U) (string name, T value) if(is(T : U[], U) && is(U == class)) { 976 977 Element roolElement = new Element(name); 978 if(value !is null) { 979 value.map!(item => serializeObject!(options)(item)) 980 .each!((item) { 981 if(item !is null) roolElement.appendNode(item); 982 })(); 983 } 984 985 return roolElement; 986 } 987 988 989 /** 990 * struct[] 991 */ 992 static Element toXmlElement(SerializationOptions options = SerializationOptions.Normal, 993 T : U[], U)(string name, T value) if(is(U == struct)) { 994 995 Element roolElement = new Element(name); 996 997 if(value !is null) { 998 static if(is(U == SysTime)) { 999 value.map!(item => toXmlElement("", item)) 1000 .each!((item) { 1001 if(item !is null) roolElement.appendNode(item); 1002 })(); 1003 } else { 1004 value.map!(item => serializeObject!(options)(item))() 1005 .each!((item) { 1006 if(item !is null) roolElement.appendNode(item); 1007 })(); 1008 } 1009 } 1010 1011 return roolElement; 1012 } 1013 1014 /** 1015 * V[K] 1016 */ 1017 static Element toXmlElement(SerializationOptions options = SerializationOptions.Normal, bool childNodeStyle = true, 1018 T : V[K], V, K)(string name, T value) { 1019 Element result = new Element(name); 1020 1021 static if(childNodeStyle) { 1022 1023 foreach (ref K key; value.keys) { 1024 1025 static if(isSomeString!K) { 1026 string keyName = key; 1027 } else { 1028 string keyName = key.to!string(); 1029 } 1030 1031 static if(is(V == SysTime)) { 1032 Element element = toXmlElement(keyName, value[key]); 1033 result.appendNode(element); 1034 1035 } else static if(is(V == class) || is(V == struct) || is(V == interface)) { 1036 Element element = toXmlElement!(options)(keyName, value[key]); 1037 result.appendNode(element); 1038 1039 } else { 1040 Element element = toXmlElement(keyName, value[key]); 1041 result.appendNode(element); 1042 } 1043 } 1044 1045 } else { 1046 1047 foreach (ref K key; value.keys) { 1048 1049 static if(isSomeString!K) { 1050 string keyName = key; 1051 } else { 1052 string keyName = key.to!string(); 1053 } 1054 1055 Attribute attribute; 1056 static if(isSomeString!V) { 1057 attribute = new Attribute(keyName, value[key]); 1058 } else { 1059 attribute = new Attribute(keyName, value[key].to!string()); 1060 } 1061 result.appendAttribute(attribute); 1062 } 1063 } 1064 return result; 1065 } 1066 } 1067 1068 1069 alias toDocument = XmlSerializer.toDocument; 1070 alias toObject = XmlSerializer.toObject;