001    package railo.runtime.text.xml;
002    
003    import java.io.ByteArrayInputStream;
004    import java.io.File;
005    import java.io.IOException;
006    import java.io.InputStream;
007    import java.io.Reader;
008    import java.io.StringReader;
009    import java.io.StringWriter;
010    import java.util.ArrayList;
011    import java.util.Iterator;
012    import java.util.List;
013    import java.util.Map;
014    
015    import javax.xml.parsers.DocumentBuilder;
016    import javax.xml.parsers.DocumentBuilderFactory;
017    import javax.xml.parsers.FactoryConfigurationError;
018    import javax.xml.parsers.ParserConfigurationException;
019    import javax.xml.transform.Transformer;
020    import javax.xml.transform.TransformerException;
021    import javax.xml.transform.TransformerFactory;
022    import javax.xml.transform.dom.DOMResult;
023    import javax.xml.transform.dom.DOMSource;
024    import javax.xml.transform.sax.SAXSource;
025    import javax.xml.transform.stream.StreamResult;
026    import javax.xml.transform.stream.StreamSource;
027    
028    import org.apache.xalan.processor.TransformerFactoryImpl;
029    import org.apache.xerces.jaxp.DocumentBuilderFactoryImpl;
030    import org.ccil.cowan.tagsoup.Parser;
031    import org.w3c.dom.Attr;
032    import org.w3c.dom.CDATASection;
033    import org.w3c.dom.CharacterData;
034    import org.w3c.dom.Comment;
035    import org.w3c.dom.Document;
036    import org.w3c.dom.Element;
037    import org.w3c.dom.NamedNodeMap;
038    import org.w3c.dom.Node;
039    import org.w3c.dom.NodeList;
040    import org.w3c.dom.Text;
041    import org.xml.sax.InputSource;
042    import org.xml.sax.SAXException;
043    import org.xml.sax.XMLReader;
044    import org.xml.sax.helpers.XMLReaderFactory;
045    
046    import railo.commons.io.IOUtil;
047    import railo.commons.io.res.Resource;
048    import railo.commons.io.res.util.ResourceUtil;
049    import railo.commons.lang.StringUtil;
050    import railo.runtime.PageContext;
051    import railo.runtime.engine.ThreadLocalPageContext;
052    import railo.runtime.exp.ExpressionException;
053    import railo.runtime.exp.PageException;
054    import railo.runtime.exp.XMLException;
055    import railo.runtime.op.Caster;
056    import railo.runtime.op.Decision;
057    import railo.runtime.text.xml.struct.XMLMultiElementStruct;
058    import railo.runtime.text.xml.struct.XMLStruct;
059    import railo.runtime.text.xml.struct.XMLStructFactory;
060    import railo.runtime.type.Array;
061    import railo.runtime.type.ArrayImpl;
062    import railo.runtime.type.Collection;
063    import railo.runtime.type.Collection.Key;
064    import railo.runtime.type.KeyImpl;
065    import railo.runtime.type.Struct;
066    
067    /**
068     * 
069     */
070    public final class XMLUtil {
071        public static final Collection.Key XMLCOMMENT = KeyImpl.intern("xmlcomment");
072        public static final Collection.Key XMLTEXT = KeyImpl.intern("xmltext");
073        public static final Collection.Key XMLCDATA = KeyImpl.intern("xmlcdata");
074        public static final Collection.Key XMLCHILDREN = KeyImpl.intern("xmlchildren");
075        public static final Collection.Key XMLNODES = KeyImpl.intern("xmlnodes");
076        public static final Collection.Key XMLNSURI = KeyImpl.intern("xmlnsuri");
077        public static final Collection.Key XMLNSPREFIX = KeyImpl.intern("xmlnsprefix");
078        public static final Collection.Key XMLROOT = KeyImpl.intern("xmlroot");
079        public static final Collection.Key XMLPARENT = KeyImpl.intern("xmlparent");
080        public static final Collection.Key XMLNAME = KeyImpl.intern("xmlname");
081        public static final Collection.Key XMLTYPE = KeyImpl.intern("xmltype");
082        public static final Collection.Key XMLVALUE = KeyImpl.intern("xmlvalue");
083        public static final Collection.Key XMLATTRIBUTES = KeyImpl.intern("xmlattributes");
084            /*
085            private static final Collection.Key  = KeyImpl.getInstance();
086            private static final Collection.Key  = KeyImpl.getInstance();
087            private static final Collection.Key  = KeyImpl.getInstance();
088            private static final Collection.Key  = KeyImpl.getInstance();
089            private static final Collection.Key  = KeyImpl.getInstance();
090            private static final Collection.Key  = KeyImpl.getInstance();
091            */
092        
093        
094        
095            //static DOMParser parser = new DOMParser();
096            private static DocumentBuilder docBuilder;
097            //private static DocumentBuilderFactory factory;
098        private static TransformerFactory transformerFactory;
099            
100    
101        public static String unescapeXMLString(String str) {
102            
103            StringBuffer rtn=new StringBuffer();
104            int posStart=-1;
105            int posFinish=-1;
106            while((posStart=str.indexOf('&',posStart))!=-1) {
107                    int last=posFinish+1;
108                    
109                    posFinish=str.indexOf(';',posStart);
110                    if(posFinish==-1)break;
111                    rtn.append(str.substring(last,posStart));
112                    if(posStart+1<posFinish) {
113                            rtn.append(unescapeXMLEntity(str.substring(posStart+1,posFinish)));
114                    }
115                    else {
116                            rtn.append("&;");
117                    }
118                    
119                    posStart=posFinish+1;
120            }
121            rtn.append(str.substring(posFinish+1));
122            return rtn.toString();
123        }
124    
125        public static String unescapeXMLString2(String str) {
126    
127            StringBuffer sb=new StringBuffer();
128            int index,last=0,indexSemi;
129            while((index=str.indexOf('&',last))!=-1) {
130                    sb.append(str.substring(last,index));
131                    indexSemi=str.indexOf(';',index+1);
132    
133                    if(indexSemi==-1) {
134                            sb.append('&');
135                            last=index+1;
136                    }
137                    else if(index+1==indexSemi) {
138                            sb.append("&;");
139                            last=index+2;
140                    }
141                    else {
142                            sb.append(unescapeXMLEntity(str.substring(index+1,indexSemi)));
143                            last=indexSemi+1;
144                    }
145            }
146            sb.append(str.substring(last));
147            return sb.toString();
148        }
149        
150        
151        
152        
153        private static String unescapeXMLEntity(String str) {
154            if("lt".equals(str)) return "<";
155            if("gt".equals(str)) return ">";
156            if("amp".equals(str)) return "&";
157            if("apos".equals(str)) return "'";
158            if("quot".equals(str)) return "\"";
159                    return "&"+str+";";
160            }
161    
162            public static String escapeXMLString(String xmlStr) {
163            char c;
164            StringBuffer sb=new StringBuffer();
165            int len=xmlStr.length();
166            for(int i=0;i<len;i++) {
167                    c=xmlStr.charAt(i);
168                    if(c=='<')           sb.append("&lt;");
169                    else if(c=='>')      sb.append("&gt;");
170                    else if(c=='&')     sb.append("&amp;");
171                    //else if(c=='\'')      sb.append("&amp;");
172                    else if(c=='"') sb.append("&quot;");
173                    //else if(c>127) sb.append("&#"+((int)c)+";");
174                    else sb.append(c);
175            }
176            return sb.toString();
177        }
178        
179        
180        /**
181         * @return returns a singelton TransformerFactory
182         */
183        public static TransformerFactory getTransformerFactory() {
184            // Saxon
185            //if(transformerFactory==null)transformerFactory=new com.icl.saxon.TransformerFactoryImpl();
186            // Xalan
187            if(transformerFactory==null)transformerFactory=new TransformerFactoryImpl();
188            // Trax
189            //if(transformerFactory==null)transformerFactory=new com.jclark.xsl.trax.TransformerFactoryImpl();
190            // Trax
191            //if(transformerFactory==null)transformerFactory=new jd.xml.xslt.trax.TransformerFactoryImpl();
192            // Caucho
193            //if(transformerFactory==null)transformerFactory=new Xsl();
194            // xsltc
195            //if(transformerFactory==null)transformerFactory=new org.apache.xalan.xsltc.trax.TransformerFactoryImpl();
196            
197            
198            return transformerFactory;
199        }
200        
201        /**
202         * parse XML/HTML String to a XML DOM representation
203         * @param xml XML InputSource
204         * @param isHtml is a HTML or XML Object
205         * @return parsed Document
206         * @throws SAXException
207         * @throws IOException
208         * @throws ParserConfigurationException 
209         */
210        public static final Document parse(InputSource xml,InputSource validator, boolean isHtml) 
211            throws SAXException, IOException {
212            
213            if(!isHtml) {
214                    // try to load org.apache.xerces.jaxp.DocumentBuilderFactoryImpl, oracle impl sucks
215                    DocumentBuilderFactory factory = null;
216                    try{
217                            factory = new DocumentBuilderFactoryImpl();
218                    }
219                    catch(Throwable t) {
220                            factory = DocumentBuilderFactory.newInstance();
221                    }
222                    
223                    //print.o(factory);
224                if(validator==null) {
225                    XMLUtil.setAttributeEL(factory,XMLConstants.NON_VALIDATING_DTD_EXTERNAL, Boolean.FALSE);
226                    XMLUtil.setAttributeEL(factory,XMLConstants.NON_VALIDATING_DTD_GRAMMAR, Boolean.FALSE);
227                }
228                else {
229                    XMLUtil.setAttributeEL(factory,XMLConstants.VALIDATION_SCHEMA, Boolean.TRUE);
230                    XMLUtil.setAttributeEL(factory,XMLConstants.VALIDATION_SCHEMA_FULL_CHECKING, Boolean.TRUE);
231                
232                    
233                }
234                
235                
236                factory.setNamespaceAware(true);
237                factory.setValidating(validator!=null);
238                
239                try {
240                                    DocumentBuilder builder = factory.newDocumentBuilder();
241                        builder.setEntityResolver(new XMLEntityResolverDefaultHandler(validator));
242                        builder.setErrorHandler(new ThrowingErrorHandler(true,true,false));
243                        return  builder.parse(xml);
244                            } 
245                catch (ParserConfigurationException e) {
246                                    throw new SAXException(e);
247                            }
248                
249                    /*DOMParser parser = new DOMParser();
250                    print.out("parse");
251                    parser.setEntityResolver(new XMLEntityResolverDefaultHandler(validator));
252                    parser.parse(xml);
253                    return parser.getDocument();*/
254            }
255            
256            XMLReader reader = new Parser();
257                reader.setFeature(Parser.namespacesFeature, true);
258                reader.setFeature(Parser.namespacePrefixesFeature, true);
259            
260            try {
261                Transformer transformer = TransformerFactory.newInstance().newTransformer();
262                
263                DOMResult result = new DOMResult();
264                transformer.transform(new SAXSource(reader, xml), result);
265                return XMLUtil.getDocument(result.getNode());
266            } 
267            catch (Exception e) {
268                throw new SAXException(e);
269            }
270        }
271            
272            private static void setAttributeEL(DocumentBuilderFactory factory,String name, Object value) {
273                    try{
274                            factory.setAttribute(name, value);
275                    }
276                    catch(Throwable t){
277                            //SystemOut.printDate("attribute ["+name+"] is not allowed for ["+factory.getClass().getName()+"]");
278                    }
279            }
280    
281            /**
282             * sets a node to a node (Expression Less)
283             * @param node
284             * @param key
285             * @param value
286             * @return Object set
287             */
288            public static Object setPropertyEL(Node node, Collection.Key key, Object value) {
289                    try {
290                            return setProperty(node,key,value);
291                    } catch (PageException e) {
292                            return null;
293                    }
294            }
295            public static Object setProperty(Node node, Collection.Key key, Object value,boolean caseSensitive, Object defaultValue) {
296                    try {
297                            return setProperty(node,key,value,caseSensitive);
298                    } catch (PageException e) {
299                            return defaultValue;
300                    }
301            }
302            
303            /**
304             * sets a node to a node
305             * @param node
306             * @param key
307             * @param value
308             * @return Object set
309             * @throws PageException
310             */
311    
312            public static Object setProperty(Node node, Collection.Key k, Object value) throws PageException {
313                    return setProperty(node, k, value, isCaseSensitve(node));
314            }
315            
316            public static Object setProperty(Node node, Collection.Key k, Object value,boolean caseSensitive) throws PageException {
317                    Document doc=(node instanceof Document)?(Document)node:node.getOwnerDocument();
318                    // Comment
319                            if(k.equals(XMLCOMMENT)) {
320                                    removeChildren(XMLCaster.toRawNode(node),Node.COMMENT_NODE,false);
321                                    node.appendChild(XMLCaster.toRawNode(XMLCaster.toComment(doc,value)));
322                            }
323                    // NS URI
324                            else if(k.equals(XMLNSURI)) {
325                                    // TODO impl
326                                    throw new ExpressionException("XML NS URI can't be set","not implemented");
327                            }
328                    // Prefix
329                            else if(k.equals(XMLNSPREFIX)) {
330                                    // TODO impl
331                                    throw new ExpressionException("XML NS Prefix can't be set","not implemented");
332                                    //node.setPrefix(Caster.toString(value));
333                            }                       
334                    // Root
335                            else if(k.equals(XMLROOT)) {
336                                    doc.appendChild(XMLCaster.toNode(doc,value));
337                            }                       
338                    // Parent
339                            else if(k.equals(XMLPARENT)) {
340                                    Node parent = getParentNode(node,caseSensitive);
341                                    Key name = KeyImpl.init(parent.getNodeName());
342                                    parent = getParentNode(parent,caseSensitive);
343                                    
344                                    if(parent==null)
345                                            throw new ExpressionException("there is no parent element, you are already on the root element");
346                                    
347                                    return setProperty(parent, name, value, caseSensitive);
348                            }
349                    // Name 
350                            else if(k.equals(XMLNAME)) {
351                                    throw new XMLException("You can't assign a new value for the property [xmlname]");
352                            }
353                    // Type 
354                            else if(k.equals(XMLTYPE)) {
355                                    throw new XMLException("You can't change type of a xml node [xmltype]");
356                            }
357                    // value        
358                            else if(k.equals(XMLVALUE)) {
359                                    node.setNodeValue(Caster.toString(value));
360                            }
361                    // Attributes   
362                            else if(k.equals(XMLATTRIBUTES)) {
363                                    Element parent=XMLCaster.toElement(doc,node);
364                                    Attr[] attres=XMLCaster.toAttrArray(doc,value);
365                                    //print.ln("=>"+value);
366                                    for(int i=0;i<attres.length;i++) {
367                                            if(attres[i]!=null) {
368                                                    parent.setAttributeNode(attres[i]);
369                                                    //print.ln(attres[i].getName()+"=="+attres[i].getValue());
370                                            }
371                                    }
372                            }
373                    // Text 
374                            else if(k.equals(XMLTEXT)) {
375                                    removeChildCharacterData(XMLCaster.toRawNode(node),false);
376                                    node.appendChild(XMLCaster.toRawNode(XMLCaster.toText(doc,value)));
377                            }
378                    // CData        
379                            else if(k.equals(XMLCDATA)) {
380                                    removeChildCharacterData(XMLCaster.toRawNode(node),false);
381                                    node.appendChild(XMLCaster.toRawNode(XMLCaster.toCDATASection(doc,value)));
382                            }
383                    // Children     
384                            else if(k.equals(XMLCHILDREN) || k.equals(XMLNODES)) {
385                                    Node[] nodes=XMLCaster.toNodeArray(doc,value);
386                                    removeChildren(XMLCaster.toRawNode(node),Node.ELEMENT_NODE,false);
387                                    for(int i=0;i<nodes.length;i++) {
388                                            if(nodes[i]==node) throw new XMLException("can't assign a XML Node to himself");
389                                            if(nodes[i]!=null)node.appendChild(XMLCaster.toRawNode(nodes[i]));
390                                    }
391                            }
392                            else {
393                                    boolean isIndex=false;
394                                Node child = XMLCaster.toNode(doc,value);
395                                    if(!k.getString().equalsIgnoreCase(child.getNodeName()) && !(isIndex=Decision.isInteger(k))) {
396                                            throw new XMLException("if you assign a XML Element to a XMLStruct , assignment property must have same name like XML Node Name", "Property Name is "+k.getString()+" and XML Element Name is "+child.getNodeName());
397                                    }
398                                    Node n;
399                                    
400                                    // by index
401                                    if(isIndex) {
402                                            NodeList list = XMLUtil.getChildNodes(node.getParentNode(), Node.ELEMENT_NODE,true,node.getNodeName());
403                                            int len = list.getLength();
404                                            
405                                            
406                                            int index=Caster.toIntValue(k);
407                                            if(index>len || index<1){
408                                                    String detail=len>1?
409                                                                    "your index is "+index+", but there are only "+len+" child elements":
410                                                                    "your index is "+index+", but there is only "+len+" child element";
411                                                    
412                                                    
413                                                    throw new XMLException("index is out of range", detail);
414                                            }
415                                            n=list.item(index-1);
416                                            XMLUtil.replaceChild(child, n);
417                                            return value;
418                                    }
419                                    
420                                    
421                                    
422                                    
423                                    NodeList list = XMLUtil.getChildNodes(node, Node.ELEMENT_NODE);
424                                    int len = list.getLength();
425                                    
426                                    // by name
427                                    for(int i=0;i<len;i++) {
428                                            n=list.item(i);
429                                            if(nameEqual(n, k.getString(), caseSensitive)) {
430                                                    XMLUtil.replaceChild(child, n);
431                                                    return value;
432                                            }
433                                    }
434                                    node.appendChild(XMLCaster.toRawNode(child));
435                            }
436                            
437                            return value;
438            }
439    
440    
441            public static void replaceChild(Node newChild, Node oldChild) {
442                    
443    
444                    Node nc = XMLCaster.toRawNode(newChild);
445                    Node oc = XMLCaster.toRawNode(oldChild);
446                    Node p = oc.getParentNode();
447    
448                    if(nc!=oc)p.replaceChild(nc, oc);
449            }
450    
451            public static Object getProperty(Node node, Collection.Key key, Object defaultValue) {
452                    return getProperty(node, key,isCaseSensitve(node),defaultValue);
453            }
454            
455            
456            /**
457             * returns a property from a XMl Node  (Expression Less)
458             * @param node
459             * @param key
460             * @param caseSensitive
461             * @return Object matching key
462             */
463            public static Object getProperty(Node node, Collection.Key k,boolean caseSensitive, Object defaultValue) {
464                    try {
465                            return getProperty(node, k,caseSensitive);
466                    } catch (SAXException e) {
467                            return defaultValue;
468                    }
469            }
470            
471            public static Object getProperty(Node node, Collection.Key key) throws SAXException {
472                    return getProperty(node, key,isCaseSensitve(node));
473            }
474            
475            /**
476             * returns a property from a XMl Node
477             * @param node
478             * @param key
479             * @param caseSensitive
480             * @return Object matching key
481             * @throws SAXException
482             */
483            public static Object getProperty(Node node, Collection.Key k,boolean caseSensitive) throws SAXException {
484                //String lcKey=StringUtil.toLowerCase(key);
485                    if(k.getLowerString().startsWith("xml")) {
486                    // Comment
487                            if(k.equals(XMLCOMMENT)) {
488                                    StringBuffer sb=new StringBuffer();
489                                    NodeList list = node.getChildNodes();
490                                    int len=list.getLength();
491                                    for(int i=0;i<len;i++) {
492                                            Node n=list.item(i);
493                                            if(n instanceof Comment) {
494                                                    sb.append(((Comment)n).getData());
495                                            }
496                                    }
497                                    return sb.toString();
498                            }
499                    // NS URI
500                            if(k.equals(XMLNSURI)) {
501                                    undefinedInRoot(k,node);
502                                    return param(node.getNamespaceURI(),"");
503                            }
504                    // Prefix
505                            if(k.equals(XMLNSPREFIX)) {
506                                    undefinedInRoot(k,node);
507                                    return param(node.getPrefix(),"");
508                            }
509                    // Root
510                            else if(k.equals(XMLROOT)) {
511                                    Element re = getRootElement(node,caseSensitive);
512                                    if(re==null) throw new SAXException("Attribute ["+k.getString()+"] not found in XML, XML is empty");
513                                    return param(re,"");
514                            }
515                    // Parent
516                            else if(k.equals(XMLPARENT)) {
517                                    
518                                    Node parent = getParentNode(node,caseSensitive);
519                                    if(parent==null) {
520                                            if(node.getNodeType()==Node.DOCUMENT_NODE)
521                                                    throw new SAXException("Attribute ["+k.getString()+"] not found in XML, there is no parent element, you are already at the root element");
522                                            throw new SAXException("Attribute ["+k.getString()+"] not found in XML, there is no parent element");
523                                    }
524                                    return parent;
525                            }
526                    // Name 
527                            else if(k.equals(XMLNAME)) {
528                                    return node.getNodeName();
529                            }
530                    // Value        
531                            else if(k.equals(XMLVALUE)) {
532                                    return StringUtil.toStringEmptyIfNull(node.getNodeValue());
533                            }
534                    // type 
535                            else if(k.equals(XMLTYPE)) {
536                                    return getTypeAsString(node,true);
537                            }
538                    // Attributes   
539                            else if(k.equals(XMLATTRIBUTES)) {
540                                    NamedNodeMap attr = node.getAttributes();
541                                    
542                                    if(attr==null)throw undefined(k,node);
543                                    return new XMLAttributes(node,caseSensitive);
544                            }
545                    // Text 
546                            else if(k.equals(XMLTEXT)) {
547                                    undefinedInRoot(k,node);
548                                    StringBuffer sb=new StringBuffer();
549                                    NodeList list = node.getChildNodes();
550                                    int len=list.getLength();
551                                    for(int i=0;i<len;i++) {
552                                            Node n=list.item(i);
553                        if(n instanceof Text || n instanceof CDATASection) {
554                            sb.append(((CharacterData)n).getData());
555                                            }
556                                    }
557                    return sb.toString();
558                            }
559                            else if(k.equals(XMLCDATA)) {
560                                    undefinedInRoot(k,node);
561                                    StringBuffer sb=new StringBuffer();
562                                    NodeList list = node.getChildNodes();
563                                    int len=list.getLength();
564                                    for(int i=0;i<len;i++) {
565                                            Node n=list.item(i);
566                        if(n instanceof Text || n instanceof CDATASection) {
567                            sb.append(((CharacterData)n).getData());
568                                            }
569                                    }
570                    return sb.toString();
571                            }
572                            // children     
573                            else if(k.equals(XMLCHILDREN) || k.equals(XMLNODES)) {
574                                    return new XMLNodeList(node,caseSensitive);
575                            }
576                    }
577                    
578                    if(node instanceof Document) {
579                        node=((Document)node).getDocumentElement();
580                        if(node==null) throw new SAXException("Attribute ["+k.getString()+"] not found in XML, XML is empty");
581                        
582                        //if((!caseSensitive && node.getNodeName().equalsIgnoreCase(k.getString())) || (caseSensitive && node.getNodeName().equals(k.getString()))) {
583                        if(nameEqual(node, k.getString(), caseSensitive)) {
584                                    return XMLStructFactory.newInstance(node,caseSensitive);
585                            }
586                    }
587                    else if(node.getNodeType()==Node.ELEMENT_NODE && Decision.isInteger(k)){
588                            int index=Caster.toIntValue(k,0);
589                            int count=0;
590                            Node parent = node.getParentNode();
591                            String nodeName=node.getNodeName();
592                            Element[] children = XMLUtil.getChildElementsAsArray(parent);
593                                    
594                            for(int i=0;i<children.length;i++){
595                                    if(XMLUtil.nameEqual(children[i],nodeName,caseSensitive)) count++;
596                                    
597                                    if(count==index) return XMLCaster.toXMLStruct(children[i],caseSensitive);
598                            }
599                            String detail;
600                            if(count==0)detail="there are no Elements with this name";
601                            else if(count==1)detail="there is only 1 Element with this name";
602                            else detail="there are only "+count+" Elements with this name";
603                            throw new SAXException("invalid index ["+k.getString()+"] for Element with name ["+node.getNodeName()+"], "+detail);
604                    }
605                    else {
606                            List<Node> children = XMLUtil.getChildNodesAsList(node,Node.ELEMENT_NODE,caseSensitive,null);
607                            int len=children.size();
608                            Array array=null;//new ArrayImpl();
609                            Element el;
610                            XMLStruct sct=null,first=null;
611                            for(int i=0;i<len;i++) {
612                                    el=(Element) children.get(i);// XMLCaster.toXMLStruct(getChildNode(index),caseSensitive);
613                                    if(XMLUtil.nameEqual(el,k.getString(),caseSensitive)) {
614                                            sct = XMLCaster.toXMLStruct(el,caseSensitive);
615                                            
616                                            if(array!=null) {
617                                                    array.appendEL(sct);
618                                            }
619                                            else if(first!=null) {
620                                                    array=new ArrayImpl();
621                                                    array.appendEL(first);
622                                                    array.appendEL(sct);
623                                            }
624                                            else {
625                                                    first=sct;
626                                            }
627                                    }
628                            }
629                            
630                            if(array!=null) {
631                                    try {
632                                            return new XMLMultiElementStruct(array,false);
633                                    } catch (PageException e) {}
634                            }
635                            if(first!=null) return first;
636                    }
637                    throw new SAXException("Attribute ["+k.getString()+"] not found");
638            }
639    
640    
641        private static SAXException undefined(Key key, Node node) {
642            if(node.getNodeType()==Node.DOCUMENT_NODE)
643                    return new SAXException("you cannot address ["+key+"] on the Document Object, to address ["+key+"]  from the root Node use [{variable-name}.xmlRoot."+key+"]");
644            
645            return new SAXException(key+" is undefined");
646            }
647    
648        private static void undefinedInRoot(Key key, Node node) throws SAXException {
649            if(node.getNodeType()==Node.DOCUMENT_NODE)
650                    throw undefined(key, node);
651            }
652    
653            /**
654         * check if given name is equal to name of the element (with and without namespace)
655         * @param node
656         * @param k
657         * @param caseSensitive
658         * @return
659         */
660        public static boolean nameEqual(Node node, String name, boolean caseSensitive) {
661                    if(name==null) return false;
662            if(caseSensitive){
663                    return name.equals(node.getNodeName()) || name.equals(node.getLocalName());
664            }
665            return name.equalsIgnoreCase(node.getNodeName()) || name.equalsIgnoreCase(node.getLocalName());
666            }
667    
668            public static boolean isCaseSensitve(Node node) {
669                    if(node instanceof XMLStruct) return ((XMLStruct)node).isCaseSensitive();
670            return true;
671            }
672    
673            /**
674         * removes child from a node
675         * @param node
676         * @param key
677         * @param caseSensitive
678         * @return removed property
679         */
680        public static Object removeProperty(Node node, Collection.Key k,boolean caseSensitive) {
681            
682            //String lcKeyx=k.getLowerString();
683            if(k.getLowerString().startsWith("xml")) {
684            // Comment
685                if(k.equals(XMLCOMMENT)) {
686                    StringBuffer sb=new StringBuffer();
687                    NodeList list = node.getChildNodes();
688                    int len=list.getLength();
689                    for(int i=0;i<len;i++) {
690                        Node n=list.item(i);
691                        if(n instanceof Comment) {
692                            sb.append(((Comment)n).getData());
693                            node.removeChild(XMLCaster.toRawNode(n));
694                        }
695                    }
696                    return sb.toString();
697                }
698            // Text 
699                else if(k.equals(XMLTEXT)) {
700                    StringBuffer sb=new StringBuffer();
701                    NodeList list = node.getChildNodes();
702                    int len=list.getLength();
703                    for(int i=0;i<len;i++) {
704                        Node n=list.item(i);
705                        if(n instanceof Text || n instanceof CDATASection) {
706                            sb.append(((CharacterData)n).getData());
707                            node.removeChild(XMLCaster.toRawNode(n));
708                        }
709                    }
710                    return sb.toString();
711                }
712                // children 
713                else if(k.equals(XMLCHILDREN) || k.equals(XMLNODES)) {
714                    NodeList list=node.getChildNodes();
715                    for(int i=list.getLength()-1;i>=0;i--) {
716                        node.removeChild(XMLCaster.toRawNode(list.item(i)));
717                    }
718                    return list;
719                }
720            }
721             
722                NodeList nodes = node.getChildNodes();
723                Array array=new ArrayImpl();
724                for(int i=nodes.getLength()-1;i>=0;i--) {
725                    Object o=nodes.item(i);
726                    if(o instanceof Element) {
727                        Element el=(Element) o;
728                        if(nameEqual(el, k.getString(), caseSensitive)) {
729                            array.appendEL(XMLCaster.toXMLStruct(el,caseSensitive));
730                            node.removeChild(XMLCaster.toRawNode(el));
731                        }
732                    }
733                }
734                
735                if(array.size()>0) {
736                    try {
737                        return new XMLMultiElementStruct(array,false);
738                    } catch (PageException e) {}
739                }
740                return null;
741        }
742        
743    
744            private static Object param(Object o1, Object o2) {
745                    if(o1==null)return o2;
746                    return o1;
747            }
748            
749            /**
750             * return the root Element from a node
751             * @param node node to get root element from
752             * @param caseSensitive
753             * @return Root Element
754             */
755            public static Element getRootElement(Node node, boolean caseSensitive) {
756                Document doc=null;
757                    if(node instanceof Document) doc=(Document) node;
758                    else doc=node.getOwnerDocument();
759                    Element el = doc.getDocumentElement();
760                    if(el==null) return null;
761                    return (Element)XMLStructFactory.newInstance(el,caseSensitive);
762            }
763            
764    
765            public static Node getParentNode(Node node, boolean caseSensitive) {
766                    Node parent = node.getParentNode();
767                if(parent==null) return null;
768                    return XMLStructFactory.newInstance(parent,caseSensitive);
769            }
770    
771            /**
772             * returns a new Empty XMl Document
773             * @return new Document
774             * @throws ParserConfigurationException
775             * @throws FactoryConfigurationError
776             */
777            public static Document newDocument() throws ParserConfigurationException, FactoryConfigurationError {
778                    if(docBuilder==null) {
779                            docBuilder=DocumentBuilderFactory.newInstance().newDocumentBuilder();
780                    }
781                    return docBuilder.newDocument();
782            }
783            
784            /**
785             * return the Owner Document of a Node List
786             * @param nodeList
787             * @return XML Document
788             * @throws XMLException
789             */
790            public static Document getDocument(NodeList nodeList) throws XMLException {
791                    if(nodeList instanceof Document) return (Document)nodeList;
792                    int len=nodeList.getLength();
793                    for(int i=0;i<len;i++) {
794                            Node node=nodeList.item(i);
795                            if(node!=null) return node.getOwnerDocument();
796                    }
797                    throw new XMLException("can't get Document from NodeList, in NoteList are no Nodes");
798            }
799            
800            /**
801             * return the Owner Document of a Node
802             * @param node
803             * @return XML Document
804             */
805            public static Document getDocument(Node node) {
806                    if(node instanceof Document) return (Document)node;
807                    return node.getOwnerDocument();
808            }
809            
810            
811            /**
812             * removes child elements from a specific type
813             * @param node node to remove elements from
814             * @param type Type Definition to remove (Constant value from class Node)
815             * @param deep remove also in sub nodes
816             */
817            private synchronized static void removeChildren(Node node, short type, boolean deep) {
818                    NodeList list = node.getChildNodes();
819                    
820                    for(int i=list.getLength();i>=0;i--) {
821                            Node n=list.item(i);
822                            if(n ==null )continue;
823                            
824                            if(n.getNodeType()==type)node.removeChild(XMLCaster.toRawNode(n));
825                            else if(deep)removeChildren(n,type,deep);
826                    }
827            }
828            
829            /**
830             * remove children from type CharacterData from a node, this includes Text,Comment and CDataSection nodes
831             * @param node
832             * @param type
833             * @param deep
834             */
835            private synchronized static void removeChildCharacterData(Node node, boolean deep) {
836                    NodeList list = node.getChildNodes();
837                    
838                    for(int i=list.getLength();i>=0;i--) {
839                            Node n=list.item(i);
840                            if(n ==null )continue;
841                            
842                            if(n instanceof CharacterData)node.removeChild(XMLCaster.toRawNode(n));
843                            else if(deep)removeChildCharacterData(n,deep);
844                    }
845            }
846    
847            /**
848             * return all Children of a node by a defined type as Node List
849             * @param node node to get children from
850             * @param type type of returned node
851             * @param filter 
852             * @param caseSensitive 
853             * @return all matching child node
854             */
855            public synchronized static ArrayNodeList getChildNodes(Node node, short type) {
856                    return getChildNodes(node, type, false, null);
857            }
858            
859    
860            public synchronized static int childNodesLength(Node node, short type, boolean caseSensitive, String filter) {
861                    NodeList nodes=node.getChildNodes();
862                    int len=nodes.getLength();
863                    Node n;
864                    int count=0;
865                    for(int i=0;i<len;i++) {
866                            try {
867                                    n=nodes.item(i);
868                                    if(n!=null && n.getNodeType()==type){
869                                            if(filter==null || (caseSensitive?filter.equals(n.getLocalName()):filter.equalsIgnoreCase(n.getLocalName())))
870                                            count++;
871                                    }
872                            }
873                            catch(Throwable t){}
874                    }
875                    return count;
876            }
877            
878            public synchronized static ArrayNodeList getChildNodes(Node node, short type, boolean caseSensitive, String filter) {
879                    ArrayNodeList rtn=new ArrayNodeList();
880                    NodeList nodes=node.getChildNodes();
881                    int len=nodes.getLength();
882                    Node n;
883                    for(int i=0;i<len;i++) {
884                            try {
885                                    n=nodes.item(i);
886                                    if(n!=null && n.getNodeType()==type){
887                                            if(filter==null || (caseSensitive?filter.equals(n.getLocalName()):filter.equalsIgnoreCase(n.getLocalName())))
888                                            rtn.add(n);
889                                    }
890                            }
891                            catch(Throwable t){}
892                    }
893                    return rtn;
894            }
895            
896            public synchronized static List<Node> getChildNodesAsList(Node node, short type, boolean caseSensitive, String filter) {
897                    List<Node> rtn=new ArrayList<Node>();
898                    NodeList nodes=node.getChildNodes();
899                    int len=nodes.getLength();
900                    Node n;
901                    for(int i=0;i<len;i++) {
902                            try {
903                                    n=nodes.item(i);
904                                    if(n!=null && n.getNodeType()==type){
905                                            if(filter==null || (caseSensitive?filter.equals(n.getLocalName()):filter.equalsIgnoreCase(n.getLocalName())))
906                                            rtn.add(n);
907                                    }
908                            }
909                            catch(Throwable t){}
910                    }
911                    return rtn;
912            }
913            
914    
915            public synchronized static Node getChildNode(Node node, short type, boolean caseSensitive, String filter, int index) {
916                    NodeList nodes=node.getChildNodes();
917                    int len=nodes.getLength();
918                    Node n;
919                    int count=0;
920                    for(int i=0;i<len;i++) {
921                            try {
922                                    n=nodes.item(i);
923                                    if(n!=null && n.getNodeType()==type){
924                                            if(filter==null || (caseSensitive?filter.equals(n.getLocalName()):filter.equalsIgnoreCase(n.getLocalName()))) {
925                                                    if(count==index) return n;
926                                                    count++;
927                                            }
928                                    }
929                            }
930                            catch(Throwable t){}
931                    }
932                    return null;
933            }
934            
935            
936            
937        /**
938         * return all Children of a node by a defined type as Node Array
939         * @param node node to get children from
940         * @param type type of returned node
941         * @param filter 
942         * @param caseSensitive 
943         * @return all matching child node
944         */
945        public static Node[] getChildNodesAsArray(Node node, short type) {
946            ArrayNodeList nodeList=getChildNodes(node, type);
947            return nodeList.toArray(new Node[nodeList.getLength()]);
948        }
949    
950        public static Node[] getChildNodesAsArray(Node node, short type, boolean caseSensitive, String filter) {
951            ArrayNodeList nodeList=getChildNodes(node, type,caseSensitive,filter);
952            return  nodeList.toArray(new Node[nodeList.getLength()]);
953        }
954        
955        /**
956         * return all Element Children of a node
957         * @param node node to get children from
958         * @return all matching child node
959         */
960        public static Element[] getChildElementsAsArray(Node node) {
961            ArrayNodeList nodeList=getChildNodes(node,Node.ELEMENT_NODE);
962            return  nodeList.toArray(new Element[nodeList.getLength()]);
963        }
964    
965        /**
966         * transform a XML Object to a other format, with help of a XSL Stylesheet
967         * @param xml xml to convert
968         * @param xsl xsl used to convert
969         * @return resulting string
970         * @throws TransformerException
971         * @throws SAXException
972         * @throws IOException
973         */
974        public static String transform(InputSource xml, InputSource xsl) throws TransformerException, SAXException, IOException {
975            return transform( parse( xml, null , false ), xsl, null );
976        }
977        
978        /**
979         * transform a XML Object to a other format, with help of a XSL Stylesheet
980         * @param xml xml to convert
981         * @param xsl xsl used to convert
982         * @param parameters parameters used to convert
983         * @return resulting string
984         * @throws TransformerException
985         * @throws SAXException
986         * @throws IOException
987         */
988        public static String transform(InputSource xml, InputSource xsl, Map parameters) throws TransformerException, SAXException, IOException {
989            return transform( parse( xml, null , false ), xsl, parameters );
990        }
991    
992        /**
993         * transform a XML Document to a other format, with help of a XSL Stylesheet
994         * @param xml xml to convert
995         * @param xsl xsl used to convert
996         * @return resulting string
997         * @throws TransformerException
998         * @throws SAXException
999         * @throws IOException
1000         */
1001        public static String transform( Document doc, InputSource xsl ) throws TransformerException {
1002                    return transform( doc, xsl, null );
1003            }
1004        
1005        /**
1006         * transform a XML Document to a other format, with help of a XSL Stylesheet
1007         * @param xml xml to convert
1008         * @param xsl xsl used to convert
1009         * @param parameters parameters used to convert
1010         * @return resulting string
1011         * @throws TransformerException
1012         * @throws SAXException
1013         * @throws IOException
1014         */
1015        public static String transform(Document doc, InputSource xsl, Map parameters) throws TransformerException {
1016            StringWriter sw = new StringWriter();
1017            TransformerFactory factory = XMLUtil.getTransformerFactory();
1018            factory.setErrorListener(SimpleErrorListener.THROW_FATAL);
1019                    Transformer transformer = factory.newTransformer(new StreamSource(xsl.getCharacterStream()));
1020                    if (parameters != null) {
1021                            Iterator it = parameters.entrySet().iterator();
1022                            while ( it.hasNext() ) {
1023                                    Map.Entry e = (Map.Entry) it.next();
1024                                    transformer.setParameter(e.getKey().toString(), e.getValue());
1025                            }
1026                    }
1027                    transformer.transform(new DOMSource(doc), new StreamResult(sw));
1028                    return sw.toString();
1029            }
1030    
1031        /**
1032         * returns the Node Type As String
1033         * @param node
1034             * @param cftype 
1035             * @return
1036         */
1037            public static String getTypeAsString(Node node, boolean cftype) {
1038                    String suffix=cftype?"":"_NODE";
1039                    
1040            switch(node.getNodeType()) {
1041                    case Node.ATTRIBUTE_NODE:                               return "ATTRIBUTE"+suffix;
1042                    case Node.CDATA_SECTION_NODE:                   return "CDATA_SECTION"+suffix;
1043                    case Node.COMMENT_NODE:                                 return "COMMENT"+suffix;
1044                    case Node.DOCUMENT_FRAGMENT_NODE:               return "DOCUMENT_FRAGMENT"+suffix;
1045                    case Node.DOCUMENT_NODE:                                return "DOCUMENT"+suffix;
1046                    case Node.DOCUMENT_TYPE_NODE:                   return "DOCUMENT_TYPE"+suffix;
1047                    case Node.ELEMENT_NODE:                                 return "ELEMENT"+suffix;
1048                    case Node.ENTITY_NODE:                                  return "ENTITY"+suffix;
1049                    case Node.ENTITY_REFERENCE_NODE:                return "ENTITY_REFERENCE"+suffix;
1050                    case Node.NOTATION_NODE:                                return "NOTATION"+suffix;
1051                    case Node.PROCESSING_INSTRUCTION_NODE:  return "PROCESSING_INSTRUCTION"+suffix;
1052                    case Node.TEXT_NODE:                                    return "TEXT"+suffix;
1053                    default:                                                                return "UNKNOW"+suffix;
1054            }
1055        }
1056    
1057            public synchronized static Element getChildWithName(String name, Element el) {
1058                    Element[] children = XMLUtil.getChildElementsAsArray(el);
1059                    for(int i=0;i<children.length;i++) {
1060                            if(name.equalsIgnoreCase(children[i].getNodeName()))
1061                                    return children[i];
1062                    }
1063                    return null;
1064            }
1065            
1066            public static InputSource toInputSource(Resource res) throws IOException {
1067                    String str = IOUtil.toString((res), null);
1068                    return new InputSource(new StringReader(str));
1069        }
1070    
1071            public static InputSource toInputSource(PageContext pc, Object value) throws IOException, ExpressionException {
1072                    if(value instanceof InputSource) {
1073                return (InputSource) value;
1074            }
1075                    if(value instanceof String) {
1076                return toInputSource(pc, (String)value);
1077            }
1078                    if(value instanceof StringBuffer) {
1079                return toInputSource(pc, value.toString());
1080            }
1081            if(value instanceof Resource) {
1082                    String str = IOUtil.toString(((Resource)value), null);
1083                    return new InputSource(new StringReader(str));
1084            }
1085                    if(value instanceof File) {
1086                    String str = IOUtil.toString(ResourceUtil.toResource(((File)value)), null);
1087                    return new InputSource(new StringReader(str));
1088            }
1089                    if(value instanceof InputStream) {
1090                            InputStream is = (InputStream)value;
1091                            try {
1092                                    String str = IOUtil.toString(is, null);
1093                            return new InputSource(new StringReader(str));
1094                            }
1095                            finally {
1096                                    IOUtil.closeEL(is);
1097                            }
1098            }
1099                    if(value instanceof Reader) {
1100                            Reader reader = (Reader)value;
1101                            try {
1102                                    String str = IOUtil.toString(reader);
1103                            return new InputSource(new StringReader(str));
1104                            }
1105                            finally {
1106                                    IOUtil.closeEL(reader);
1107                            }
1108            }
1109                    if(value instanceof byte[]) {
1110                            return new InputSource(new ByteArrayInputStream((byte[])value));
1111            }
1112                    throw new ExpressionException("cat cast object of type ["+Caster.toClassName(value)+"] to a Input for xml parser");
1113                    
1114            }
1115            
1116            public static InputSource toInputSource(PageContext pc, String xml) throws IOException, ExpressionException {
1117                    return toInputSource(pc, xml,true);
1118            }
1119            
1120            public static InputSource toInputSource(PageContext pc, String xml, boolean canBePath) throws IOException, ExpressionException {
1121                    // xml text
1122                    xml=xml.trim(); 
1123                    if(!canBePath || xml.startsWith("<"))        {
1124                            return new InputSource(new StringReader(xml));
1125                    }
1126                    // xml link
1127                    pc=ThreadLocalPageContext.get(pc);
1128                    Resource res = ResourceUtil.toResourceExisting(pc, xml);
1129                    return toInputSource(pc, res);
1130            }
1131            
1132            public static Struct validate(InputSource xml, InputSource schema, String strSchema) throws XMLException {
1133            return new XMLValidator(schema,strSchema).validate(xml);
1134        }
1135    
1136            /**
1137             * adds a child at the first place 
1138             * @param parent
1139             * @param child
1140             */
1141            public static void prependChild(Element parent, Element child) {
1142                    Node first = parent.getFirstChild();
1143                    if(first==null)parent.appendChild(child);
1144                    else {
1145                            parent.insertBefore(child, first);
1146                    }
1147            }
1148    
1149            public static void setFirst(Node parent, Node node) {
1150                    Node first = parent.getFirstChild();
1151                    if(first!=null) parent.insertBefore(node, first);
1152                    else parent.appendChild(node);
1153            }
1154    
1155            public static XMLReader createXMLReader(String oprionalDefaultSaxParser) throws SAXException {
1156                    try{
1157                            return XMLReaderFactory.createXMLReader(oprionalDefaultSaxParser);
1158                    }
1159                    catch(Throwable t){
1160                            return XMLReaderFactory.createXMLReader();
1161                    }
1162            }
1163    }