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