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