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;