001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.converter;
020
021import java.io.IOException;
022import java.io.StringReader;
023import java.io.Writer;
024import java.util.Date;
025import java.util.HashMap;
026import java.util.Iterator;
027import java.util.List;
028import java.util.ListIterator;
029import java.util.Map;
030import java.util.TimeZone;
031
032import javax.xml.parsers.FactoryConfigurationError;
033
034import lucee.commons.lang.NumberUtil;
035import lucee.runtime.Component;
036import lucee.runtime.ComponentScope;
037import lucee.runtime.ComponentSpecificAccess;
038import lucee.runtime.PageContext;
039import lucee.runtime.component.Property;
040import lucee.runtime.engine.ThreadLocalPageContext;
041import lucee.runtime.exp.PageException;
042import lucee.runtime.op.Caster;
043import lucee.runtime.op.date.DateCaster;
044import lucee.runtime.orm.ORMUtil;
045import lucee.runtime.text.xml.XMLUtil;
046import lucee.runtime.type.Array;
047import lucee.runtime.type.ArrayImpl;
048import lucee.runtime.type.Collection;
049import lucee.runtime.type.Collection.Key;
050import lucee.runtime.type.KeyImpl;
051import lucee.runtime.type.Query;
052import lucee.runtime.type.QueryImpl;
053import lucee.runtime.type.Struct;
054import lucee.runtime.type.StructImpl;
055import lucee.runtime.type.UDF;
056import lucee.runtime.type.dt.DateTime;
057import lucee.runtime.type.dt.DateTimeImpl;
058import lucee.runtime.type.util.CollectionUtil;
059import lucee.runtime.type.util.ComponentProUtil;
060import lucee.runtime.type.util.ComponentUtil;
061import lucee.runtime.type.util.KeyConstants;
062
063import org.apache.xerces.parsers.DOMParser;
064import org.w3c.dom.Document;
065import org.w3c.dom.Element;
066import org.w3c.dom.Node;
067import org.w3c.dom.NodeList;
068import org.xml.sax.InputSource;
069
070/**
071 * class to serialize and desirilize WDDX Packes
072 */
073public final class XMLConverter extends ConverterSupport {
074        private static final Collection.Key REMOTING_FETCH = KeyImpl.intern("remotingFetch");
075        
076        private int deep=1;
077        private char del='"';
078        private TimeZone timeZone;
079        private boolean ignoreRemotingFetch=true;
080    //private PageContext pcx;
081
082        private String type;
083
084        private int id=0;
085
086        /**
087         * constructor of the class
088         * @param timeZone 
089         * @param xmlConform define if generated xml conform output or wddx conform output (wddx is not xml conform)
090         */
091        public XMLConverter(TimeZone timeZone,boolean ignoreRemotingFetch) {
092                this.timeZone=timeZone;
093                this.ignoreRemotingFetch=ignoreRemotingFetch;
094        }
095        
096        /**
097         * defines timezone info will
098         * @param timeZone
099         */
100        public void setTimeZone(TimeZone timeZone) {
101                this.timeZone=timeZone;
102        }
103
104        /**
105         * serialize a Date
106         * @param date Date to serialize
107         * @return serialized date
108         * @throws ConverterException
109         */
110        private String _serializeDate(Date date) {
111                return _serializeDateTime(new DateTimeImpl(date));
112        }
113        /**
114         * serialize a DateTime
115         * @param dateTime DateTime to serialize
116         * @return serialized dateTime
117         * @throws ConverterException
118         */
119        private String _serializeDateTime(DateTime dateTime) {
120                /* ACF FORMAT
121                String strDate = new lucee.runtime.format.DateFormat(Locale.US).format(dateTime,"mmmm, dd yyyy");
122                String strTime = new lucee.runtime.format.TimeFormat(Locale.US).format(dateTime,"HH:mm:ss");
123                return goIn()+strDate+" "+strTime;
124                */
125                return goIn()+JSONDateFormat.format(dateTime,null);
126        }
127
128        /**
129         * serialize a Array
130         * @param array Array to serialize
131         * @param done 
132         * @return serialized array
133         * @throws ConverterException
134         */
135        private String _serializeArray(Array array, Map<Object,String> done, String id) throws ConverterException {
136                return _serializeList(array.toList(),done,id);
137        }
138        
139        /**
140         * serialize a List (as Array)
141         * @param list List to serialize
142         * @param done 
143         * @return serialized list
144         * @throws ConverterException
145         */
146        private String _serializeList(List list, Map<Object,String> done, String id) throws ConverterException {
147                // <ARRAY ID="1" SIZE="1"><ITEM INDEX="1" TYPE="STRING">hello world</ITEM></ARRAY>
148                StringBuilder sb=new StringBuilder(goIn()+"<ARRAY ID=\""+id+"\" SIZE="+del+list.size()+del+">");
149                int index;
150                ListIterator it=list.listIterator();
151                while(it.hasNext()) {
152                        //<ITEM INDEX="1" TYPE="STRING">hello world</ITEM>
153                        index=it.nextIndex();
154                        String value = _serialize(it.next(),done);
155            sb.append(goIn()+"<ITEM INDEX=\""+(index+1)+"\" TYPE=\""+type+"\">");
156            sb.append(value);
157            sb.append(goIn()+"</ITEM>");
158                }
159                
160                sb.append(goIn()+"</ARRAY>");
161                type="ARRAY";
162                return sb.toString();
163        }
164
165        /**
166         * serialize a Component
167         * @param component Component to serialize
168         * @param done 
169         * @return serialized component
170         * @throws ConverterException 
171         */
172        private String _serializeComponent(Component component, Map<Object,String> done) throws ConverterException {
173                StringBuilder sb=new StringBuilder();
174                Component ca;
175                component=new ComponentSpecificAccess(Component.ACCESS_PRIVATE, ca=component);
176                boolean isPeristent=ComponentProUtil.isPersistent(ca);
177
178        deep++;
179        Object member;
180        Iterator<Key> it = component.keyIterator();
181        Collection.Key key;
182        while(it.hasNext()) {
183                key=it.next();
184                member = component.get(key,null);
185                if(member instanceof UDF) continue;
186                sb.append(goIn()+"<var scope=\"this\" name="+del+key.toString()+del+">");
187            sb.append(_serialize(member,done));
188            sb.append(goIn()+"</var>");
189        }
190
191        Property p;
192        Boolean remotingFetch;
193        Struct props = ignoreRemotingFetch?null:ComponentUtil.getPropertiesAsStruct(ca,false);
194        ComponentScope scope = ca.getComponentScope();
195        it=scope.keyIterator();
196        while(it.hasNext()) {
197                key=Caster.toKey(it.next(),null);
198                if(!ignoreRemotingFetch) {
199                        p=(Property) props.get(key,null);
200                if(p!=null) {
201                        remotingFetch=Caster.toBoolean(p.getDynamicAttributes().get(REMOTING_FETCH,null),null);
202                        if(remotingFetch==null){
203                                        if(isPeristent  && ORMUtil.isRelated(p)) continue;
204                                }
205                                else if(!remotingFetch.booleanValue()) continue;
206                }
207                }
208                
209                member = scope.get(key,null);
210                if(member instanceof UDF || key.equals(KeyConstants._this)) continue;
211            sb.append(goIn()+"<var scope=\"variables\" name="+del+key.toString()+del+">");
212            sb.append(_serialize(member,done));
213            sb.append(goIn()+"</var>");
214        }
215        
216        
217        deep--;
218        try {
219                        //return goIn()+"<struct>"+sb+"</struct>";
220                        return goIn()+"<component md5=\""+ComponentUtil.md5(component)+"\" name=\""+component.getAbsName()+"\">"+sb+"</component>";
221                } 
222                catch (Exception e) {
223                        throw toConverterException(e);
224                }
225        }
226
227        /**
228         * serialize a Struct
229         * @param struct Struct to serialize
230         * @param done 
231         * @return serialized struct
232         * @throws ConverterException
233         */
234        private String _serializeStruct(Struct struct, Map<Object,String> done, String id) throws ConverterException {
235        StringBuilder sb=new StringBuilder(goIn()+"<STRUCT ID=\""+id+"\">");
236        
237        Iterator<Key> it = struct.keyIterator();
238
239        deep++;
240        while(it.hasNext()) {
241            Key key = it.next();
242            // <ENTRY NAME="STRING" TYPE="STRING">hello</ENTRY>
243            String value = _serialize(struct.get(key,null),done);
244            sb.append(goIn()+"<ENTRY NAME=\""+key.toString()+"\" TYPE=\""+type+"\">");
245            sb.append(value);
246            sb.append(goIn()+"</ENTRY>");
247        }
248        deep--;
249        
250        sb.append(goIn()+"</STRUCT>");
251        type="STRUCT";
252        return sb.toString();
253        }
254
255        /**
256         * serialize a Map (as Struct)
257         * @param map Map to serialize
258         * @param done 
259         * @return serialized map
260         * @throws ConverterException
261         */
262        private String _serializeMap(Map map, Map<Object,String> done) throws ConverterException {
263                StringBuilder sb=new StringBuilder(goIn()+"<struct>");
264                
265                Iterator it=map.keySet().iterator();
266
267                deep++;
268                while(it.hasNext()) {
269                        Object key=it.next();
270                        sb.append(goIn()+"<var name="+del+key.toString()+del+">");
271                        sb.append(_serialize(map.get(key),done));
272                        sb.append(goIn()+"</var>");
273                }
274                deep--;
275                
276                sb.append(goIn()+"</struct>");
277                return sb.toString();
278        }
279
280        /**
281         * serialize a Query
282         * @param query Query to serialize
283         * @param done 
284         * @return serialized query
285         * @throws ConverterException
286         */
287        private String _serializeQuery(Query query, Map<Object,String> done, String id) throws ConverterException {
288                
289                /*<QUERY ID="1">
290                 * <COLUMNNAMES>
291                 * <COLUMN NAME="a"></COLUMN>
292                 * <COLUMN NAME="b"></COLUMN>
293                 * </COLUMNNAMES>
294                 * 
295                 * <ROWS>
296                 *      <ROW>
297                 *      <COLUMN TYPE="STRING">a1</COLUMN>
298                 *  <COLUMN TYPE="STRING">b1</COLUMN>
299                 *  </ROW>
300                 *  <ROW>
301                 *  <COLUMN TYPE="STRING">a2</COLUMN>
302                 *  <COLUMN TYPE="STRING">b2</COLUMN>
303                 *  </ROW>
304                 *  </ROWS>
305                 *  </QUERY>
306                */
307                Collection.Key[] keys = CollectionUtil.keys(query);
308                StringBuilder sb=new StringBuilder(goIn()+"<QUERY ID=\""+id+"\">");
309                
310                // columns
311                sb.append(goIn()+"<COLUMNNAMES>");
312                for(int i=0;i<keys.length;i++) {
313                        sb.append(goIn()+"<COLUMN NAME=\""+keys[i].getString()+"\"></COLUMN>");
314                }
315                sb.append(goIn()+"</COLUMNNAMES>");
316                
317                String value;
318                deep++;
319                sb.append(goIn()+"<ROWS>");
320                int len=query.getRecordcount();
321                for(int row=1;row<=len;row++) {
322                        sb.append(goIn()+"<ROW>");
323                        for(int col=0;col<keys.length;col++) {
324                                try {
325                                        value=_serialize(query.getAt(keys[col],row),done);
326                                } catch (PageException e) {
327                                        value=_serialize(e.getMessage(),done);
328                                }
329                                sb.append("<COLUMN TYPE=\""+type+"\">"+value+"</COLUMN>");
330                        }
331                        sb.append(goIn()+"</ROW>");
332                        
333                }
334                sb.append(goIn()+"</ROWS>");
335                deep--;
336                
337                sb.append(goIn()+"</QUERY>");
338                type="QUERY";
339                return sb.toString();
340        }
341        
342        /**
343         * serialize a Object to his xml Format represenation
344         * @param object Object to serialize
345         * @param done 
346         * @return serialized Object
347         * @throws ConverterException
348         */
349        private String _serialize(Object object, Map<Object,String> done) throws ConverterException {
350
351                type="OBJECT";
352                
353                String rtn;
354                deep++;
355                // NULL
356                if(object==null) {
357                        rtn= goIn()+"";
358                        deep--;
359                        type="NULL";
360                        return rtn;
361                }
362                // String
363                if(object instanceof String) {
364                        rtn= goIn()+XMLUtil.escapeXMLString(object.toString());
365                        deep--;
366                        type="STRING";
367                        return rtn;
368                }
369                // Number
370                if(object instanceof Number) {
371                        rtn= goIn()+((Number)object).doubleValue();
372                        deep--;
373                        type="NUMBER";
374                        return rtn;
375                }
376                // Boolean
377                if(object instanceof Boolean) {
378                        rtn= goIn()+((Boolean)object).booleanValue();
379                        deep--;
380                        type="BOOLEAN";
381                        return rtn;
382                }
383                // DateTime
384                if(object instanceof DateTime) {
385                        rtn= _serializeDateTime((DateTime)object);
386                        deep--;
387                        type="DATE";
388                        return rtn;
389                }
390                // Date
391                if(object instanceof Date) {
392                        rtn= _serializeDate((Date)object);
393                        deep--;
394                        type="DATE";
395                        return rtn;
396                }
397
398                Object raw = LazyConverter.toRaw(object);
399                String strId=done.get(raw);
400                if(strId!=null){
401                        rtn= goIn()+"<REF id=\""+strId+"\"\\>";
402                        deep--;
403                        type="NULL";
404                        return rtn;
405                }
406                strId=Caster.toString(++this.id);
407                done.put(raw,strId);
408                try {
409                        // Component
410                        if(object instanceof Component) {
411                                rtn= _serializeComponent((Component)object,done);
412                                deep--;
413                                return rtn;
414                        }
415                        // Struct
416                        if(object instanceof Struct) {
417                                rtn= _serializeStruct((Struct)object,done,strId);
418                                deep--;
419                                return rtn;
420                        }
421                        // Map
422                        if(object instanceof Map) {
423                                rtn= _serializeMap((Map)object,done);
424                                deep--;
425                                return rtn;
426                        }
427                        // Array
428                        if(object instanceof Array) {
429                                rtn= _serializeArray((Array)object,done,strId);
430                                deep--;
431                                return rtn;
432                        }
433                        // List
434                        if(object instanceof List) {
435                                rtn= _serializeList((List)object,done,strId);
436                                deep--;
437                                return rtn;
438                        }
439                        // Query
440                        if(object instanceof Query) {
441                                rtn= _serializeQuery((Query)object,done,strId);
442                                deep--;
443                                return rtn;
444                        }
445                }
446                finally{
447                        done.remove(raw);
448                }
449                // Others
450                rtn="<STRUCT ID=\""+strId+"\" TYPE=\""+Caster.toTypeName(object)+"\"></STRUCT>";
451                deep--;
452                return rtn;
453        }
454
455        @Override
456        public void writeOut(PageContext pc, Object source, Writer writer) throws ConverterException, IOException {
457                writer.write(serialize(source));
458                writer.flush();
459        }
460        
461        /**
462         * serialize a Object to his xml Format represenation and create a valid wddx representation
463         * @param object Object to serialize
464         * @return serialized wddx package
465         * @throws ConverterException
466         */
467        public String serialize(Object object) throws ConverterException {
468                deep=0;
469                
470                StringBuilder sb=new StringBuilder();   
471                //if(xmlConform)sb.append("<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");     
472                deep++;
473                sb.append(_serialize(object,new HashMap<Object,String>()));
474                deep--;
475                return sb.toString();
476        }
477        
478
479        /**
480         * deserialize a WDDX Package (XML String Representation) to a runtime object
481         * @param strWddx
482         * @param validate
483         * @return Object represent WDDX Package
484         * @throws ConverterException
485         * @throws IOException
486         * @throws FactoryConfigurationError
487         */
488        public Object deserialize(String strWddx, boolean validate) throws ConverterException, IOException, FactoryConfigurationError {
489                try {
490                        DOMParser parser = new DOMParser();
491                        if(validate) parser.setEntityResolver(new WDDXEntityResolver());
492                        
493            parser.parse(new InputSource(new StringReader(strWddx)));
494            Document doc=parser.getDocument();
495                    
496                    // WDDX Package
497                    NodeList docChldren = doc.getChildNodes();
498                    Node wddxPacket=doc;
499                    int len = docChldren.getLength();
500                    for(int i = 0; i < len; i++) {
501                        Node node=docChldren.item(i);
502                        if(node.getNodeName().equalsIgnoreCase("wddxPacket")) {
503                                wddxPacket=node;
504                                break;
505                        }
506                    }
507
508                        NodeList nl = wddxPacket.getChildNodes();
509                        int n = nl.getLength();
510
511                        
512                        for(int i = 0; i < n; i++) {
513                                Node data = nl.item(i);
514                                if(data.getNodeName().equals("data")) {
515                                        NodeList list=data.getChildNodes();
516                                        len=list.getLength();
517                                        for(int y=0;y<len;y++) {
518                                                Node node=list.item(y);
519                                                if(node instanceof Element)
520                                                        return _deserialize((Element)node);
521                                                
522                                        }
523                                }
524                        }
525                        
526                        throw new IllegalArgumentException("Invalid WDDX Format: node 'data' not found in WDD packet");
527
528                }
529                catch(org.xml.sax.SAXException sxe) {
530                        throw new IllegalArgumentException("XML Error: " + sxe.toString());
531                }
532        }
533        
534        
535        
536        /**
537         * deserialize a WDDX Package (XML Element) to a runtime object
538         * @param element
539         * @return deserialized Element
540         * @throws ConverterException
541         */
542        private Object _deserialize(Element element) throws ConverterException {
543                String nodeName=element.getNodeName().toLowerCase();
544                
545                // NULL
546                if(nodeName.equals("null")) {
547                        return null;
548                }
549                // String
550                else if(nodeName.equals("string")) {
551                        return _deserializeString(element);
552                        /*Node data=element.getFirstChild();
553                        if(data==null) return "";
554                        
555                        String value=data.getNodeValue();
556                        
557                        if(value==null) return "";
558                        return XMLUtil.unescapeXMLString(value);*/
559                }
560                // Number
561                else if(nodeName.equals("number")) {
562                        try {
563                                Node data=element.getFirstChild();
564                                if(data==null) return new Double(0);
565                                return Caster.toDouble(data.getNodeValue());
566                        } catch (Exception e) {
567                                throw toConverterException(e);
568                        }
569                }
570                // Boolean
571                else if(nodeName.equals("boolean")) {
572                        try {
573                                return Caster.toBoolean(element.getAttribute("value"));
574                        } catch (PageException e) {
575                                throw toConverterException(e);
576                                
577                        }
578                }
579                // Array
580                else if(nodeName.equals("array")) {
581                        return _deserializeArray(element);
582                }
583                // Component
584                else if(nodeName.equals("component")) {
585                        return  _deserializeComponent(element);
586                }
587                // Struct
588                else if(nodeName.equals("struct")) {
589                        return  _deserializeStruct(element);
590                }
591                // Query
592                else if(nodeName.equals("recordset")) {
593                        return  _deserializeQuery(element);
594                }
595                // DateTime
596                else if(nodeName.equalsIgnoreCase("dateTime")) {
597                        try {
598                                return DateCaster.toDateAdvanced(element.getFirstChild().getNodeValue(),timeZone);
599                        } 
600            catch (Exception e) {
601                                throw toConverterException(e);
602                        } 
603                }
604                else 
605                        throw new ConverterException("can't deserialize Element of type ["+nodeName+"] to a Object representation");
606                
607        }
608
609        private Object _deserializeString(Element element) {
610                NodeList childList = element.getChildNodes();
611                int len = childList.getLength();
612                StringBuilder sb=new StringBuilder();
613                Node data;
614                String str;
615                for(int i=0;i<len;i++) {
616                        data=childList.item(i);
617                        if(data==null)continue;
618                        
619                        //<char code="0a"/>
620                        if("char".equals(data.getNodeName())) {
621                                str=((Element)data).getAttribute("code");
622                                sb.append((char)NumberUtil.hexToInt(str, 10));
623                        }
624                        else {
625                                sb.append(str=data.getNodeValue());
626                        }
627                }
628                return sb.toString();
629                //return XMLUtil.unescapeXMLString(sb.toString());
630        }
631
632        /**
633         * Desirialize a Query Object
634         * @param recordset Query Object as XML Element
635         * @return Query Object
636         * @throws ConverterException
637         */
638        private Object _deserializeQuery(Element recordset) throws ConverterException {
639                try {
640                        // create Query Object
641                        Query query=new QueryImpl(
642                                        lucee.runtime.type.util.ListUtil.listToArray(
643                                                        recordset.getAttribute("fieldNames"),','
644                                        )
645                                ,Caster.toIntValue(recordset.getAttribute("rowCount")),"query"
646                        );
647                        
648                        NodeList list = recordset.getChildNodes();
649                        int len=list.getLength();
650                        for(int i=0;i<len;i++) {
651                                Node node=list.item(i);
652                                if(node instanceof Element) {
653                                        _deserializeQueryField(query,(Element) node);
654                                }                       
655                        }
656                        return query;
657                }
658                catch(PageException e) {
659                        throw toConverterException(e);
660                }
661                
662        }
663
664        /**
665         * deserilize a single Field of a query WDDX Object
666         * @param query
667         * @param field
668         * @throws ConverterException 
669         * @throws PageException
670         */
671        private void _deserializeQueryField(Query query,Element field) throws PageException, ConverterException {
672                String name=field.getAttribute("name");
673                NodeList list = field.getChildNodes();
674                int len=list.getLength();
675                int count=0;
676                for(int i=0;i<len;i++) {
677                        Node node=list.item(i);
678                        if(node instanceof Element) {
679                                query.setAt(name,++count,_deserialize((Element) node));
680                        }                       
681                }
682                
683        }
684        
685        /**
686         * Desirialize a Component Object
687         * @param elComp Component Object as XML Element
688         * @return Component Object
689         * @throws ConverterException 
690         * @throws ConverterException
691         */
692        private Object _deserializeComponent(Element elComp) throws ConverterException {
693//              String type=elStruct.getAttribute("type");
694                String name=elComp.getAttribute("name");
695                String md5=elComp.getAttribute("md5");
696                
697                // TLPC
698                PageContext pc = ThreadLocalPageContext.get();
699                
700                // Load comp
701                Component comp=null;
702                try {
703                        comp = pc.loadComponent(name);
704                        if(!ComponentUtil.md5(comp).equals(md5)){
705                                throw new ConverterException("component ["+name+"] in this enviroment has not the same interface as the component to load, it is possible that one off the components has Functions added dynamicly.");
706                        }
707                } 
708                catch (ConverterException e) {
709                        throw e;
710                }
711                catch (Exception e) {
712                        throw new ConverterException(e.getMessage());
713                }
714                
715                
716                NodeList list=elComp.getChildNodes();
717                ComponentScope scope = comp.getComponentScope();
718                int len=list.getLength();
719                String scopeName;
720                Element var,value;
721                Collection.Key key;
722                for(int i=0;i<len;i++) {
723            Node node=list.item(i);
724                        if(node instanceof Element) {
725                                var=(Element)node;
726                                value=getChildElement((Element)node);
727                                scopeName=var.getAttribute("scope");
728                                if(value!=null) {
729                                        key=Caster.toKey(var.getAttribute("name"),null);
730                                        if(key==null) continue;
731                                        if("variables".equalsIgnoreCase(scopeName))
732                                                scope.setEL(key,_deserialize(value));
733                                        else
734                                                comp.setEL(key,_deserialize(value));
735                                }
736            }
737                }
738        return comp;
739        }
740
741        /**
742         * Desirialize a Struct Object
743         * @param elStruct Struct Object as XML Element
744         * @return Struct Object
745         * @throws ConverterException
746         */
747        private Object _deserializeStruct(Element elStruct) throws ConverterException {
748                String type=elStruct.getAttribute("type");
749                Struct struct=new StructImpl();
750        
751                NodeList list=elStruct.getChildNodes();
752                int len=list.getLength();
753                for(int i=0;i<len;i++) {
754            //print.ln(i);
755            
756                        Node node=list.item(i);
757                        if(node instanceof Element) {
758                                Element var=(Element)node;
759                                Element value=getChildElement((Element)node);
760                                if(value!=null) {
761                                        struct.setEL(var.getAttribute("name"),_deserialize(value));
762                                        
763                                }
764            }
765                }
766        if(struct.size()==0 && type!=null && type.length()>0) {
767            return "";
768        }        
769                return struct;
770        }
771
772        /**
773         * Desirialize a Struct Object
774         * @param el Struct Object as XML Element
775         * @return Struct Object
776         * @throws ConverterException
777         */
778        private Array _deserializeArray(Element el) throws ConverterException {
779                Array array=new ArrayImpl();
780                
781                NodeList list=el.getChildNodes();
782                int len=list.getLength();
783                for(int i=0;i<len;i++) {
784                        Node node=list.item(i);
785                        if(node instanceof Element)
786                                try {
787                                        array.append(_deserialize((Element)node));
788                                } catch (PageException e) {
789                                        throw toConverterException(e);
790                                }
791                        
792                }
793                return array;
794        }
795
796        /**
797         * return fitst child Element of a Element, if there are no child Elements return null
798         * @param parent parent node
799         * @return child Element
800         */
801        private Element getChildElement(Element parent) {
802                NodeList list=parent.getChildNodes();
803                int len=list.getLength();
804                for(int i=0;i<len;i++) {
805                        Node node=list.item(i);
806                        if(node instanceof Element) {
807                                return (Element)node;
808                        }                       
809                }
810                return null;
811        }
812        
813        
814        /**
815         * @return return current blockquote
816         */
817        private String goIn() {
818                //StringBuilder rtn=new StringBuilder(deep);
819                //for(int i=0;i<deep;i++) rtn.append('\t');
820                //return rtn.toString();
821                return "";
822        }
823
824    @Override
825    public boolean equals(Object obj) {
826        return timeZone.equals(obj);
827    }
828}