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