package railo.runtime.text.xml;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.xalan.processor.TransformerFactoryImpl;
import org.apache.xerces.jaxp.DocumentBuilderFactoryImpl;
import org.ccil.cowan.tagsoup.Parser;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

import railo.commons.io.IOUtil;
import railo.commons.io.res.Resource;
import railo.commons.io.res.util.ResourceUtil;
import railo.commons.lang.StringUtil;
import railo.runtime.PageContext;
import railo.runtime.engine.ThreadLocalPageContext;
import railo.runtime.exp.ExpressionException;
import railo.runtime.exp.PageException;
import railo.runtime.exp.XMLException;
import railo.runtime.op.Caster;
import railo.runtime.op.Decision;
import railo.runtime.text.xml.struct.XMLMultiElementStruct;
import railo.runtime.text.xml.struct.XMLStruct;
import railo.runtime.text.xml.struct.XMLStructFactory;
import railo.runtime.type.Array;
import railo.runtime.type.ArrayImpl;
import railo.runtime.type.Collection;
import railo.runtime.type.Collection.Key;
import railo.runtime.type.KeyImpl;
import railo.runtime.type.Struct;

/**
 * 
 */
public final class XMLUtil {
    public static final Collection.Key XMLCOMMENT = KeyImpl.intern("xmlcomment");
    public static final Collection.Key XMLTEXT = KeyImpl.intern("xmltext");
    public static final Collection.Key XMLCDATA = KeyImpl.intern("xmlcdata");
    public static final Collection.Key XMLCHILDREN = KeyImpl.intern("xmlchildren");
    public static final Collection.Key XMLNODES = KeyImpl.intern("xmlnodes");
    public static final Collection.Key XMLNSURI = KeyImpl.intern("xmlnsuri");
    public static final Collection.Key XMLNSPREFIX = KeyImpl.intern("xmlnsprefix");
    public static final Collection.Key XMLROOT = KeyImpl.intern("xmlroot");
    public static final Collection.Key XMLPARENT = KeyImpl.intern("xmlparent");
    public static final Collection.Key XMLNAME = KeyImpl.intern("xmlname");
    public static final Collection.Key XMLTYPE = KeyImpl.intern("xmltype");
    public static final Collection.Key XMLVALUE = KeyImpl.intern("xmlvalue");
    public static final Collection.Key XMLATTRIBUTES = KeyImpl.intern("xmlattributes");
	/*
	private static final Collection.Key  = KeyImpl.getInstance();
	private static final Collection.Key  = KeyImpl.getInstance();
	private static final Collection.Key  = KeyImpl.getInstance();
	private static final Collection.Key  = KeyImpl.getInstance();
	private static final Collection.Key  = KeyImpl.getInstance();
	private static final Collection.Key  = KeyImpl.getInstance();
	*/
    
    
    
	//static DOMParser parser = new DOMParser();
	private static DocumentBuilder docBuilder;
	//private static DocumentBuilderFactory factory;
    private static TransformerFactory transformerFactory;
	

    public static String unescapeXMLString(String str) {
      	
      	StringBuffer rtn=new StringBuffer();
      	int posStart=-1;
      	int posFinish=-1;
      	while((posStart=str.indexOf('&',posStart))!=-1) {
      		int last=posFinish+1;
      		
      		posFinish=str.indexOf(';',posStart);
      		if(posFinish==-1)break;
      		rtn.append(str.substring(last,posStart));
      		if(posStart+1<posFinish) {
      			rtn.append(unescapeXMLEntity(str.substring(posStart+1,posFinish)));
      		}
      		else {
      			rtn.append("&;");
      		}
      		
      		posStart=posFinish+1;
      	}
      	rtn.append(str.substring(posFinish+1));
      	return rtn.toString();
    }

    public static String unescapeXMLString2(String str) {

    	StringBuffer sb=new StringBuffer();
    	int index,last=0,indexSemi;
    	while((index=str.indexOf('&',last))!=-1) {
    		sb.append(str.substring(last,index));
    		indexSemi=str.indexOf(';',index+1);

    		if(indexSemi==-1) {
    			sb.append('&');
    			last=index+1;
    		}
    		else if(index+1==indexSemi) {
    			sb.append("&;");
    			last=index+2;
    		}
    		else {
    			sb.append(unescapeXMLEntity(str.substring(index+1,indexSemi)));
    			last=indexSemi+1;
    		}
    	}
      	sb.append(str.substring(last));
      	return sb.toString();
    }
    
    
    
    
    private static String unescapeXMLEntity(String str) {
    	if("lt".equals(str)) return "<";
    	if("gt".equals(str)) return ">";
    	if("amp".equals(str)) return "&";
    	if("apos".equals(str)) return "'";
    	if("quot".equals(str)) return "\"";
		return "&"+str+";";
	}

	public static String escapeXMLString(String xmlStr) {
    	char c;
    	StringBuffer sb=new StringBuffer();
    	int len=xmlStr.length();
    	for(int i=0;i<len;i++) {
    		c=xmlStr.charAt(i);
    		if(c=='<') 		sb.append("&lt;");
    		else if(c=='>')	sb.append("&gt;");
    		else if(c=='&')	sb.append("&amp;");
    		//else if(c=='\'')	sb.append("&amp;");
    		else if(c=='"')	sb.append("&quot;");
    		//else if(c>127) sb.append("&#"+((int)c)+";");
    		else sb.append(c);
    	}
    	return sb.toString();
    }
    
    
    /**
     * @return returns a singelton TransformerFactory
     */
    public static TransformerFactory getTransformerFactory() {
        // Saxon
        //if(transformerFactory==null)transformerFactory=new com.icl.saxon.TransformerFactoryImpl();
        // Xalan
        if(transformerFactory==null)transformerFactory=new TransformerFactoryImpl();
        // Trax
        //if(transformerFactory==null)transformerFactory=new com.jclark.xsl.trax.TransformerFactoryImpl();
        // Trax
        //if(transformerFactory==null)transformerFactory=new jd.xml.xslt.trax.TransformerFactoryImpl();
        // Caucho
        //if(transformerFactory==null)transformerFactory=new Xsl();
        // xsltc
        //if(transformerFactory==null)transformerFactory=new org.apache.xalan.xsltc.trax.TransformerFactoryImpl();
        
        
        return transformerFactory;
    }
    
    /**
     * parse XML/HTML String to a XML DOM representation
     * @param xml XML InputSource
     * @param isHtml is a HTML or XML Object
     * @return parsed Document
     * @throws SAXException
     * @throws IOException
     * @throws ParserConfigurationException 
     */
    public static final Document parse(InputSource xml,InputSource validator, boolean isHtml) 
        throws SAXException, IOException {
        
        if(!isHtml) {
        	// try to load org.apache.xerces.jaxp.DocumentBuilderFactoryImpl, oracle impl sucks
        	DocumentBuilderFactory factory = null;
        	try{
        		factory = new DocumentBuilderFactoryImpl();
        	}
        	catch(Throwable t) {
        		factory = DocumentBuilderFactory.newInstance();
        	}
        	
        	//print.o(factory);
            if(validator==null) {
            	XMLUtil.setAttributeEL(factory,XMLConstants.NON_VALIDATING_DTD_EXTERNAL, Boolean.FALSE);
            	XMLUtil.setAttributeEL(factory,XMLConstants.NON_VALIDATING_DTD_GRAMMAR, Boolean.FALSE);
            }
            else {
            	XMLUtil.setAttributeEL(factory,XMLConstants.VALIDATION_SCHEMA, Boolean.TRUE);
            	XMLUtil.setAttributeEL(factory,XMLConstants.VALIDATION_SCHEMA_FULL_CHECKING, Boolean.TRUE);
            
                
            }
            
            
            factory.setNamespaceAware(true);
            factory.setValidating(validator!=null);
            
            try {
				DocumentBuilder builder = factory.newDocumentBuilder();
	            builder.setEntityResolver(new XMLEntityResolverDefaultHandler(validator));
	            builder.setErrorHandler(new ThrowingErrorHandler(true,true,false));
	            return  builder.parse(xml);
			} 
            catch (ParserConfigurationException e) {
				throw new SAXException(e);
			}
            
	        /*DOMParser parser = new DOMParser();
	        print.out("parse");
	        parser.setEntityResolver(new XMLEntityResolverDefaultHandler(validator));
	        parser.parse(xml);
	        return parser.getDocument();*/
        }
        
        XMLReader reader = new Parser();
            reader.setFeature(Parser.namespacesFeature, true);
            reader.setFeature(Parser.namespacePrefixesFeature, true);
        
        try {
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            
            DOMResult result = new DOMResult();
            transformer.transform(new SAXSource(reader, xml), result);
            return XMLUtil.getDocument(result.getNode());
        } 
        catch (Exception e) {
            throw new SAXException(e);
        }
    }
	
	private static void setAttributeEL(DocumentBuilderFactory factory,String name, Object value) {
		try{
			factory.setAttribute(name, value);
		}
		catch(Throwable t){
			//SystemOut.printDate("attribute ["+name+"] is not allowed for ["+factory.getClass().getName()+"]");
		}
	}

	/**
	 * sets a node to a node (Expression Less)
	 * @param node
	 * @param key
	 * @param value
	 * @return Object set
	 */
	public static Object setPropertyEL(Node node, Collection.Key key, Object value) {
		try {
			return setProperty(node,key,value);
		} catch (PageException e) {
			return null;
		}
	}
	public static Object setProperty(Node node, Collection.Key key, Object value,boolean caseSensitive, Object defaultValue) {
		try {
			return setProperty(node,key,value,caseSensitive);
		} catch (PageException e) {
			return defaultValue;
		}
	}
	
	/**
	 * sets a node to a node
	 * @param node
	 * @param key
	 * @param value
	 * @return Object set
	 * @throws PageException
	 */

	public static Object setProperty(Node node, Collection.Key k, Object value) throws PageException {
		return setProperty(node, k, value, isCaseSensitve(node));
	}
	
	public static Object setProperty(Node node, Collection.Key k, Object value,boolean caseSensitive) throws PageException {
		Document doc=(node instanceof Document)?(Document)node:node.getOwnerDocument();
		// Comment
			if(k.equals(XMLCOMMENT)) {
				removeChildren(XMLCaster.toRawNode(node),Node.COMMENT_NODE,false);
				node.appendChild(XMLCaster.toRawNode(XMLCaster.toComment(doc,value)));
			}
		// NS URI
			else if(k.equals(XMLNSURI)) {
				// TODO impl
				throw new ExpressionException("XML NS URI can't be set","not implemented");
			}
		// Prefix
			else if(k.equals(XMLNSPREFIX)) {
				// TODO impl
				throw new ExpressionException("XML NS Prefix can't be set","not implemented");
				//node.setPrefix(Caster.toString(value));
			}			
		// Root
			else if(k.equals(XMLROOT)) {
				doc.appendChild(XMLCaster.toNode(doc,value,false));
			}			
		// Parent
			else if(k.equals(XMLPARENT)) {
				Node parent = getParentNode(node,caseSensitive);
				Key name = KeyImpl.init(parent.getNodeName());
				parent = getParentNode(parent,caseSensitive);
				
				if(parent==null)
					throw new ExpressionException("there is no parent element, you are already on the root element");
				
				return setProperty(parent, name, value, caseSensitive);
			}
		// Name	
			else if(k.equals(XMLNAME)) {
				throw new XMLException("You can't assign a new value for the property [xmlname]");
			}
		// Type	
			else if(k.equals(XMLTYPE)) {
				throw new XMLException("You can't change type of a xml node [xmltype]");
			}
		// value	
			else if(k.equals(XMLVALUE)) {
				node.setNodeValue(Caster.toString(value));
			}
		// Attributes	
			else if(k.equals(XMLATTRIBUTES)) {
				Element parent=XMLCaster.toElement(doc,node);
				Attr[] attres=XMLCaster.toAttrArray(doc,value);
				//print.ln("=>"+value);
				for(int i=0;i<attres.length;i++) {
					if(attres[i]!=null) {
						parent.setAttributeNode(attres[i]);
						//print.ln(attres[i].getName()+"=="+attres[i].getValue());
					}
				}
			}
		// Text	
			else if(k.equals(XMLTEXT)) {
				removeChildCharacterData(XMLCaster.toRawNode(node),false);
				node.appendChild(XMLCaster.toRawNode(XMLCaster.toText(doc,value)));
			}
		// CData	
			else if(k.equals(XMLCDATA)) {
				removeChildCharacterData(XMLCaster.toRawNode(node),false);
				node.appendChild(XMLCaster.toRawNode(XMLCaster.toCDATASection(doc,value)));
			}
		// Children	
			else if(k.equals(XMLCHILDREN) || k.equals(XMLNODES)) {
				Node[] nodes=XMLCaster.toNodeArray(doc,value);
				removeChildren(XMLCaster.toRawNode(node),Node.ELEMENT_NODE,false);
				for(int i=0;i<nodes.length;i++) {
					if(nodes[i]==node) throw new XMLException("can't assign a XML Node to himself");
					if(nodes[i]!=null)node.appendChild(XMLCaster.toRawNode(nodes[i]));
				}
			}
			else {
				boolean isIndex=false;
			    Node child = XMLCaster.toNode(doc,value,false);
				if(!k.getString().equalsIgnoreCase(child.getNodeName()) && !(isIndex=Decision.isInteger(k))) {
					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());
				}
				Node n;
				
				// by index
				if(isIndex) {
					NodeList list = XMLUtil.getChildNodes(node.getParentNode(), Node.ELEMENT_NODE,true,node.getNodeName());
					int len = list.getLength();
					
					
					int index=Caster.toIntValue(k);
					if(index>len || index<1){
						String detail=len>1?
								"your index is "+index+", but there are only "+len+" child elements":
								"your index is "+index+", but there is only "+len+" child element";
						
						
						throw new XMLException("index is out of range", detail);
					}
					n=list.item(index-1);
					XMLUtil.replaceChild(child, n);
					return value;
				}
				
				
				
				
				NodeList list = XMLUtil.getChildNodes(node, Node.ELEMENT_NODE);
				int len = list.getLength();
				
				// by name
				for(int i=0;i<len;i++) {
					n=list.item(i);
					if(nameEqual(n, k.getString(), caseSensitive)) {
						XMLUtil.replaceChild(child, n);
						return value;
					}
				}
				node.appendChild(XMLCaster.toRawNode(child));
			}
			
			return value;
	}


	public static void replaceChild(Node newChild, Node oldChild) {
		

		Node nc = XMLCaster.toRawNode(newChild);
		Node oc = XMLCaster.toRawNode(oldChild);
		Node p = oc.getParentNode();

		if(nc!=oc)p.replaceChild(nc, oc);
	}

	public static Object getProperty(Node node, Collection.Key key, Object defaultValue) {
		return getProperty(node, key,isCaseSensitve(node),defaultValue);
	}
	
	
	/**
	 * returns a property from a XMl Node  (Expression Less)
	 * @param node
	 * @param key
	 * @param caseSensitive
	 * @return Object matching key
	 */
	public static Object getProperty(Node node, Collection.Key k,boolean caseSensitive, Object defaultValue) {
		try {
			return getProperty(node, k,caseSensitive);
		} catch (SAXException e) {
			return defaultValue;
		}
	}
	
	public static Object getProperty(Node node, Collection.Key key) throws SAXException {
		return getProperty(node, key,isCaseSensitve(node));
	}
	
	/**
	 * returns a property from a XMl Node
	 * @param node
	 * @param key
	 * @param caseSensitive
	 * @return Object matching key
	 * @throws SAXException
	 */
	public static Object getProperty(Node node, Collection.Key k,boolean caseSensitive) throws SAXException {
	    //String lcKey=StringUtil.toLowerCase(key);
		if(k.getLowerString().startsWith("xml")) {
		// Comment
			if(k.equals(XMLCOMMENT)) {
				StringBuffer sb=new StringBuffer();
				NodeList list = node.getChildNodes();
				int len=list.getLength();
				for(int i=0;i<len;i++) {
					Node n=list.item(i);
					if(n instanceof Comment) {
						sb.append(((Comment)n).getData());
					}
				}
				return sb.toString();
			}
		// NS URI
			if(k.equals(XMLNSURI)) {
				undefinedInRoot(k,node);
				return param(node.getNamespaceURI(),"");
			}
		// Prefix
			if(k.equals(XMLNSPREFIX)) {
				undefinedInRoot(k,node);
				return param(node.getPrefix(),"");
			}
		// Root
			else if(k.equals(XMLROOT)) {
				Element re = getRootElement(node,caseSensitive);
				if(re==null) throw new SAXException("Attribute ["+k.getString()+"] not found in XML, XML is empty");
				return param(re,"");
			}
		// Parent
			else if(k.equals(XMLPARENT)) {
				
				Node parent = getParentNode(node,caseSensitive);
				if(parent==null) {
					if(node.getNodeType()==Node.DOCUMENT_NODE)
						throw new SAXException("Attribute ["+k.getString()+"] not found in XML, there is no parent element, you are already at the root element");
					throw new SAXException("Attribute ["+k.getString()+"] not found in XML, there is no parent element");
				}
				return parent;
			}
		// Name	
			else if(k.equals(XMLNAME)) {
				return node.getNodeName();
			}
		// Value	
			else if(k.equals(XMLVALUE)) {
				return StringUtil.toStringEmptyIfNull(node.getNodeValue());
			}
		// type	
			else if(k.equals(XMLTYPE)) {
				return getTypeAsString(node,true);
			}
		// Attributes	
			else if(k.equals(XMLATTRIBUTES)) {
				NamedNodeMap attr = node.getAttributes();
				
				if(attr==null)throw undefined(k,node);
				return new XMLAttributes(node,caseSensitive);
			}
		// Text	
			else if(k.equals(XMLTEXT)) {
				undefinedInRoot(k,node);
				StringBuffer sb=new StringBuffer();
				NodeList list = node.getChildNodes();
				int len=list.getLength();
				for(int i=0;i<len;i++) {
					Node n=list.item(i);
                    if(n instanceof Text || n instanceof CDATASection) {
                        sb.append(((CharacterData)n).getData());
					}
				}
                return sb.toString();
			}
			else if(k.equals(XMLCDATA)) {
				undefinedInRoot(k,node);
				StringBuffer sb=new StringBuffer();
				NodeList list = node.getChildNodes();
				int len=list.getLength();
				for(int i=0;i<len;i++) {
					Node n=list.item(i);
                    if(n instanceof Text || n instanceof CDATASection) {
                        sb.append(((CharacterData)n).getData());
					}
				}
                return sb.toString();
			}
			// children	
			else if(k.equals(XMLCHILDREN) || k.equals(XMLNODES)) {
				return new XMLNodeList(node,caseSensitive);
			}
		}
		
		if(node instanceof Document) {
		    node=((Document)node).getDocumentElement();
		    if(node==null) throw new SAXException("Attribute ["+k.getString()+"] not found in XML, XML is empty");
		    
		    //if((!caseSensitive && node.getNodeName().equalsIgnoreCase(k.getString())) || (caseSensitive && node.getNodeName().equals(k.getString()))) {
		    if(nameEqual(node, k.getString(), caseSensitive)) {
				return XMLStructFactory.newInstance(node,caseSensitive);
			}
		}
		else if(node.getNodeType()==Node.ELEMENT_NODE && Decision.isInteger(k)){
			int index=Caster.toIntValue(k,0);
			int count=0;
			Node parent = node.getParentNode();
			String nodeName=node.getNodeName();
			Element[] children = XMLUtil.getChildElementsAsArray(parent);
				
			for(int i=0;i<children.length;i++){
				if(XMLUtil.nameEqual(children[i],nodeName,caseSensitive)) count++;
				
				if(count==index) return XMLCaster.toXMLStruct(children[i],caseSensitive);
			}
			String detail;
			if(count==0)detail="there are no Elements with this name";
			else if(count==1)detail="there is only 1 Element with this name";
			else detail="there are only "+count+" Elements with this name";
			throw new SAXException("invalid index ["+k.getString()+"] for Element with name ["+node.getNodeName()+"], "+detail);
		}
		else {
			List<Node> children = XMLUtil.getChildNodesAsList(node,Node.ELEMENT_NODE,caseSensitive,null);
			int len=children.size();
			Array array=null;//new ArrayImpl();
			Element el;
			XMLStruct sct=null,first=null;
			for(int i=0;i<len;i++) {
				el=(Element) children.get(i);// XMLCaster.toXMLStruct(getChildNode(index),caseSensitive);
				if(XMLUtil.nameEqual(el,k.getString(),caseSensitive)) {
					sct = XMLCaster.toXMLStruct(el,caseSensitive);
					
					if(array!=null) {
						array.appendEL(sct);
					}
					else if(first!=null) {
						array=new ArrayImpl();
						array.appendEL(first);
						array.appendEL(sct);
					}
					else {
						first=sct;
					}
				}
			}
			
			if(array!=null) {
				try {
					return new XMLMultiElementStruct(array,false);
				} catch (PageException e) {}
			}
			if(first!=null) return first;
		}
		throw new SAXException("Attribute ["+k.getString()+"] not found");
	}


    private static SAXException undefined(Key key, Node node) {
    	if(node.getNodeType()==Node.DOCUMENT_NODE)
    		return new SAXException("you cannot address ["+key+"] on the Document Object, to address ["+key+"]  from the root Node use [{variable-name}.xmlRoot."+key+"]");
    	
    	return new SAXException(key+" is undefined");
	}

    private static void undefinedInRoot(Key key, Node node) throws SAXException {
    	if(node.getNodeType()==Node.DOCUMENT_NODE)
    		throw undefined(key, node);
	}

	/**
     * check if given name is equal to name of the element (with and without namespace)
     * @param node
     * @param k
     * @param caseSensitive
     * @return
     */
    public static boolean nameEqual(Node node, String name, boolean caseSensitive) {
		if(name==null) return false;
    	if(caseSensitive){
    		return name.equals(node.getNodeName()) || name.equals(node.getLocalName());
    	}
    	return name.equalsIgnoreCase(node.getNodeName()) || name.equalsIgnoreCase(node.getLocalName());
	}

	public static boolean isCaseSensitve(Node node) {
		if(node instanceof XMLStruct) return ((XMLStruct)node).isCaseSensitive();
    	return true;
	}

	/**
     * removes child from a node
     * @param node
     * @param key
     * @param caseSensitive
     * @return removed property
     */
    public static Object removeProperty(Node node, Collection.Key k,boolean caseSensitive) {
        
        //String lcKeyx=k.getLowerString();
        if(k.getLowerString().startsWith("xml")) {
        // Comment
            if(k.equals(XMLCOMMENT)) {
                StringBuffer sb=new StringBuffer();
                NodeList list = node.getChildNodes();
                int len=list.getLength();
                for(int i=0;i<len;i++) {
                    Node n=list.item(i);
                    if(n instanceof Comment) {
                        sb.append(((Comment)n).getData());
                        node.removeChild(XMLCaster.toRawNode(n));
                    }
                }
                return sb.toString();
            }
        // Text 
            else if(k.equals(XMLTEXT)) {
                StringBuffer sb=new StringBuffer();
                NodeList list = node.getChildNodes();
                int len=list.getLength();
                for(int i=0;i<len;i++) {
                    Node n=list.item(i);
                    if(n instanceof Text || n instanceof CDATASection) {
                        sb.append(((CharacterData)n).getData());
                        node.removeChild(XMLCaster.toRawNode(n));
                    }
                }
                return sb.toString();
            }
            // children 
            else if(k.equals(XMLCHILDREN) || k.equals(XMLNODES)) {
                NodeList list=node.getChildNodes();
                for(int i=list.getLength()-1;i>=0;i--) {
                    node.removeChild(XMLCaster.toRawNode(list.item(i)));
                }
                return list;
            }
        }
         
            NodeList nodes = node.getChildNodes();
            Array array=new ArrayImpl();
            for(int i=nodes.getLength()-1;i>=0;i--) {
                Object o=nodes.item(i);
                if(o instanceof Element) {
                    Element el=(Element) o;
                    if(nameEqual(el, k.getString(), caseSensitive)) {
                        array.appendEL(XMLCaster.toXMLStruct(el,caseSensitive));
                        node.removeChild(XMLCaster.toRawNode(el));
                    }
                }
            }
            
            if(array.size()>0) {
                try {
                    return new XMLMultiElementStruct(array,false);
                } catch (PageException e) {}
            }
            return null;
    }
    

	private static Object param(Object o1, Object o2) {
		if(o1==null)return o2;
		return o1;
	}
	
	/**
	 * return the root Element from a node
	 * @param node node to get root element from
	 * @param caseSensitive
	 * @return Root Element
	 */
	public static Element getRootElement(Node node, boolean caseSensitive) {
	    Document doc=null;
		if(node instanceof Document) doc=(Document) node;
		else doc=node.getOwnerDocument();
		Element el = doc.getDocumentElement();
		if(el==null) return null;
		return (Element)XMLStructFactory.newInstance(el,caseSensitive);
	}
	

	public static Node getParentNode(Node node, boolean caseSensitive) {
		Node parent = node.getParentNode();
	    if(parent==null) return null;
		return XMLStructFactory.newInstance(parent,caseSensitive);
	}

	/**
	 * returns a new Empty XMl Document
	 * @return new Document
	 * @throws ParserConfigurationException
	 * @throws FactoryConfigurationError
	 */
	public static Document newDocument() throws ParserConfigurationException, FactoryConfigurationError {
		if(docBuilder==null) {
			docBuilder=DocumentBuilderFactory.newInstance().newDocumentBuilder();
		}
		return docBuilder.newDocument();
	}
	
	/**
	 * return the Owner Document of a Node List
	 * @param nodeList
	 * @return XML Document
	 * @throws XMLException
	 */
	public static Document getDocument(NodeList nodeList) throws XMLException {
		if(nodeList instanceof Document) return (Document)nodeList;
		int len=nodeList.getLength();
		for(int i=0;i<len;i++) {
			Node node=nodeList.item(i);
			if(node!=null) return node.getOwnerDocument();
		}
		throw new XMLException("can't get Document from NodeList, in NoteList are no Nodes");
	}
	
	/**
	 * return the Owner Document of a Node
	 * @param node
	 * @return XML Document
	 */
	public static Document getDocument(Node node) {
		if(node instanceof Document) return (Document)node;
		return node.getOwnerDocument();
	}
	
	
	/**
	 * removes child elements from a specific type
	 * @param node node to remove elements from
	 * @param type Type Definition to remove (Constant value from class Node)
	 * @param deep remove also in sub nodes
	 */
	private synchronized static void removeChildren(Node node, short type, boolean deep) {
		NodeList list = node.getChildNodes();
		
		for(int i=list.getLength();i>=0;i--) {
			Node n=list.item(i);
			if(n ==null )continue;
			
			if(n.getNodeType()==type)node.removeChild(XMLCaster.toRawNode(n));
			else if(deep)removeChildren(n,type,deep);
		}
	}
	
	/**
	 * remove children from type CharacterData from a node, this includes Text,Comment and CDataSection nodes
	 * @param node
	 * @param type
	 * @param deep
	 */
	private synchronized static void removeChildCharacterData(Node node, boolean deep) {
		NodeList list = node.getChildNodes();
		
		for(int i=list.getLength();i>=0;i--) {
			Node n=list.item(i);
			if(n ==null )continue;
			
			if(n instanceof CharacterData)node.removeChild(XMLCaster.toRawNode(n));
			else if(deep)removeChildCharacterData(n,deep);
		}
	}

	/**
	 * return all Children of a node by a defined type as Node List
	 * @param node node to get children from
	 * @param type type of returned node
	 * @param filter 
	 * @param caseSensitive 
	 * @return all matching child node
	 */
	public synchronized static ArrayNodeList getChildNodes(Node node, short type) {
		return getChildNodes(node, type, false, null);
	}
	

	public synchronized static int childNodesLength(Node node, short type, boolean caseSensitive, String filter) {
		NodeList nodes=node.getChildNodes();
		int len=nodes.getLength();
		Node n;
		int count=0;
		for(int i=0;i<len;i++) {
			try {
				n=nodes.item(i);
				if(n!=null && n.getNodeType()==type){
					if(filter==null || (caseSensitive?filter.equals(n.getLocalName()):filter.equalsIgnoreCase(n.getLocalName())))
					count++;
				}
			}
			catch(Throwable t){}
		}
		return count;
	}
	
	public synchronized static ArrayNodeList getChildNodes(Node node, short type, boolean caseSensitive, String filter) {
		ArrayNodeList rtn=new ArrayNodeList();
		NodeList nodes=node.getChildNodes();
		int len=nodes.getLength();
		Node n;
		for(int i=0;i<len;i++) {
			try {
				n=nodes.item(i);
				if(n!=null && n.getNodeType()==type){
					if(filter==null || (caseSensitive?filter.equals(n.getLocalName()):filter.equalsIgnoreCase(n.getLocalName())))
					rtn.add(n);
				}
			}
			catch(Throwable t){}
		}
		return rtn;
	}
	
	public synchronized static List<Node> getChildNodesAsList(Node node, short type, boolean caseSensitive, String filter) {
		List<Node> rtn=new ArrayList<Node>();
		NodeList nodes=node.getChildNodes();
		int len=nodes.getLength();
		Node n;
		for(int i=0;i<len;i++) {
			try {
				n=nodes.item(i);
				if(n!=null && n.getNodeType()==type){
					if(filter==null || (caseSensitive?filter.equals(n.getLocalName()):filter.equalsIgnoreCase(n.getLocalName())))
					rtn.add(n);
				}
			}
			catch(Throwable t){}
		}
		return rtn;
	}
	

	public synchronized static Node getChildNode(Node node, short type, boolean caseSensitive, String filter, int index) {
		NodeList nodes=node.getChildNodes();
		int len=nodes.getLength();
		Node n;
		int count=0;
		for(int i=0;i<len;i++) {
			try {
				n=nodes.item(i);
				if(n!=null && n.getNodeType()==type){
					if(filter==null || (caseSensitive?filter.equals(n.getLocalName()):filter.equalsIgnoreCase(n.getLocalName()))) {
						if(count==index) return n;
						count++;
					}
				}
			}
			catch(Throwable t){}
		}
		return null;
	}
	
	
	
    /**
     * return all Children of a node by a defined type as Node Array
     * @param node node to get children from
     * @param type type of returned node
     * @param filter 
     * @param caseSensitive 
     * @return all matching child node
     */
    public static Node[] getChildNodesAsArray(Node node, short type) {
        ArrayNodeList nodeList=getChildNodes(node, type);
        return nodeList.toArray(new Node[nodeList.getLength()]);
    }

    public static Node[] getChildNodesAsArray(Node node, short type, boolean caseSensitive, String filter) {
        ArrayNodeList nodeList=getChildNodes(node, type,caseSensitive,filter);
        return  nodeList.toArray(new Node[nodeList.getLength()]);
    }
    
    /**
     * return all Element Children of a node
     * @param node node to get children from
     * @return all matching child node
     */
    public static Element[] getChildElementsAsArray(Node node) {
        ArrayNodeList nodeList=getChildNodes(node,Node.ELEMENT_NODE);
        return  nodeList.toArray(new Element[nodeList.getLength()]);
    }

    /**
     * transform a XML Object to a other format, with help of a XSL Stylesheet
     * @param xml xml to convert
     * @param xsl xsl used to convert
     * @return resulting string
     * @throws TransformerException
     * @throws SAXException
     * @throws IOException
     */
    public static String transform(InputSource xml, InputSource xsl) throws TransformerException, SAXException, IOException {
    	return transform( parse( xml, null , false ), xsl, null );
    }
    
    /**
     * transform a XML Object to a other format, with help of a XSL Stylesheet
     * @param xml xml to convert
     * @param xsl xsl used to convert
     * @param parameters parameters used to convert
     * @return resulting string
     * @throws TransformerException
     * @throws SAXException
     * @throws IOException
     */
    public static String transform(InputSource xml, InputSource xsl, Map parameters) throws TransformerException, SAXException, IOException {
    	return transform( parse( xml, null , false ), xsl, parameters );
    }

    /**
     * transform a XML Document to a other format, with help of a XSL Stylesheet
     * @param xml xml to convert
     * @param xsl xsl used to convert
     * @return resulting string
     * @throws TransformerException
     * @throws SAXException
     * @throws IOException
     */
    public static String transform( Document doc, InputSource xsl ) throws TransformerException {
		return transform( doc, xsl, null );
	}
    
    /**
     * transform a XML Document to a other format, with help of a XSL Stylesheet
     * @param xml xml to convert
     * @param xsl xsl used to convert
     * @param parameters parameters used to convert
     * @return resulting string
     * @throws TransformerException
     * @throws SAXException
     * @throws IOException
     */
    public static String transform(Document doc, InputSource xsl, Map parameters) throws TransformerException {
    	StringWriter sw = new StringWriter();
    	TransformerFactory factory = XMLUtil.getTransformerFactory();
    	factory.setErrorListener(SimpleErrorListener.THROW_FATAL);
		Transformer transformer = factory.newTransformer(new StreamSource(xsl.getCharacterStream()));
		if (parameters != null) {
			Iterator it = parameters.entrySet().iterator();
			while ( it.hasNext() ) {
				Map.Entry e = (Map.Entry) it.next();
				transformer.setParameter(e.getKey().toString(), e.getValue());
			}
		}
		transformer.transform(new DOMSource(doc), new StreamResult(sw));
		return sw.toString();
	}

    /**
     * returns the Node Type As String
     * @param node
	 * @param cftype 
	 * @return
     */
	public static String getTypeAsString(Node node, boolean cftype) {
		String suffix=cftype?"":"_NODE";
		
        switch(node.getNodeType()) {
    		case Node.ATTRIBUTE_NODE: 				return "ATTRIBUTE"+suffix;
    		case Node.CDATA_SECTION_NODE: 			return "CDATA_SECTION"+suffix;
    		case Node.COMMENT_NODE: 				return "COMMENT"+suffix;
    		case Node.DOCUMENT_FRAGMENT_NODE: 		return "DOCUMENT_FRAGMENT"+suffix;
    		case Node.DOCUMENT_NODE: 				return "DOCUMENT"+suffix;
    		case Node.DOCUMENT_TYPE_NODE: 			return "DOCUMENT_TYPE"+suffix;
    		case Node.ELEMENT_NODE: 				return "ELEMENT"+suffix;
    		case Node.ENTITY_NODE: 					return "ENTITY"+suffix;
    		case Node.ENTITY_REFERENCE_NODE: 		return "ENTITY_REFERENCE"+suffix;
    		case Node.NOTATION_NODE: 				return "NOTATION"+suffix;
    		case Node.PROCESSING_INSTRUCTION_NODE: 	return "PROCESSING_INSTRUCTION"+suffix;
    		case Node.TEXT_NODE: 					return "TEXT"+suffix;
    		default: 								return "UNKNOW"+suffix;
        }
    }

	public synchronized static Element getChildWithName(String name, Element el) {
		Element[] children = XMLUtil.getChildElementsAsArray(el);
		for(int i=0;i<children.length;i++) {
			if(name.equalsIgnoreCase(children[i].getNodeName()))
				return children[i];
		}
		return null;
	}
	
	public static InputSource toInputSource(Resource res) throws IOException {
        	String str = IOUtil.toString((res), (Charset)null);
        	return new InputSource(new StringReader(str));
    }

	public static InputSource toInputSource(PageContext pc, Object value) throws IOException, ExpressionException {
		if(value instanceof InputSource) {
            return (InputSource) value;
        }
		if(value instanceof String) {
            return toInputSource(pc, (String)value);
        }
		if(value instanceof StringBuffer) {
            return toInputSource(pc, value.toString());
        }
        if(value instanceof Resource) {
        	String str = IOUtil.toString(((Resource)value), (Charset)null);
        	return new InputSource(new StringReader(str));
        }
		if(value instanceof File) {
        	String str = IOUtil.toString(ResourceUtil.toResource(((File)value)),(Charset)null);
        	return new InputSource(new StringReader(str));
        }
		if(value instanceof InputStream) {
			InputStream is = (InputStream)value;
			try {
				String str = IOUtil.toString(is, (Charset)null);
	        	return new InputSource(new StringReader(str));
			}
			finally {
				IOUtil.closeEL(is);
			}
        }
		if(value instanceof Reader) {
			Reader reader = (Reader)value;
			try {
				String str = IOUtil.toString(reader);
	        	return new InputSource(new StringReader(str));
			}
			finally {
				IOUtil.closeEL(reader);
			}
        }
		if(value instanceof byte[]) {
			return new InputSource(new ByteArrayInputStream((byte[])value));
        }
		throw new ExpressionException("cat cast object of type ["+Caster.toClassName(value)+"] to a Input for xml parser");
        	
	}
	
	public static InputSource toInputSource(PageContext pc, String xml) throws IOException, ExpressionException {
		return toInputSource(pc, xml,true);
	}
	
	public static InputSource toInputSource(PageContext pc, String xml, boolean canBePath) throws IOException, ExpressionException {
		// xml text
		xml=xml.trim(); 
		if(!canBePath || xml.startsWith("<"))	{
			return new InputSource(new StringReader(xml));
		}
		// xml link
		pc=ThreadLocalPageContext.get(pc);
		Resource res = ResourceUtil.toResourceExisting(pc, xml);
		return toInputSource(pc, res);
	}
	
	public static Struct validate(InputSource xml, InputSource schema, String strSchema) throws XMLException {
    	return new XMLValidator(schema,strSchema).validate(xml);
    }

	/**
	 * adds a child at the first place 
	 * @param parent
	 * @param child
	 */
	public static void prependChild(Element parent, Element child) {
		Node first = parent.getFirstChild();
		if(first==null)parent.appendChild(child);
		else {
			parent.insertBefore(child, first);
		}
	}

	public static void setFirst(Node parent, Node node) {
		Node first = parent.getFirstChild();
		if(first!=null) parent.insertBefore(node, first);
		else parent.appendChild(node);
	}

	public static XMLReader createXMLReader(String oprionalDefaultSaxParser) throws SAXException {
		try{
			return XMLReaderFactory.createXMLReader(oprionalDefaultSaxParser);
		}
		catch(Throwable t){
			return XMLReaderFactory.createXMLReader();
		}
	}
}