jef.tools.XMLUtils.java Source code

Java tutorial

Introduction

Here is the source code for jef.tools.XMLUtils.java

Source

/*
 * JEF - Copyright 2009-2010 Jiyi (mr.jiyi@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jef.tools;

import java.io.BufferedReader;
import java.io.File;
import java.io.FilterReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PushbackInputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Array;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;

import javax.management.ReflectionException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.html.dom.HTMLDocumentImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
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.w3c.dom.html.HTMLDocument;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import com.alibaba.fastjson.JSONObject;

import jef.common.log.LogUtil;
import jef.tools.io.Charsets;
import jef.tools.reflect.BeanWrapper;
import jef.tools.reflect.BeanWrapperImpl;
import jef.tools.reflect.Property;
import jef.tools.reflect.UnsafeUtils;

/**
 * JAXP?XML???
 * 
 * 
 * <b>??xercesImpl</b>
 * 
 * <pre>
 * ?xerces??
 * xerces apache?
 * ?xercesImpl 2.6.x2.11.x??? 2.7.1~2.9.1
 *    2.7.1???cybernekoHTML??2.6.2?
 *    2.10.0org.w3c.dom.ElementTraversalJDK 6??xml-api
 *    weblogic???
 *    2.9.1
 * </pre>
 * 
 * @author jiyi
 * 
 */
public class XMLUtils {
    private static final Logger log = LoggerFactory.getLogger("XMLUtils");

    /**
     * DocumentBuilderFactory<br>
     * ?DocumentBuilderFactory0.3ms??
     */
    private static DocumentBuilderFactory domFactoryTT;
    private static DocumentBuilderFactory domFactoryTF;
    private static DocumentBuilderFactory domFactoryFT;
    private static DocumentBuilderFactory domFactoryFF;

    /**
     * ??????
     */
    static {
        try {
            Class.forName("org.apache.xerces.xni.XMLDocumentHandler");
            try {
                Class<?> cParser = Class.forName("org.cyberneko.html.parsers.DOMFragmentParser");
                if (cParser != null) {
                    parser = (jef.tools.IDOMFragmentParser) cParser.newInstance();
                }
            } catch (Exception e) {
                // common-net??HTML?
                LogUtil.warn(
                        "The EF-HTML parser engine not found, HTMLParser feature will be disabled. Import easyframe 'common-misc' library to the classpath to activate this feature.");
            }
        } catch (Exception e) {
            // xerces??HTML?
            LogUtil.warn(
                    "The Apache xerces implemention not avaliable, HTMLParser feature will be disabled. you must import library 'xercesImpl'(version >= 2.7.1) into classpath.");
        }
        try {
            domFactoryTT = initFactory(true, true);
            domFactoryTF = initFactory(true, false);
            domFactoryFT = initFactory(false, true);
            domFactoryFF = initFactory(false, false);
        } catch (Exception e) {
            log.error("FATAL: Error in init DocumentBuilderFactory. XML Parser will not work!", e);
        }
    }

    /*
     * ?
     * 
     * @param ignorComments 
     * 
     * @param namespaceAware ??
     * 
     * @return DocumentBuilderFactoy
     */
    private static DocumentBuilderFactory initFactory(boolean ignorComments, boolean namespaceAware) {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setIgnoringElementContentWhitespace(true);
        dbf.setValidating(false); // DTD
        dbf.setIgnoringComments(ignorComments);
        dbf.setNamespaceAware(namespaceAware);
        // dbf.setCoalescing(true);//CDATA
        // ?Text???
        try {
            // dbf.setFeature("http://xml.org/sax/features/namespaces", false);
            // dbf.setFeature("http://xml.org/sax/features/validation", false);
            dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
            dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        } catch (ParserConfigurationException e) {
            log.warn(
                    "Your xerces implemention is too old to support 'load-dtd-grammar' and 'load-external-dtd' feature. Please upgrade xercesImpl.jar to 2.6.2 or above.");
        } catch (AbstractMethodError e) {
            log.warn(
                    "Your xerces implemention is too old to support 'load-dtd-grammar' and 'load-external-dtd' feature. Please upgrade xercesImpl.jar to 2.6.2 or above.");
        }

        try {
            dbf.setAttribute("http://xml.org/sax/features/external-general-entities", false);
        } catch (IllegalArgumentException e) {
            log.warn("Your xerces implemention is too old to support 'external-general-entities' attribute.");
        }
        try {
            dbf.setAttribute("http://xml.org/sax/features/external-parameter-entities", false);
        } catch (IllegalArgumentException e) {
            log.warn("Your xerces implemention is too old to support 'external-parameter-entities' attribute.");
        }
        try {
            dbf.setAttribute("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        } catch (IllegalArgumentException e) {
            log.warn("Your xerces implemention is too old to support 'load-external-dtd' attribute.");
        }
        return dbf;
    }

    // ??ErrorHandler
    private static final ErrorHandler EH = new ErrorHandler() {
        public void error(SAXParseException x) throws SAXException {
            throw x;
        }

        public void fatalError(SAXParseException x) throws SAXException {
            throw x;
        }

        public void warning(SAXParseException x) throws SAXException {
            log.warn("SAXParserWarnning:", x);
        }
    };

    /**
     * ??DTD?classpathDTD????DTD
     */
    private static final EntityResolver ER = new EntityResolver() {
        public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
            if (systemId != null && systemId.endsWith(".dtd")) {
                URL url = new URL(systemId);
                String file = StringUtils.substringAfterLastIfExist(url.getFile(), "/");
                URL u = this.getClass().getClassLoader().getResource(file);
                if (u == null) {
                    u = url;
                }
                InputSource source = new InputSource(u.openStream());
                source.setPublicId(publicId);
                source.setSystemId(systemId);
                return source;
            }
            return null;
        }
    };

    /**
     * DocumentBuilderCache<br>
     * ?DocumentBuilder0.4ms??
     */
    private static final ThreadLocal<DocumentBuilderCache> REUSABLE_BUILDER = new ThreadLocal<DocumentBuilderCache>() {
        @Override
        protected DocumentBuilderCache initialValue() {
            return new DocumentBuilderCache();
        }
    };

    /**
     * DocumentBuilder
     * 
     * @author jiyi
     * 
     */
    private final static class DocumentBuilderCache {
        DocumentBuilder cacheTT;
        DocumentBuilder cacheTF;
        DocumentBuilder cacheFT;
        DocumentBuilder cacheFF;

        /**
         * ????DocumentBuilder
         * 
         * @param ignorComments
         * @param namespaceAware
         * @return
         */
        private DocumentBuilder getDocumentBuilder(boolean ignorComments, boolean namespaceAware) {
            if (ignorComments && namespaceAware) {
                if (cacheTT == null) {
                    cacheTT = initBuilder(domFactoryTT);
                }
                return cacheTT;
            } else if (ignorComments) {
                if (cacheTF == null) {
                    cacheTF = initBuilder(domFactoryTF);
                }
                return cacheTF;
            } else if (namespaceAware) {
                if (cacheFT == null) {
                    cacheFT = initBuilder(domFactoryFT);
                }
                return cacheFT;
            } else {
                if (cacheFF == null) {
                    cacheFF = initBuilder(domFactoryFF);
                }
                return cacheFF;
            }
        }

        private DocumentBuilder initBuilder(DocumentBuilderFactory domFactory) {
            DocumentBuilder builder;
            try {
                builder = domFactory.newDocumentBuilder();
            } catch (ParserConfigurationException e) {
                throw new UnsupportedOperationException(e);
            }
            builder.setErrorHandler(EH);
            builder.setEntityResolver(ER);
            return builder;
        }
    }

    /**
     * Xpath?
     */
    private static XPathFactory xp = XPathFactory.newInstance();

    /**
     * HTML?
     */
    private static jef.tools.IDOMFragmentParser parser;

    /**
     * Json??XML Document(Json-Lib)
     * 
     * @param json
     *            ??json
     * @return json??XML
     * @throws SAXException
     * @throws IOException
     */
    public static Document loadDocument(JSONObject json) {
        return XMLFastJsonParser.DEFAULT.toDocument(json);
    }

    /**
     * XML Document?JsonObject,loadDocument(JsonObject json)?
     * 
     * @param node
     *            ??
     * @return ??json
     */
    public static JSONObject toJsonObject(Node node) {
        return XMLFastJsonParser.DEFAULT.toJsonObject(node);
    }

    /**
     * XML
     * 
     * @param file
     *            
     * @return Document ?DOM
     * @throws SAXException
     *             ?
     * @throws IOException
     *             ??
     */
    public static Document loadDocument(File file) throws SAXException, IOException {
        return loadDocument(file, true);
    }

    /**
     * XML
     * 
     * @param file
     *            
     * @param ignorComments
     *            ?XML
     * @return Document ?DOM
     * @throws SAXException
     * @throws IOException
     */
    public static Document loadDocument(File file, boolean ignorComments) throws SAXException, IOException {
        InputStream in = IOUtils.getInputStream(file);
        try {
            Document document = loadDocument(in, null, ignorComments, false);
            return document;
        } finally {
            in.close();
        }
    }

    /**
     * ?XML
     * 
     * @param filename
     *            
     * @return ?DOM
     * @throws SAXException
     * @throws IOException
     */
    public static Document loadDocument(String filename) throws SAXException, IOException {
        return loadDocument(new File(filename));
    }

    /**
     * URLXML
     * 
     * @param reader
     * @return ?DOM
     * @throws SAXException
     * @throws IOException
     */
    public static Document loadDocument(URL url) throws SAXException, IOException {
        return loadDocument(url.openStream(), null, true, false);
    }

    /**
     * ReaderXML
     * 
     * @param reader
     *            ?
     * @param ignorComments
     *            ?
     * @param namespaceAware
     *            ???
     * @return ?DOM
     * @throws SAXException
     * @throws IOException
     */
    public static Document loadDocument(Reader reader, boolean ignorComments, boolean namespaceAware)
            throws SAXException, IOException {
        try {
            DocumentBuilder db = REUSABLE_BUILDER.get().getDocumentBuilder(ignorComments, namespaceAware);
            InputSource is = new InputSource(reader);
            Document doc = db.parse(is);
            return doc;
        } finally {
            IOUtils.closeQuietly(reader);
        }
    }

    /**
     * ?xml
     * 
     * @param xmlContent
     *            XML
     * @return Document DOM
     * @throws SAXException
     *             ?
     * @throws IOException
     *             
     */
    public static Document parse(String xmlContent) throws SAXException, IOException {
        Reader reader = null;
        try {
            reader = new StringReader(xmlContent);
            return loadDocument(reader, true, false);
        } finally {
            IOUtils.closeQuietly(reader);
        }
    }

    /**
     * ?xml
     * 
     * @param xmlContent
     *            XML
     * @return Document ??DOM
     * @throws SAXException
     *             ?
     * @throws IOException
     *             
     * @deprecated use {@link #parse(String)}
     */
    public static Document loadDocumentByString(String xmlContent) throws SAXException, IOException {
        return parse(xmlContent);
    }

    /**
     * ?XML
     * 
     * @param in
     *            ?
     * @param charSet
     *            ?
     * @param ignorComment
     *            
     * @return Document ?DOM
     * @throws SAXException
     *             ?
     * @throws IOException
     *             
     */
    public static Document loadDocument(InputStream in, String charSet, boolean ignorComment)
            throws SAXException, IOException {
        return loadDocument(in, charSet, ignorComment, false);
    }

    /**
     * XML
     * 
     * @param in
     *            ?
     * @param charSet
     *            ?
     * @param ignorComment
     *            
     * @return Document. DOM
     * @throws SAXException
     *             ?
     * @throws IOException
     *             
     */
    public static Document loadDocument(InputStream in, String charSet, boolean ignorComments,
            boolean namespaceAware) throws SAXException, IOException {
        DocumentBuilder db = REUSABLE_BUILDER.get().getDocumentBuilder(ignorComments, namespaceAware);
        InputSource is = null;
        // ????charset
        if (charSet == null) {// ?200???
            byte[] buf = new byte[200];
            PushbackInputStream pin = new PushbackInputStream(in, 200);
            in = pin;
            int len = pin.read(buf);
            if (len > 0) {
                pin.unread(buf, 0, len);
                charSet = getCharsetInXml(buf, len);
            }
        }
        if (charSet != null) {
            is = new InputSource(new XmlFixedReader(new InputStreamReader(in, charSet)));
            is.setEncoding(charSet);
        } else { // ?
            Reader reader = new InputStreamReader(in, "UTF-8");// XML???Reader??Reader?XML?
            is = new InputSource(new XmlFixedReader(reader));
        }
        Document doc = db.parse(is);
        doc.setXmlStandalone(true);// True???standalone="no"
        return doc;

    }

    /**
     * ?XML?xml?
     * 
     * @param buf
     *            XML
     * @param len
     *            
     * @return XML???null
     */
    public static String getCharsetInXml(byte[] buf, int len) {
        buf = ArrayUtils.subarray(buf, 0, len);
        String s = new String(buf).toLowerCase();
        int n = s.indexOf("encoding=");
        if (n > -1) {
            s = s.substring(n + 9);
            if (s.charAt(0) == '\"' || s.charAt(0) == '\'') {
                s = s.substring(1);
            }
            n = StringUtils.indexOfAny(s, "\"' ><");
            if (n > -1) {
                s = s.substring(0, n);
            }
            if (StringUtils.isEmpty(s)) {
                return null;
            }
            s = Charsets.getStdName(s);
            return s;
        } else {
            return null;
        }
    }

    /**
     * HTML
     * 
     * @param in
     *            ?
     * @return DocumentFragment DOM
     * @throws SAXException
     *             ?
     * @throws IOException
     *             
     */
    public static DocumentFragment parseHTML(Reader in) throws SAXException, IOException {
        if (parser == null)
            throw new UnsupportedOperationException(
                    "HTML parser module not loaded, to activate this feature, you must add JEF common-ioc.jar to classpath");
        InputSource source;
        source = new InputSource(in);
        synchronized (parser) {
            HTMLDocument document = new HTMLDocumentImpl();
            DocumentFragment fragment = document.createDocumentFragment();
            parser.parse(source, fragment);
            return fragment;
        }
    }

    /**
     * HTML
     * 
     * @param in
     *            ?
     * @param charSet
     *            ?
     * @return DocumentFragment DOM
     * @throws SAXException
     *             ?
     * @throws IOException
     *             
     */
    public static DocumentFragment parseHTML(File file) throws IOException, SAXException {
        InputStream in = IOUtils.getInputStream(file);
        try {
            DocumentFragment document = parseHTML(in, null);
            return document;
        } finally {
            in.close();
        }
    }

    /**
     * ?HTMLDocument
     * 
     * @param url
     *            ?
     * @return DocumentFragment DOM
     * @throws SAXException
     * @throws IOException
     */
    public static DocumentFragment parseHTML(URL url) throws SAXException, IOException {
        return parseHTML(url.openStream(), null);
    }

    /**
     * ??HTML?
     * 
     * @param in
     *            ?
     * @param charSet
     *            null
     * @return ??DocumentFragment
     * @throws SAXException
     *             XML
     * @throws IOException
     *             IO?
     * @deprecated Use {@link #parseHTML(InputStream, String)} instead.
     */
    public static DocumentFragment loadHtmlDocument(InputStream in, String charSet)
            throws SAXException, IOException {
        return parseHTML(in, charSet);
    }

    /**
     * ??HTML
     * 
     * @param in
     *            ?
     * @param charSet
     *            null
     * @return ??DocumentFragment
     * @throws SAXException
     *             XML
     * @throws IOException
     *             IO?
     */
    public static DocumentFragment parseHTML(InputStream in, String charSet) throws SAXException, IOException {
        if (parser == null)
            throw new UnsupportedOperationException(
                    "HTML parser module not loaded, to activate this feature, you must add JEF common-ioc.jar to classpath");
        InputSource source;
        if (charSet != null) {
            source = new InputSource(new XmlFixedReader(new InputStreamReader(in, charSet)));
            source.setEncoding(charSet);
        } else {
            source = new InputSource(in);
        }
        synchronized (parser) {
            HTMLDocument document = new HTMLDocumentImpl();
            DocumentFragment fragment = document.createDocumentFragment();
            parser.parse(source, fragment);
            return fragment;
        }
    }

    /**
     * ?XML
     * 
     * @param doc
     *            DOM
     * @param file
     *            ?
     * @throws IOException
     *             
     */
    public static void saveDocument(Node doc, File file) throws IOException {
        saveDocument(doc, file, "UTF-8");
    }

    /**
     * ?XML
     * 
     * @param doc
     *            DOM
     * @param file
     *            
     * @param encoding
     *            ?
     * @throws IOException
     */
    public static void saveDocument(Node doc, File file, String encoding) throws IOException {
        OutputStream os = IOUtils.getOutputStream(file);
        try {
            output(doc, os, encoding);
        } finally {
            os.close();
        }
    }

    /**
     * XML?
     * 
     * @param node
     *            DOM
     * @param os
     *            ?
     * @throws IOException
     *             
     */
    public static void output(Node node, OutputStream os) throws IOException {
        output(node, os, null, 4, null);
    }

    /**
     * XML?
     * 
     * @param node
     *            DOM
     * @param os
     *            ?
     * @param encoding
     *            ?
     * @throws IOException
     *             
     */
    public static void output(Node node, OutputStream os, String encoding) {
        output(node, os, encoding, 4, null);
    }

    /**
     * ?String
     * 
     * @param node
     *            DOM
     * @return ??XML
     */
    public static String toString(Node node) {
        return toString(node, null);
    }

    /**
     * XML?
     * 
     * @param node
     *            DOM
     * @param os
     *            ?
     * @param encoding
     *            ?
     * @param warpLine
     *            
     * @param xmlDeclare
     *            nulldocument?xmltrue? false
     * @throws IOException
     *             
     */
    public static void output(Node node, OutputStream os, String encoding, int warpLine, Boolean xmlDeclare) {
        try {
            StreamResult sr = new StreamResult(
                    encoding == null ? new OutputStreamWriter(os) : new OutputStreamWriter(os, encoding));
            output(node, sr, encoding, warpLine, xmlDeclare);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * ?
     * 
     * @param node
     *            ??Document
     * @param os
     *            ?
     * @param encoding
     *            ?
     * @param warpLine
     *            ??
     * @throws IOException
     *             
     */
    public static void output(Node node, Writer os, String encoding, int indent) throws IOException {
        StreamResult sr = new StreamResult(os);
        output(node, sr, encoding, indent, null);
    }

    private static void output(Node node, StreamResult sr, String encoding, int indent, Boolean XmlDeclarion)
            throws IOException {
        if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
            sr.getWriter().write(node.getNodeValue());
            sr.getWriter().flush();
            return;
        }

        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer t = null;
        try {
            if (indent > 0) {
                try {
                    tf.setAttribute("indent-number", indent);
                    t = tf.newTransformer();
                    // ?XML??XML?
                    t.setOutputProperty(OutputKeys.INDENT, "yes");
                } catch (Exception e) {
                }
            } else {
                t = tf.newTransformer();
            }

            t.setOutputProperty(OutputKeys.METHOD, "xml");
            if (encoding != null) {
                t.setOutputProperty(OutputKeys.ENCODING, encoding);
            }
            if (XmlDeclarion == null) {
                XmlDeclarion = (node instanceof Document);
            }
            if (node instanceof Document) {
                Document doc = (Document) node;
                if (doc.getDoctype() != null) {
                    t.setOutputProperty(javax.xml.transform.OutputKeys.DOCTYPE_PUBLIC,
                            doc.getDoctype().getPublicId());
                    t.setOutputProperty(javax.xml.transform.OutputKeys.DOCTYPE_SYSTEM,
                            doc.getDoctype().getSystemId());
                }
            }
            if (XmlDeclarion) {
                t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
            } else {
                t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            }
        } catch (Exception tce) {
            throw new IOException(tce);
        }
        DOMSource doms = new DOMSource(node);
        try {
            t.transform(doms, sr);
        } catch (TransformerException te) {
            IOException ioe = new IOException();
            ioe.initCause(te);
            throw ioe;
        }
    }

    /**
     * CDATA
     * 
     * @param node
     *            
     * @param data
     *            CDATA
     * @return ?CDATA
     */
    public static CDATASection addCDataText(Node node, String data) {
        Document doc = null;
        if (node.getNodeType() == Node.DOCUMENT_NODE) {
            doc = (Document) node;
        } else {
            doc = node.getOwnerDocument();
        }
        CDATASection e = doc.createCDATASection(data);
        node.appendChild(e);
        return e;
    }

    /**
     * XPath
     * 
     * @param startPoint
     *            
     * @param expr
     *            xpath?
     * @return xpath?
     * @throws XPathExpressionException
     */
    public static String evalXpath(Object startPoint, String expr) throws XPathExpressionException {
        XPath xpath = xp.newXPath();
        return xpath.evaluate(expr, startPoint);
    }

    /**
     * XPATH
     * 
     * @param startPoint
     *            
     * @param expr
     *            xpath?
     * @return xpath
     * @throws XPathExpressionException
     */
    public static Node selectNode(Object startPoint, String expr) throws XPathExpressionException {
        XPath xpath = xp.newXPath();
        return (Node) xpath.evaluate(expr, startPoint, XPathConstants.NODE);
    }

    /**
     * XPATH
     * 
     * @param startPoint
     *            
     * @param expr
     *            xpath?
     * @return ?xpath
     * @throws XPathExpressionException
     */
    public static NodeList selectNodes(Object startPoint, String expr) throws XPathExpressionException {
        XPath xpath = xp.newXPath();
        return (NodeList) xpath.evaluate(expr, startPoint, XPathConstants.NODESET);
    }

    /**
     * XPATH
     * 
     * @param start
     * @param expr
     * @return
     * @throws XPathExpressionException
     */
    public static List<Element> selectElements(Node start, String expr) throws XPathExpressionException {
        return toElementList(selectNodes(start, expr));
    }

    /**
     * ?
     * 
     * @param node
     *            
     * @param data
     *            
     * @return DOM
     */
    public static Text setText(Node node, String data) {
        Document doc = null;
        if (node.getNodeType() == Node.DOCUMENT_NODE) {
            doc = (Document) node;
        } else {
            doc = node.getOwnerDocument();
        }
        clearChildren(node, Node.TEXT_NODE);
        Text t = doc.createTextNode(data);
        node.appendChild(t);
        return t;
    }

    /**
     * ?
     * 
     * @param node
     *            
     * @param comment
     *            
     * @return Comment
     */
    public static Comment addComment(Node node, String comment) {
        Document doc = null;
        if (node.getNodeType() == Node.DOCUMENT_NODE) {
            doc = (Document) node;
        } else {
            doc = node.getOwnerDocument();
        }
        Comment e = doc.createComment(comment);
        node.appendChild(e);
        return e;
    }

    /**
     * ?? 
     * 
     * <pre>
     *    &lt;parent&gt;
     *       &lt;a&gt;text-a&lt;/a&gt;
     *       &lt;c&gt;text-c&lt;/c&gt;
     *    &lt;/parent&gt;
     * </pre>
     * 
     * {@code addElementBefore(c,"b","text-b")}c?c 
     * 
     * <pre>
     *     &lt;parent&gt;
     *       &lt;a&gt;text-a&lt;/a&gt;
     *       &lt;b&gt;text-b&lt;/b&gt;
     *       &lt;c&gt;text-c&lt;/c&gt;
     *    &lt;/parent&gt;
     * </pre>
     * 
     * @param node
     *            DOM
     * @param tagName
     *            ??
     * @param nodeText
     *            
     * @return Element
     */
    public static Element addElementBefore(Node node, String tagName, String... nodeText) {
        Node pNode = node.getParentNode();
        List<Node> movingNodes = new ArrayList<Node>();
        for (Node n : toArray(pNode.getChildNodes())) {
            if (n == node) {
                movingNodes.add(n);
            } else if (movingNodes.size() > 0) {
                movingNodes.add(n);
            }
        }
        Element e = addElement(pNode, tagName, nodeText);
        for (Node n : movingNodes) {
            pNode.appendChild(n);
        }
        return e;
    }

    /**
     * ??
     * 
     * @param node
     *            DOM
     * @param tagName
     *            ??
     * @param nodeText
     *            
     * @return Element
     */
    public static Element addElementAfter(Node node, String tagName, String... nodeText) {
        Node pNode = node.getParentNode();
        List<Node> movingNodes = new ArrayList<Node>();
        boolean flag = false;
        for (Node n : toArray(pNode.getChildNodes())) {
            if (flag) {
                movingNodes.add(n);
            } else if (n == node) {
                flag = true;
            }
        }
        Element e = addElement(pNode, tagName, nodeText);
        for (Node n : movingNodes) {
            pNode.appendChild(n);
        }
        return e;
    }

    /**
     * ???
     * 
     * @param node
     *            
     * @param tagName
     *            ??
     * @param nodeText
     *            
     * @return Element
     */
    public static Element replaceElement(Node node, String tagName, String... nodeText) {
        Node pNode = node.getParentNode();
        Assert.notNull(pNode);
        Document doc = null;
        if (node.getNodeType() == Node.DOCUMENT_NODE) {
            doc = (Document) node;
        } else {
            doc = node.getOwnerDocument();
        }
        Element e = doc.createElement(tagName);
        if (nodeText.length == 1) {
            setText(e, nodeText[0]);
        } else if (nodeText.length > 1) {
            setText(e, StringUtils.join(nodeText, '\n'));
        }
        pNode.replaceChild(e, node);
        return e;
    }

    /**
     * Element
     * 
     * @param parent
     *            
     * @param tagName
     *            ???
     * @param attribName
     *            ??
     * @param attribValue
     *            
     * @return 
     */
    public static Element getOrCreateChildElement(Node parent, String tagName, String attribName,
            String attribValue) {
        for (Element e : XMLUtils.childElements(parent, tagName)) {
            if (attribValue == null || attribValue.equals(XMLUtils.attrib(e, attribName))) {
                return e;
            }
        }
        Element e = XMLUtils.addElement(parent, tagName);
        e.setAttribute(attribName, attribValue);
        return e;
    }

    /**
     * TagName
     * 
     * @param node
     *            
     * @param tagName
     *            ???
     * @return ?
     */
    public static int removeChildElements(Node node, String... tagName) {
        List<Element> list = XMLUtils.childElements(node, tagName);
        for (Element e : list) {
            node.removeChild(e);
        }
        return list.size();
    }

    /**
     * ???
     * 
     * @param node
     *            
     */
    public static void clearChildren(Node node) {
        clearChildren(node, 0);
    }

    /**
     * 
     * 
     * @param node
     * @param type
     *            ??NodeType0
     */
    public static void clearChildren(Node node, int type) {
        for (Node child : toArray(node.getChildNodes())) {
            if (type == 0 || child.getNodeType() == type) {
                node.removeChild(child);
            }
        }
    }

    /**
     * 
     * 
     * @param element
     *            ?
     */
    public static void clearAttribute(Element element) {
        for (Node node : toArray(element.getAttributes())) {
            element.removeAttributeNode((Attr) node);
        }
    }

    /**
     * ?
     * 
     * @param element
     *            ?
     */
    public static void clearChildrenAndAttr(Element element) {
        clearChildren(element);
        clearAttribute(element);
    }

    /**
     * ?
     * 
     * @param node
     *            
     * @param tagName
     *            ??
     * @param nodeText
     *            
     * @return Element
     */
    public static Element addElement(Node node, String tagName, String... nodeText) {
        Document doc = null;
        if (node.getNodeType() == Node.DOCUMENT_NODE) {
            doc = (Document) node;
        } else {
            doc = node.getOwnerDocument();
        }
        Element e = doc.createElement(tagName);
        node.appendChild(e);
        if (nodeText.length == 1) {
            setText(e, nodeText[0]);
        } else if (nodeText.length > 1) {
            setText(e, StringUtils.join(nodeText, '\n'));
        }
        return e;
    }

    /**
     * ?????
     * 
     * @param node
     *            ????
     * @param newName
     *            ??
     * @return ????DOMElement
     */
    public static Element changeNodeName(Element node, String newName) {
        Document doc = node.getOwnerDocument();
        Element newEle = doc.createElement(newName);
        Node parent = node.getParentNode();
        parent.removeChild(node);
        parent.appendChild(newEle);

        for (Node child : toArray(node.getChildNodes())) {
            node.removeChild(child);
            newEle.appendChild(child);
        }
        return newEle;
    }

    /**
     * Element(??)
     * 
     * @param node
     *            
     * @param tagName
     *            ????nullElement
     * @return ??
     */
    public static List<Element> childElements(Node node, String... tagName) {
        if (node == null)
            throw new NullPointerException("the input node can not be null!");
        List<Element> list = new ArrayList<Element>();
        NodeList nds = node.getChildNodes();
        if (tagName.length == 0 || tagName[0] == null) {// ?API
            tagName = null;
        }
        for (int i = 0; i < nds.getLength(); i++) {
            Node child = nds.item(i);
            if (child.getNodeType() == Node.ELEMENT_NODE) {
                Element e = (Element) child;
                if (tagName == null || ArrayUtils.contains(tagName, e.getNodeName())) {
                    list.add(e);
                }
            } else if (child.getNodeType() == Node.CDATA_SECTION_NODE) {
            } else if (child.getNodeType() == Node.COMMENT_NODE) {
            } else if (child.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE) {

            } else if (child.getNodeType() == Node.DOCUMENT_NODE) {

            } else if (child.getNodeType() == Node.DOCUMENT_TYPE_NODE) {
            } else if (child.getNodeType() == Node.ATTRIBUTE_NODE) {
            } else if (child.getNodeType() == Node.TEXT_NODE) {
            }
        }
        return list;
    }

    static class MyNodeList implements NodeList {
        Node[] list;

        public int getLength() {
            return list.length;
        }

        public Node item(int index) {
            return list[index];
        }

        public MyNodeList(Node[] list) {
            this.list = list;
        }

        public MyNodeList(List<? extends Node> list) {
            this.list = list.toArray(new Node[list.size()]);
        }
    }

    /**
     * ?(Trimed)
     * 
     * @param element
     *            
     * @return xml
     */
    public static String nodeText(Node element) {
        Node first = first(element, Node.TEXT_NODE, Node.CDATA_SECTION_NODE);
        if (first != null && first.getNodeType() == Node.CDATA_SECTION_NODE) {
            return ((CDATASection) first).getTextContent();
        }
        StringBuilder sb = new StringBuilder();
        if (first == null || StringUtils.isBlank(first.getTextContent())) {
            for (Node n : toArray(element.getChildNodes())) {
                if (n.getNodeType() == Node.TEXT_NODE) {
                    sb.append(n.getTextContent());
                } else if (n.getNodeType() == Node.CDATA_SECTION_NODE) {
                    sb.append(((CDATASection) n).getTextContent());
                }
            }
        } else {
            sb.append(first.getTextContent());
        }
        return StringUtils.trimToNull(StringEscapeUtils.unescapeHtml(sb.toString()));
    }

    /**
     * text
     * 
     * @param element
     *            
     * @param withChildren
     *            ???
     * @return xml
     */
    public static String nodeText(Node element, boolean withChildren) {
        StringBuilder sb = new StringBuilder();
        for (Node node : toArray(element.getChildNodes())) {
            if (node.getNodeType() == Node.TEXT_NODE) {
                sb.append(node.getNodeValue().trim());
            } else if (node.getNodeType() == Node.CDATA_SECTION_NODE) {
                sb.append(((CDATASection) node).getTextContent());
            } else if (withChildren) {
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    sb.append(nodeText((Element) node, true));
                }
            }
        }
        return sb.toString();
    }

    /**
     * 
     * 
     * @param e
     *            
     * @param attributeName
     *            ??
     * @return xml
     */
    public static String attrib(Element e, String attributeName) {
        if (!e.hasAttribute(attributeName))
            return null;
        String text = e.getAttribute(attributeName);
        return (text == null) ? null : StringEscapeUtils.unescapeXml(text.trim());
    }

    /**
     * (???)
     * 
     * @param e
     *            
     * @param attributeName
     *            ??
     * @return ??List
     */
    public static List<String> attribs(Element e, String attributeName) {
        List<String> _list = new ArrayList<String>();
        if (e.hasAttribute(attributeName)) {
            String text = e.getAttribute(attributeName);
            _list.add((text == null) ? null : StringEscapeUtils.unescapeHtml(text.trim()));
        }
        if (e.hasChildNodes()) {
            NodeList nds = e.getChildNodes();
            for (int i = 0; i < nds.getLength(); i++) {
                Node child = nds.item(i);
                if (child.getNodeType() == Node.ELEMENT_NODE) {
                    _list.addAll(attribs((Element) child, attributeName));
                }
            }
        }
        return _list;
    }

    /**
     * ???(Trim)
     * 
     * @param element
     *            
     * @param subEleName
     *            ???
     * @return ?xml
     */
    public static String nodeText(Element element, String subEleName) {
        Element e = first(element, subEleName);
        if (e == null)
            return null;
        return nodeText(e);
    }

    /**
     * n(?)
     * 
     * @param parent
     *            
     * @param elementName
     *            ??
     * @param index
     *            ??
     * @return DOM
     */
    public static Element nthElement(Element parent, String elementName, int index) {
        NodeList nds = parent.getElementsByTagName(elementName);
        if (nds.getLength() < index)
            throw new NoSuchElementException();
        Element node = (Element) nds.item(index - 1);
        return node;
    }

    /**
     * ??Tag Name?
     * 
     * @param parent
     *            
     * @param elementName
     *            ???
     * @return ??
     */
    public static Element first(Node node, String tagName) {
        if (node == null)
            return null;
        NodeList nds = node.getChildNodes();
        for (int i = 0; i < nds.getLength(); i++) {
            Node child = nds.item(i);
            if (child.getNodeType() == Node.ELEMENT_NODE) {
                Element e = (Element) child;
                if (tagName == null || tagName.equals(e.getNodeName())) {
                    return e;
                }
                // } else if (child.getNodeType() == Node.CDATA_SECTION_NODE) {
                // } else if (child.getNodeType() == Node.COMMENT_NODE) {
                // } else if (child.getNodeType() ==
                // Node.DOCUMENT_FRAGMENT_NODE) {
                // } else if (child.getNodeType() == Node.DOCUMENT_NODE) {
                // } else if (child.getNodeType() == Node.DOCUMENT_TYPE_NODE) {
                // } else if (child.getNodeType() == Node.ATTRIBUTE_NODE) {
                // } else if (child.getNodeType() == Node.TEXT_NODE) {
            }
        }
        return null;
    }

    /**
     * ?(?)
     * 
     * @param node
     *            
     * @param nodeType
     *            Node?{@link Node#ELEMENT_NODE}?
     *            {@link Node#DOCUMENT_NODE}
     * @return 
     */
    public static Node first(Node node, int... nodeType) {
        if (node == null)
            return null;
        NodeList nds = node.getChildNodes();
        for (int i = 0; i < nds.getLength(); i++) {
            Node child = nds.item(i);
            if (ArrayUtils.contains(nodeType, child.getNodeType())) {
                return child;
            }
        }
        return null;
    }

    /**
     * XML
     * 
     * @param tagName
     *            ??
     * @return Document
     */
    public static Document newDocument(String tagName) {
        Assert.notNull(tagName);
        Document doc = newDocument();
        addElement(doc, tagName);
        return doc;
    }

    /**
     * XML
     * 
     * @return XML
     */
    public static Document newDocument() {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.newDocument();
            document.setXmlStandalone(true);
            return document;
        } catch (ParserConfigurationException e) {
            LogUtil.exception(e);
            return null;
        }
    }

    /**
     * NamedNodeMap?Node
     * 
     * @param nds
     *            NamedNodeMap
     * @return Node
     */
    public static Node[] toArray(NamedNodeMap nds) {
        Node[] array = new Node[nds.getLength()];
        for (int i = 0; i < nds.getLength(); i++) {
            array[i] = nds.item(i);
        }
        return array;
    }

    /**
     * Map
     * 
     * @param e
     *            
     * @param attrMap
     *            
     * @param isSubNode
     *            ?false,true?
     */
    public static void setAttributesByMap(Element e, Map<String, Object> attrMap, boolean isSubNode) {
        if (attrMap == null)
            return;
        setAttrMap(e, attrMap, isSubNode);
    }

    /**
     * Map
     * 
     * @param e
     *            
     * @param map
     *            
     */
    public static void setAttributesByMap(Element e, Map<String, Object> map) {
        setAttributesByMap(e, map, false);
    }

    @SuppressWarnings("rawtypes")
    private static void setAttrMap(Element e, Map attrMap, boolean isSubNode) {
        if (isSubNode) {
            for (Object keyObj : attrMap.keySet()) {
                String key = StringUtils.toString(keyObj);
                Object value = attrMap.get(key);
                if (value.getClass().isArray()) {
                    setAttrArray(e, key, (Object[]) value, isSubNode);
                    continue;
                } else if (value instanceof List) {
                    setAttrArray(e, key, ((List) value).toArray(), isSubNode);
                    continue;
                }
                Element child = first(e, key);
                if (child == null) {
                    child = addElement(e, key);
                }

                if (value instanceof Map) {
                    setAttrMap(child, (Map) value, isSubNode);
                } else {
                    setText(child, StringUtils.toString(value));
                }

            }
        } else {
            for (Object keyObj : attrMap.keySet()) {
                String key = StringUtils.toString(keyObj);
                Object value = attrMap.get(key);
                if (value instanceof Map) {
                    Element child = first(e, key);
                    if (child == null) {
                        child = addElement(e, key);
                    }
                    setAttrMap(child, (Map) value, isSubNode);
                } else if (value.getClass().isArray()) {
                    setAttrArray(e, key, (Object[]) value, isSubNode);
                } else if (value instanceof List) {
                    setAttrArray(e, key, ((List) value).toArray(), isSubNode);
                } else {
                    e.setAttribute(key, StringUtils.toString(value));
                }

            }
        }
    }

    @SuppressWarnings("rawtypes")
    private static void setAttrArray(Element e, String key, Object[] value, boolean isSubNode) {
        for (Object o : value) {
            if (o instanceof Map) {
                Element child = addElement(e, key);
                setAttrMap(child, (Map) o, isSubNode);
            } else {
                Element child = addElement(e, key);
                setText(child, StringUtils.toString(o));
            }
        }
    }

    /**
     * ?
     * 
     * @param parent
     *            
     * @param tagName
     *            ????
     * @param value
     *            
     */
    public static void setNodeText(Element parent, String tagName, String value) {
        Element child = first(parent, tagName);
        if (child != null) {
            setText(child, value);
        }
    }

    /**
     * ?Map?
     * 
     * @param e
     *            
     * @return ???Map
     */
    public static Map<String, String> getAttributesMap(Element e) {
        return getAttributesMap(e, false);
    }

    /**
     * ?
     * 
     * @param e
     *            
     * @param subElementAsAttr
     *            trueElement?<br>
     *            
     * 
     *            <pre>
     * &lt;Foo size="103" name="Karen"&gt;
     *   &lt;dob&gt;2012-4-12&lt;/dobh&gt;
     *   &lt;dod&gt;2052-4-12&lt;/dodh&gt;
     * &lt;/Foo&gt;
     * </pre>
     * 
     *            subElementAsAttr=falsedob,dod?true?
     * @return
     */
    public static Map<String, String> getAttributesMap(Element e, boolean subElementAsAttr) {
        Map<String, String> attribs = new HashMap<String, String>();
        if (e == null)
            return attribs;
        NamedNodeMap nmp = e.getAttributes();
        for (int i = 0; i < nmp.getLength(); i++) {
            Attr child = (Attr) nmp.item(i);
            attribs.put(StringEscapeUtils.unescapeHtml(child.getName()),
                    StringEscapeUtils.unescapeHtml(child.getValue()));
        }
        if (subElementAsAttr) {
            NodeList nds = e.getChildNodes();
            for (int i = 0; i < nds.getLength(); i++) {
                Node node = nds.item(i);
                if (node.getNodeType() != Node.ELEMENT_NODE)
                    continue;
                Element sub = (Element) node;
                String key = sub.getNodeName();
                String value = nodeText(sub);
                if (attribs.containsKey(key)) {
                    attribs.put(key, attribs.get(key) + "," + value);
                } else {
                    attribs.put(key, value);
                }
            }
        }
        return attribs;
    }

    /**
     * ?
     * 
     * <pre>
     * &lt;object&gt;
     * &lt;id&gt;100&lt;/id&gt;
     * &lt;name&gt;Jhon smith&lt;/name&gt;
     * &lt;phone&gt;130100000&lt;/phone&gt;
     * &lt;object&gt;
     * </pre>
     * 
     * ?
     * {@code getAttributesInChildElements(objectNode, "name", "phone")}?
     * {name="Jhon smith"phone="130100000"}Map
     * ?????Map
     * 
     * @param parent
     *            
     * @param keys
     *            ?
     * @return ????Map
     */
    public static Map<String, String> getAttributesInChildElements(Element parent, String... keys) {
        NodeList nds = parent.getChildNodes();
        Map<String, String> attribs = new HashMap<String, String>();
        for (int i = 0; i < nds.getLength(); i++) {
            Node node = nds.item(i);
            if (node.getNodeType() != Node.ELEMENT_NODE)
                continue;
            Element sub = (Element) node;
            String key = sub.getNodeName();
            if (keys.length == 0 || ArrayUtils.contains(keys, key)) {
                String value = nodeText(sub);
                if (attribs.containsKey(key)) {
                    attribs.put(key, attribs.get(key) + "," + value);
                } else {
                    attribs.put(key, value);
                }
            }
        }
        return attribs;
    }

    /**
     * 
     * 
     * <pre>
     * &lt;object&gt;
     * &lt;id&gt;100&lt;/id&gt;
     * &lt;name&gt;Jhon smith&lt;/name&gt;
     * &lt;phone&gt;130100000&lt;/phone&gt;
     * &lt;object&gt;
     * </pre>
     * 
     * XML??
     * 
     * <pre>
     * &lt;object id="100" name="Jhon smith" pone="130100000"&gt;
     * &lt;/object&gt;
     * </pre>
     * 
     * ?
     * 
     * @param e
     *            ??
     * @param keys
     *            ??
     */
    public static void moveChildElementAsAttribute(Element e, String... keys) {
        NodeList nds = e.getChildNodes();
        for (Node node : toArray(nds)) {
            if (node.getNodeType() == Node.TEXT_NODE) {
                e.removeChild(node); // 
            }
            if (node.getNodeType() != Node.ELEMENT_NODE)
                continue;
            Element sub = (Element) node;
            String key = sub.getNodeName();
            if (keys.length == 0 || ArrayUtils.contains(keys, key)) {
                String value = nodeText(sub);
                e.setAttribute(key, value);
                e.removeChild(sub);
            }

        }
    }

    /**
     * XML?Java
     * 
     * @param e
     *            
     * @param clz
     *            ??java
     * @throws ReflectionException
     *             ??
     * @deprecated loadBean(Element,Class)
     */
    public static <W> W elementToBean(Element e, Class<W> clz) throws ReflectionException {
        return loadBean(e, clz);
    }

    /**
     * XML?Java
     * <p>
     * ??putBean?????load bean?load
     * beanbean??? putBean
     * 
     * @param e
     *            
     * @param bean
     *            ??java
     * @throws ReflectionException
     *             ??
     */
    public static <W> W loadBean(Element e, Class<W> clz) {
        W bean = UnsafeUtils.newInstance(clz);
        BeanWrapperImpl bw = new BeanWrapperImpl(bean);
        Map<String, String> attrs = getAttributesMap(e, true);
        for (String key : bw.getPropertyNames()) {
            if (attrs.containsKey(key)) {
                bw.setPropertyValueByString(key, attrs.get(key));
            }
        }
        return bean;
    }

    /**
     * java bean?XML ???XML?
     * 
     * @param parent
     *            ?
     * @param bean
     *            ?
     * @return ??
     */
    public static Element putBean(Node parent, Object bean) {
        if (bean == null)
            return null;
        return appendBean(parent, bean, bean.getClass(), null, null);
    }

    /**
     * java bean?XML ???XML?
     * 
     * 
     * @param node
     *            ?
     * @param bean
     *            ?
     * @param tryAttribute
     *            true?XML falseXML
     *            null???
     * @return ??
     */
    public static Element putBean(Node node, Object bean, Boolean tryAttribute) {
        if (bean == null)
            return null;
        return appendBean(node, bean, bean.getClass(), tryAttribute, null);
    }

    /**
     * ??Document?<br/>
     * ???Document?
     * 
     * @param parent
     *            
     * @param nodes
     *            ???
     */
    public static void appendChild(Node parent, Node... nodes) {
        Document doc = parent.getOwnerDocument();
        for (Node node : nodes) {
            if (node.getOwnerDocument() != doc) {
                parent.appendChild(doc.importNode(node, true));
            } else {
                parent.appendChild(node);
            }
        }
    }

    /**
     * NodeList?
     * 
     * @param nds
     *            NodeList
     * @return Node
     */
    public static Node[] toArray(NodeList nds) {
        if (nds instanceof MyNodeList)
            return ((MyNodeList) nds).list;
        Node[] array = new Node[nds.getLength()];
        for (int i = 0; i < nds.getLength(); i++) {
            array[i] = nds.item(i);
        }
        return array;
    }

    /**
     * NodeList?List
     * 
     * @param nds
     *            NodeList
     * @return Node?List
     */
    public static List<? extends Node> toList(NodeList nds) {
        if (nds instanceof MyNodeList)
            return Arrays.asList(((MyNodeList) nds).list);
        List<Node> list = new ArrayList<Node>();

        for (int i = 0; i < nds.getLength(); i++) {
            Node child = nds.item(i);
            list.add(child);
        }
        return list;
    }

    /**
     * Nodelist?Element List
     * 
     * @param nds
     *            NodeList
     * @return NodeListElement?List
     */
    public static List<Element> toElementList(NodeList nds) {
        List<Element> list = new ArrayList<Element>();
        for (int i = 0; i < nds.getLength(); i++) {
            Node child = nds.item(i);
            if (child.getNodeType() == Node.ELEMENT_NODE) {
                list.add((Element) child);
            }
        }
        return list;
    }

    /**
     * Node?NodeList{@link #toList(NodeList)}?
     * 
     * @param list
     *            Node
     * @return NodeList
     */
    public static NodeList toNodeList(List<? extends Node> list) {
        return new MyNodeList(list);
    }

    /**
     * Node?NodeList{@link #toArray(NodeList)}?
     * 
     * @param array
     *            Node
     * @return NodeList
     */
    public static NodeList toNodeList(Node[] array) {
        return new MyNodeList(array);
    }

    /**
     * ??
     * 
     * @param node
     *            ?
     * @param text
     *            
     * @param searchAttribute
     *            ?
     * @return Node?null
     */
    public static Node findFirst(Node node, String text, boolean searchAttribute) {
        String value = getValue(node);
        if (value != null && value.indexOf(text) > -1)
            return node;
        if (searchAttribute && node.getAttributes() != null) {
            for (Node n : toArray(node.getAttributes())) {
                value = getValue(n);
                if (value != null && value.indexOf(text) > -1)
                    return n;
            }
        }
        for (Node sub : toArray(node.getChildNodes())) {
            Node nd = findFirst(sub, text, searchAttribute);
            if (nd != null)
                return nd;
        }
        return null;
    }

    /**
     * 
     * 
     * @param node
     *            ?
     * @param text
     *            
     * @param searchAttribute
     *            ?
     */
    public static void removeNodeWithKeyword(Node node, String text, boolean searchAttribute) {
        String value = getValue(node);
        if (value != null && value.indexOf(text) > -1) {
            node.getParentNode().removeChild(node);
            return;
        }

        if (searchAttribute && node.getAttributes() != null) {
            for (Node n : toArray(node.getAttributes())) {
                value = getValue(n);
                if (value != null && value.indexOf(text) > -1) {
                    node.getParentNode().removeChild(node);
                    return;
                }
            }
        }
        for (Node sub : toArray(node.getChildNodes())) {
            removeNodeWithKeyword(sub, text, searchAttribute);
        }
    }

    /**
     * ??
     * 
     * @param node
     *            
     * @param text
     *            
     * @param searchAttribute
     *            ?
     * @return Node?
     */
    public static Node[] find(Node node, String text, boolean searchAttribute) {
        List<Node> result = new ArrayList<Node>();
        innerSearch(node, text, result, searchAttribute);
        return result.toArray(new Node[0]);
    }

    /**
     * ??Element??
     * 
     * @param root
     *            
     * @param tagName
     *            ??element??
     * @param attribName
     *            ????
     * @param keyword
     *            ??
     * @return ??
     */
    public static Element findElementByNameAndAttribute(Node root, String tagName, String attribName,
            String keyword) {
        Element[] es = findElementsByNameAndAttribute(root, tagName, attribName, keyword, true);
        if (es.length > 0)
            return es[0];
        return null;
    }

    /**
     * ??Element??
     * 
     * @param root
     *            
     * @param tagName
     *            ??element??
     * @param attribName
     *            ????
     * @param keyword
     *            ??
     * @return ??
     */
    public static Element[] findElementsByNameAndAttribute(Node root, String tagName, String attribName,
            String keyword) {
        return findElementsByNameAndAttribute(root, tagName, attribName, keyword, false);
    }

    /**
     * ?Element
     * 
     * @param node
     *            
     * @param attribName
     *            ??
     * @param keyword
     *            ??
     * @return ??Element
     */
    public static Element findElementByAttribute(Node node, String attribName, String keyword) {
        Element[] result = findElementsByAttribute(node, attribName, keyword, true);
        if (result.length == 0)
            return null;
        return result[0];
    }

    /**
     * Element,?
     * 
     * @param node
     *            
     * @param attribName
     *            ??
     * @param keyword
     *            
     * @return ??
     */
    public static Element[] findElementsByAttribute(Node node, String attribName, String keyword) {
        return findElementsByAttribute(node, attribName, keyword, false);
    }

    /**
     * ?attribid?JSdocument.getElementById();
     * 
     * @param node
     *            
     * @param id
     *            ID
     * @return 
     */
    public static Element findElementById(Node node, String id) {
        if (node == null)
            return null;
        if (node.getNodeType() == Node.ELEMENT_NODE) {
            Element e = (Element) node;
            if (e.hasAttribute("id")) {
                String ss = StringUtils.trim(e.getAttribute("id"));
                if (ss.equals(id)) {
                    return e;
                }
            }
        }
        for (Node sub : toArray(node.getChildNodes())) {
            Element nd = findElementById(sub, id);
            if (nd != null)
                return nd;
        }
        return null;
    }

    /**
     * ???tagNameElement
     * 
     * @param node
     *            
     * @param tagName
     *            ?????
     * @return ?
     */
    public static Element firstParent(Node node, String tagName) {
        if (StringUtils.isEmpty(tagName))
            return (Element) node.getParentNode();
        Node p = node.getParentNode();
        while (p != null) {
            if (p.getNodeType() == Node.ELEMENT_NODE && p.getNodeName().equals(tagName)) {
                return (Element) p;
            }
            p = p.getParentNode();
        }
        return null;
    }

    /**
     * ??
     * 
     * @param node
     *            
     * @param tagName
     *            ????
     * @return ?
     */
    public static Element firstSibling(Node node, String tagName) {
        Node p = node.getNextSibling();
        while (p != null) {
            if (p.getNodeType() == Node.ELEMENT_NODE) {
                if (StringUtils.isEmpty(tagName) || p.getNodeName().equals(tagName))
                    return (Element) p;
            }
            p = p.getNextSibling();
        }
        return null;
    }

    /**
     * ????
     * 
     * @param node
     *            
     * @param tagName
     *            ????
     * @return ?
     */
    public static Element firstPrevSibling(Node node, String tagName) {
        Node p = node.getPreviousSibling();
        while (p != null) {
            if (p.getNodeType() == Node.ELEMENT_NODE) {
                if (StringUtils.isEmpty(tagName) || p.getNodeName().equals(tagName))
                    return (Element) p;
            }
            p = p.getPreviousSibling();
        }
        return null;
    }

    /**
     * xml
     * <p/>
     * XMLParser???
     * ???
     * ???ReadernioStreamReader?read(char[] b, int
     * off, int len). ??XmlFixedReader???
     * <ol>
     * <li>0x00 - 0x08</li>
     * <li>0x0b - 0x0c</li>
     * <li>0x0e - 0x1f</li>
     * </ol>
     */
    public static class XmlFixedReader extends FilterReader {
        public XmlFixedReader(Reader reader) {
            super(new BufferedReader(reader));
        }

        public int read() throws IOException {
            int ch = super.read();
            while ((ch >= 0x00 && ch <= 0x08) || (ch >= 0x0b && ch <= 0x0c) || (ch >= 0x0e && ch <= 0x1f)
                    || ch == 0xFEFF) {
                ch = super.read();
            }
            return ch;
        }

        // ???

        public int read(char[] b, int off, int len) throws IOException {
            if (b == null) {
                throw new NullPointerException();
            } else if (off < 0 || len < 0 || len > b.length - off) {
                throw new IndexOutOfBoundsException();
            } else if (len == 0) {
                return 0;
            }
            int c = read();
            if (c == -1) {
                return -1;
            }
            b[off] = (char) c;
            int i = 1;
            try {
                for (; i < len; i++) {
                    c = read();
                    if (c == -1) {
                        break;
                    }
                    b[off + i] = (char) c;
                }
            } catch (IOException ee) {
            }
            return i;
        }
    }

    /**
     * Tagnameelement
     * 
     * @param node
     *            
     * @param tagName
     *            ??
     * @return ??
     */
    public static List<Element> getElementsByTagNames(Node node, String... tagName) {
        List<Element> nds = new ArrayList<Element>();
        if (tagName.length == 0)
            tagName = new String[] { "" };
        if (node.getNodeType() == Node.ELEMENT_NODE) {
            Element doc = (Element) node;
            for (String elementName : tagName) {
                nds.addAll(toElementList(doc.getElementsByTagName(elementName)));
            }
        } else if (node instanceof Document) {
            Document doc = ((Document) node);
            for (String elementName : tagName) {
                nds.addAll(toElementList(doc.getElementsByTagName(elementName)));
            }
        } else if (node instanceof DocumentFragment) {
            Document doc = ((DocumentFragment) node).getOwnerDocument();
            for (String elementName : tagName) {
                nds.addAll(toElementList(doc.getElementsByTagName(elementName)));
            }
        } else {
            throw new IllegalArgumentException("a node who doesn't support getElementsByTagName operation.");
        }
        return nds;
    }

    /**
     * Node??
     * 
     * @param node
     *            DOM
     * @param out
     *            ?
     */
    public static void printNode(Node node, OutputStream out) {
        output(node, out, null, 4, null);
    }

    /**
     * DOM?
     * 
     * @param node
     *            DOM
     * @param charset
     *            ??xmlstring?unicode string
     *            ???.
     * @param xmlHeader
     *            ???XML<?xml ....>
     * @return ??XML
     */
    public static String toString(Node node, String charset, Boolean xmlHeader) {
        if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
            return node.getNodeValue();
        }
        StringWriter sw = new StringWriter(4096);
        StreamResult sr = new StreamResult(sw);
        try {
            output(node, sr, charset, 4, xmlHeader);
        } catch (IOException e) {
            LogUtil.exception(e);
        }
        return sw.toString();
    }

    /**
     * DOMXML
     * 
     * @param node
     *            DOM
     * @param charset
     *            ??xmlstring?unicode string
     *            ???.
     * @return ??XML
     */
    public static String toString(Node node, String charset) {
        return toString(node, charset, null);
    }

    /**
     * XSD Schema
     * 
     * @param node
     *            DOM
     * @param schemaURL
     *            XSDURL
     */
    public static void setXsdSchema(Node node, String schemaURL) {
        Document doc;
        if (node.getNodeType() != Node.DOCUMENT_NODE) {
            doc = node.getOwnerDocument();
        } else {
            doc = (Document) node;
        }
        Element root = doc.getDocumentElement();
        if (schemaURL == null) {
            root.removeAttribute("xmlns:xsi");
            root.removeAttribute("xsi:noNamespaceSchemaLocation");
        } else {
            root.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
            root.setAttribute("xsi:noNamespaceSchemaLocation", schemaURL);
        }
    }

    private static void innerSearch(Node node, String text, List<Node> result, boolean searchAttribute) {
        String value = getValue(node);
        // 
        if (value != null && value.indexOf(text) > -1)
            result.add(node);
        // 
        if (searchAttribute && node.getAttributes() != null) {
            for (Node n : toArray(node.getAttributes())) {
                value = getValue(n);
                if (value != null && value.indexOf(text) > -1) {
                    result.add(n);
                }
            }
        }
        // 
        for (Node sub : toArray(node.getChildNodes())) {
            innerSearch(sub, text, result, searchAttribute);
        }
    }

    /*
     * 
     */
    private static String getValue(Node node) {
        switch (node.getNodeType()) {
        case Node.ELEMENT_NODE:
            return nodeText((Element) node);
        case Node.TEXT_NODE:
            return StringUtils.trimToNull(StringEscapeUtils.unescapeHtml(node.getTextContent()));
        case Node.CDATA_SECTION_NODE:
            return ((CDATASection) node).getTextContent();
        default:
            return StringEscapeUtils.unescapeHtml(node.getNodeValue());
        }
    }

    private static void innerSearchByAttribute(Node node, String attribName, String id, List<Element> result,
            boolean findFirst) {
        if (node.getNodeType() == Node.ELEMENT_NODE) {
            Element e = (Element) node;
            String s = attrib(e, attribName);
            if (s != null && s.equals(id)) {
                result.add(e);
                if (findFirst)
                    return;
            }
        }

        for (Node sub : toArray(node.getChildNodes())) {
            innerSearchByAttribute(sub, attribName, id, result, findFirst);
            if (findFirst && result.size() > 0)
                return;
        }
    }

    private static Element[] findElementsByNameAndAttribute(Node root, String tagName, String attribName,
            String keyword, boolean findFirst) {
        List<Element> result = new ArrayList<Element>();
        List<Element> es;
        if (root instanceof Document) {
            es = toElementList(((Document) root).getElementsByTagName(tagName));
        } else if (root instanceof Element) {
            es = toElementList(((Element) root).getElementsByTagName(tagName));
        } else if (root instanceof DocumentFragment) {
            Element eRoot = (Element) first(root, Node.ELEMENT_NODE);
            es = toElementList(eRoot.getElementsByTagName(tagName));
            if (eRoot.getNodeName().equals(tagName))
                es.add(eRoot);
        } else {
            throw new UnsupportedOperationException(root + " is a unknow Node type to find");
        }
        for (Element e : es) {
            String s = attrib(e, attribName);
            if (s != null && s.equals(keyword)) {
                result.add(e);
                if (findFirst)
                    break;
            }
        }
        return result.toArray(new Element[result.size()]);
    }

    private static Element[] findElementsByAttribute(Node node, String attribName, String keyword,
            boolean findFirst) {
        List<Element> result = new ArrayList<Element>();
        innerSearchByAttribute(node, attribName, keyword, result, findFirst);
        return result.toArray(new Element[0]);
    }

    private static Element appendBean(Node parent, Object bean, Class<?> type, Boolean asAttrib, String tagName) {
        if (type == null) {
            if (bean == null) {
                return null;
            }
            type = bean.getClass();
        }
        if (tagName == null || tagName.length() == 0) {
            tagName = type.getSimpleName();
        }
        if (type.isArray()) {
            if (bean == null)
                return null;
            Element collection = addElement(parent, tagName);
            for (int i = 0; i < Array.getLength(bean); i++) {
                appendBean(collection, Array.get(bean, i), null, asAttrib, null);
            }
            return collection;
        } else if (Collection.class.isAssignableFrom(type)) {
            if (bean == null)
                return null;
            Element collection = addElement(parent, tagName);
            for (Object obj : (Collection<?>) bean) {
                appendBean(collection, obj, null, asAttrib, null);
            }
            return collection;
        } else if (Map.class.isAssignableFrom(type)) {
            Element map = addElement(parent, tagName);
            for (Entry<?, ?> e : ((Map<?, ?>) bean).entrySet()) {
                Element entry = XMLUtils.addElement(map, "entry");
                Element key = XMLUtils.addElement(entry, "key");
                appendBean(key, e.getKey(), null, asAttrib, null);
                Element value = XMLUtils.addElement(entry, "value");
                appendBean(value, e.getValue(), null, asAttrib, null);
            }
            return map;
        } else if (CharSequence.class.isAssignableFrom(type)) {
            if (Boolean.TRUE.equals(asAttrib)) {
                ((Element) parent).setAttribute(tagName, StringUtils.toString(bean));
            } else {
                addElement(parent, tagName, StringUtils.toString(bean));
            }
        } else if (Date.class.isAssignableFrom(type)) {
            if (Boolean.FALSE.equals(asAttrib)) {
                addElement(parent, tagName, DateUtils.formatDateTime((Date) bean));
            } else {
                ((Element) parent).setAttribute(tagName, DateUtils.formatDateTime((Date) bean));
            }
        } else if (Number.class.isAssignableFrom(type) || type.isPrimitive() || type == Boolean.class) {
            if (Boolean.FALSE.equals(asAttrib)) {
                addElement(parent, tagName, StringUtils.toString(bean));
            } else {
                ((Element) parent).setAttribute(tagName, StringUtils.toString(bean));
            }
        } else {
            if (bean == null)
                return null;
            Element root = addElement(parent, type.getSimpleName());
            BeanWrapper bw = BeanWrapper.wrap(bean);
            for (Property p : bw.getProperties()) {
                appendBean(root, p.get(bean), p.getType(), asAttrib, p.getName());
            }
            return root;
        }
        return null;
    }
}