package com.lizard.fastdb.util; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Serializable; import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.XMLConstants; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.Node; import org.dom4j.io.SAXReader; import org.dom4j.io.XMLWriter; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * XML操作工具类,包含各种对XML文件和Document对象的操作方法 * * @author SHEN.GANG */ @SuppressWarnings("rawtypes") public class XMLUtils implements Serializable { private static final Log log = LogFactory.getLog(XMLUtils.class); private static final long serialVersionUID = -3665640969475129453L; // 命名空间 public static final String FASTDB_NAMESPACE = "FASTDB_ns"; // 为xpath增加命名空间的正则表达式 public static final String XPATH_NAMESPACE_REGEX = "(?:(^|/)([\\w-])|(\\[)([\\w-]+='[^']*']))(?=(?:/|[\\w-]|\\[@?[\\w-]+='[^']*'])*)"; // 增加的命名空间 public static final String XPATH_REPLACE_NAMESPACE = "$1$3" + FASTDB_NAMESPACE + ":$2$4"; /** * 元素插入位置:插在前面,值为 insertBefore */ public static final String INSERTBEFORE = "insertBefore"; /** * 元素插入位置:插在最后,值为 insertAfter */ public static final String INSERTAFTER = "insertAfter"; /** * 元素插入位置:插在元素内部最前 */ public static final String INSERT = "insert"; /** * 元素插入位置,插在元素内部最后 */ public static final String APPEND = "append"; /** * 处理XML头部采用DTD验证, 以下设置忽略验证,否则无法取到节点信息 * * @param reader * document解析器 */ private static void setReaderValidation(SAXReader reader) { // 处理XML头部采用DTD验证, 以下设置忽略验证,否则无法取到节点信息 reader.setValidation(false); reader.setEntityResolver(new EntityResolver() { public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { return new InputSource(new StringReader("")); } }); } /** * 设置DocumentFactory的命名空间 * * @param reader * document解析器 * @param namespace * 命名空间 */ private static void setReadernameSpace(SAXReader reader, String namespace) { Map<String, String> nsMap = new HashMap<String, String>(); nsMap.put(FASTDB_NAMESPACE, namespace); reader.getDocumentFactory().setXPathNamespaceURIs(nsMap); // 不需要重新读取一次doc // document = saxReader.read(xmlFile); } /** * 根据输入流InputStream获取XML文件Document对象 * <br> * 注意:XMLUtils不会关闭 InputStream,请自己手工关闭,避免输入流泄流。 * * @param inputStream xml文档输入流 * @return XML Document对象 */ public static Document getDocumentFromStream( InputStream inputStream ) { InputStreamReader isr = null; BufferedReader br = null; SAXReader saxReader = new SAXReader(); Document document = null; try { isr = new InputStreamReader(inputStream, "UTF-8"); br = new BufferedReader(isr, 1024); setReaderValidation(saxReader); document = saxReader.read(br); String namespace_uri = document.getRootElement().getNamespaceURI(); if (!StringUtils.isEmptyString(namespace_uri)) { setReadernameSpace(saxReader, namespace_uri); } } catch (DocumentException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } finally { if (br != null) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } br = null; } if (isr != null) { try { isr.close(); } catch (IOException e) { e.printStackTrace(); } isr = null; } } return document; } /** * 从jar包中加载xml文件为document对象<br> * 该方法优先从classpath进行加载,如果没有找到,再通过classloader从jar包中进行加载 * * @param loader * 指定jar包中类的类加载器 * @param path * xml文件在jar包中的相对位置 * @return Document对象 */ public static Document getDocumentFromJar(ClassLoader loader, String path) { Document document = getDocumentFromClasspath(path); if (document == null && loader != null) { SAXReader saxReader = new SAXReader(); InputStream is = loader.getResourceAsStream(path); try { setReaderValidation(saxReader); document = saxReader.read(is); String namespace_uri = document.getRootElement().getNamespaceURI(); if (!StringUtils.isEmptyString(namespace_uri)) { setReadernameSpace(saxReader, namespace_uri); } } catch (DocumentException e) { e.printStackTrace(); } } return document; } /** * 根据xml文件对象获得其Document对象 * * @param xmlFile * 待解析的XML文件对象 * @return document对象 * @throws IOException */ public static Document getDocumentFromFile(File xmlFile) throws IOException { if (!xmlFile.exists()) { throw new FileNotFoundException("file " + xmlFile + " does not exist!"); } else if (xmlFile.isDirectory()) { throw new IOException("file " + xmlFile + " exists but is a directory!"); } else if (xmlFile.canRead() == false) { throw new IOException("file " + xmlFile + " cannot be read!"); } Document document = null; SAXReader saxReader = new SAXReader(); try { setReaderValidation(saxReader); document = saxReader.read(xmlFile); String namespace_uri = document.getRootElement().getNamespaceURI(); if (!StringUtils.isEmptyString(namespace_uri)) { setReadernameSpace(saxReader, namespace_uri); } } catch (DocumentException e) { e.printStackTrace(); } return document; } /** * 根据指定<b>相对于项目根目录</b>的XML文件路径获得该文件的document对象 * * @param classpathFile * XML文件相对于项目根目录的路径,路径前面不能有/ * @return XML文件的document对象 * @throws IOException */ public static Document getDocumentFromClasspath(String classpathFile) { /*InputStream is = null; InputStreamReader isr = null; BufferedReader br = null; SAXReader saxReader = new SAXReader(); Document document = null; try { is = XMLUtils.class.getClassLoader().getResourceAsStream(classpathFile); isr = new InputStreamReader(is, "UTF-8"); br = new BufferedReader(isr, 1024); setReaderValidation(saxReader); document = saxReader.read(br); String namespace_uri = document.getRootElement().getNamespaceURI(); if (!StringUtils.isEmptyString(namespace_uri)) { setReadernameSpace(saxReader, namespace_uri); } } catch (DocumentException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } finally { if (br != null) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } br = null; } if (isr != null) { try { isr.close(); } catch (IOException e) { e.printStackTrace(); } isr = null; } if (is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } is = null; } }*/ Document document = null; InputStream is = XMLUtils.class.getClassLoader().getResourceAsStream(classpathFile); if( null != is ) { document = getDocumentFromStream(is); try { is.close(); is = null; } catch (IOException e) { e.printStackTrace(); } } return document; } /** * 根据指定的XML文件绝对路径获得该文件的document对象 * * @param filepath * 文件绝对路径 * @return XML文件的document对象 * @throws IOException */ public static Document getDocumentFromFilepath(String filepath) throws IOException { File file = new File(filepath); Document document = getDocumentFromFile(file); return document; } /** * 将指定的xml格式字符串转换为Document对象,使用UTF-8编码 * * @param xmlContent * 待解析的XML字符串 * @return document对象 */ public static Document getDocumentFromString(String xmlContent) { SAXReader saxReader = new SAXReader(); Document document = null; try { setReaderValidation(saxReader); document = saxReader.read(new ByteArrayInputStream(xmlContent.getBytes("UTF-8"))); String namespace_uri = document.getRootElement().getNamespaceURI(); if (!StringUtils.isEmptyString(namespace_uri)) { setReadernameSpace(saxReader, namespace_uri); } } catch (DocumentException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return document; } /** * 根据xpath路径获得指定的节点信息列表 <br> * 列表中每一个Map代表一个节点,key为节点的属性名称,value为对应属性的值 <br> * <br> * <b>key统一为小写</b><br> * <br> * key为<b>node_name</b>表示该节点的名称<br> * key为<b>node_value</b>表示该节点的值 * * @param document * 待解析的文档对象 * @param xpath * 节点路径 * @return 节点列表 */ public static List<Map<String, String>> getNodeInfoList(Document document, String xpath) { List<Map<String, String>> result = null; // 进行命名空间处理 if (!StringUtils.isEmptyString(document.getRootElement().getNamespaceURI())) { // 将xpath中增加命名空间 xpath = xpath.replaceAll(XPATH_NAMESPACE_REGEX, XPATH_REPLACE_NAMESPACE); } List list = document.selectNodes(xpath); if (list != null && list.size() > 0) { result = new ArrayList<Map<String, String>>(); for (Object obj : list) { Element element = (Element) obj; Map<String, String> map = new HashMap<String, String>(); map.put("node_name", element.getName()); // 节点名称 map.put("node_value", element.getText()); // 节点值 List attrList = element.attributes(); if (attrList != null && attrList.size() > 0) { for (Object o : attrList) { Node attr = (Node) o; map.put(attr.getName().toLowerCase(), attr.getText()); } } result.add(map); } } return result; } /** * 根据xpath获得指定document对象节点的属性的值 * * @param document * 待解析的document对象 * @param xpath * 节点XPath * @return 指定节点属性的值列表 */ public static List<String> getNodeAttributeValue(Document document, String xpath) { List<String> result = null; // 进行命名空间处理 if (!StringUtils.isEmptyString(document.getRootElement().getNamespaceURI())) { // 将xpath中增加命名空间 xpath = xpath.replaceAll(XPATH_NAMESPACE_REGEX, XPATH_REPLACE_NAMESPACE); } List nodeList = document.selectNodes(xpath); if (nodeList != null && nodeList.size() > 0) { result = new ArrayList<String>(); int begin = xpath.lastIndexOf("@"); int end = xpath.indexOf("]", begin + 1); if (begin == -1 || end == -1) { throw new IllegalArgumentException("no attribute has be assigned in xpath!"); } String attrName = xpath.substring(begin, end); for (Object obj : nodeList) { Node attr = (Node) obj; result.add(attr.valueOf(attrName)); } } return result; } /** * 根据xpath获得指定document对象节点属性的值,如果xpath有多个匹配结果,则只返回第一个值 * * @param document * 待解析的document对象 * @param xpath * 节点XPath * @return 指定节点属性的值 */ public static String getSingleNodeAttributeValue(Document document, String xpath) { List<String> result = getNodeAttributeValue(document, xpath); if (result != null && result.size() > 0) { return result.get(0); } else { return null; } } /** * 获得指定节点的值列表 * * @param document * 待解析的document对象 * @param xpath * 节点XPath * @return 节点值列表 */ public static List<String> getNodeValue(Document document, String xpath) { List<String> result = null; // 进行命名空间处理 if (!StringUtils.isEmptyString(document.getRootElement().getNamespaceURI())) { // 将xpath中增加命名空间 xpath = xpath.replaceAll(XPATH_NAMESPACE_REGEX, XPATH_REPLACE_NAMESPACE); } List nodeList = document.selectNodes(xpath); if (nodeList != null && nodeList.size() > 0) { result = new ArrayList<String>(); for (Object obj : nodeList) { Node node = (Node) obj; result.add(node.getText()); } } return result; } /** * 获得单一节点的值,如果xpath有多个匹配结果,则只返回第一个值 * * @param document * 待解析的document * @param xpath * 节点XPath * @return 节点值 */ public static String getSingleNodeValue(Document document, String xpath) { List<String> result = getNodeValue(document, xpath); if (result != null && result.size() > 0) { return result.get(0); } else { return null; } } public static String setXPathNamespace( Element ele, String xpath ) { // 进行命名空间处理 if (!StringUtils.isEmptyString(ele.getNamespaceURI())) { // 将xpath中增加命名空间 xpath = xpath.replaceAll(XPATH_NAMESPACE_REGEX, XPATH_REPLACE_NAMESPACE); } return xpath; } /** * 保存document对象为文件,默认UTF-8编码 * * @param document * 待保存的document对象 * @param filepath * 待保存的绝对路径 * @param fileEncoding * 文件编码 * @return 成功 == true,失败 == false */ public static boolean saveFile(Document document, String filepath, String fileEncoding) { boolean result = false; FileOutputStream outStream = null; OutputStreamWriter outWriter = null; XMLWriter writer = null; if (fileEncoding == null || "".equals(fileEncoding.trim())) { fileEncoding = "UTF-8"; } try { outStream = new FileOutputStream(filepath); outWriter = new OutputStreamWriter(outStream, fileEncoding); writer = new XMLWriter(outWriter); writer.write(document); result = true; } catch (FileNotFoundException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (writer != null) { writer.close(); } if (outWriter != null) { outWriter.close(); } if (outStream != null) { outStream.close(); } } catch (IOException e) { e.printStackTrace(); } } return result; } /** * 保存document对象为XML文件,默认UTF-8编码。 * * @param document * document对象 * @param filepath * 保存路径 * @return 成功 == true,失败 == false */ public static boolean saveFile(Document document, String filepath) { boolean result = false; result = saveFile(document, filepath, "UTF-8"); return result; } /** * 将xml字符串内容保存为xml文件 * * @param xmlContent * XML字符串 * @param filepath * 文件路径 * @return 成功 == true,失败 == false */ public static boolean saveFile(String xmlContent, String filepath) { boolean result = false; Document document = getDocumentFromString(xmlContent); result = saveFile(document, filepath, "UTF-8"); return result; } /** * 将XML字符串内容保存为指定编码的XML文件 * * @param xmlContent * XML字符串 * @param filepath * 文件路径 * @param fileEncoding * 文件编码 * @return 成功 == true,失败 == false */ public static boolean saveFile(String xmlContent, String filepath, String fileEncoding) { boolean result = false; Document document = getDocumentFromString(xmlContent); result = saveFile(document, filepath, fileEncoding); return result; } /** * 根据 schema 语法定义文件验证 xml 文件内容是否规范 * * @param schemaFile * schema 文件 * @param xmlFile * xml 文件 * @return 验证成功 == true,验证失败 == false */ public static boolean schemaValidate(String schemaFile, String xmlFile) { boolean result = false; Document document = getDocumentFromClasspath(xmlFile); result = schemaValidateXMLContent(schemaFile, document.asXML()); return result; } /** * 根据 schema 语法定义文件验证 xml 文件内容是否规范 * * @param schemaFile * schema 文件 * @param xmlContent * xml 文件内容 * @return 验证成功 == true,验证失败 == false */ public static boolean schemaValidateXMLContent(String schemaFile, String xmlContent) { boolean result = false; // 获取Schema工厂类, // 这里的XMLConstants.W3C_XML_SCHEMA_NS_URI的值就是:http://www.w3.org/2001/XMLSchema SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); try { // 读取 schema 定义文件 String fileName = XMLUtils.class.getClassLoader().getResource(schemaFile).toString(); fileName = fileName.substring(fileName.indexOf('/')); File schemaSource = new File(fileName); // 创建 schema Schema schema = factory.newSchema(schemaSource); // 从 schema 中获取 validator Validator validator = schema.newValidator(); // 解析要检测的 xml 文件内容 ByteArrayInputStream bis = new ByteArrayInputStream(xmlContent.getBytes("UTF-8")); Source source = new StreamSource(bis); // 验证 xml 文件内容格式是否符合schema语法定义规范 validator.validate(source); result = true; } catch (SAXException e) { log.error("Validate dconfigure file error:", e); } catch (IOException e) { log.error("An error has happend when validate file:", e); } return result; } /** * 在指定的父元素的指定位置插入xml字符串 * * @param filepath * xml文件路径 * @param xpath * 父元素的xpath * @param xml * 待插入的xml字符串 * @param position * 插入位置,最前 <code>XMLUtils.INSERTBEFORE</code> 或 最后 <code>XMLUtils.INSERTAFTER</code> * @param isSave * 是否保存更改后的文件 * @return 插入成功后的xml文件内容字符串 */ public static String insert(String filepath, String xpath, String xml, boolean isSave) { String result = insert(filepath, new String[]{ xpath }, new String[]{ xml }, isSave); return result; } public static String insert(String filepath, String[] xpaths, String[] xmls, boolean isSave) { String result = null; try { Document document = getDocumentFromFilepath(filepath); if (xpaths != null && xpaths.length > 0) { for (int i = 0; i < xpaths.length; i++) { insert(document, xpaths[i], xmls[i]); } } if (isSave) { FileUtils.forceDelete(new File(filepath)); saveFile(document, filepath); } result = document.asXML(); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return result; } /** * 向指定xml文档对象指定元素内插入xml * * @param document * xml文档对象 * @param xpath * 元素xpath * @param xml * 插入内容 * @return xml文档对象最终内容字符串 */ @SuppressWarnings("unchecked") public static String insert(Document document, String xpath, String xml) { String namespace = document.getRootElement().getNamespaceURI(); if (!StringUtils.isEmptyString(xpath) && !StringUtils.isEmptyString(xml)) { ((Element) document.selectSingleNode(xpath)).elements().addAll(0, getElementListFromXMl(xml, namespace)); } else { document.getRootElement().elements().addAll(0, getElementListFromXMl(xml, namespace)); } return document.asXML(); } /** * 在xml文件根元素最后插入xml字符串 * * @param filepath * xml文件路径 * @param xml * 待插入的xml字符串 * @param isSave * 是否保存更改后的文件 * @return 插入成功后的xml文件内容字符串 */ public static String append(String filepath, String xpath, String xml, boolean isSave) { String result = append(filepath, new String[]{ xpath }, new String[]{ xml }, isSave); return result; } public static String append(String filepath, String[] xpaths, String[] xmls, boolean isSave) { String result = null; try { Document document = getDocumentFromFilepath(filepath); if (xpaths != null && xpaths.length > 0) { for (int i = 0; i < xpaths.length; i++) { append(document, xpaths[i], xmls[i]); } } if (isSave) { FileUtils.forceDelete(new File(filepath)); saveFile(document, filepath); } result = document.asXML(); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return result; } @SuppressWarnings("unchecked") public static String append(Document document, String xpath, String xml) { String namespace = document.getRootElement().getNamespaceURI(); if (!StringUtils.isEmptyString(xpath) && !StringUtils.isEmptyString(xml)) { ((Element) document.selectSingleNode(xpath)).elements().addAll(getElementListFromXMl(xml, namespace)); } else { document.getRootElement().elements().addAll(getElementListFromXMl(xml, namespace)); } return document.asXML(); } /** * 在指定的元素前面插入xml字符串 * * @param filepath * xml文件路径 * @param xpath * 指定元素的xpath,xpath为空表示在根元素最前面插入 * @param xml * 待插入的xml字符串 * @param isSave * 是否保存更改后的文件 * @return 插入成功后的xml文件内容字符串 */ public static String insertBefore(String filepath, String xpath, String xml, boolean isSave) { String result = insertBefore(filepath, new String[]{ xpath }, new String[]{ xml }, isSave); return result; } /** * 在指定的元素前面插入xml字符串 * * @param filepath * xml文件路径 * @param xpath * 指定元素的xpath,xpath为空表示在根元素最前面插入 * @param xml * 待插入的xml字符串 * @param isSave * 是否保存更改后的文件 * @return 插入成功后的xml文件内容字符串 */ public static String insertBefore(String filepath, String[] xpaths, String[] xmls, boolean isSave) { String result = null; try { Document document = getDocumentFromFilepath(filepath); if (xpaths != null && xpaths.length > 0) { for (int i = 0; i < xpaths.length; i++) { insertBefore(document, xpaths[i], xmls[i]); } } if (isSave) { FileUtils.forceDelete(new File(filepath)); saveFile(document, filepath); } result = document.asXML(); } catch (Exception e) { e.printStackTrace(); } return result; } /** * 向xml文档对象指定位置前面插入xml * * @param document * xml文档对象 * @param xpath * 位置xpath * @param xml * 插入内容 * @return xml文档最终内容字符串 */ @SuppressWarnings("unchecked") public static String insertBefore(Document document, String xpath, String xml) { String namespace = document.getRootElement().getNamespaceURI(); if (!StringUtils.isEmptyString(xpath) && !StringUtils.isEmptyString(xml)) { List<Element> list = ((Element) document.selectSingleNode(xpath)).getParent().elements(); for (int j = 0; j < list.size(); j++) { if (list.get(j).matches(xpath)) { list.addAll(j, getElementListFromXMl(xml, namespace)); break; } } } else { document.getRootElement().elements().addAll(0, getElementListFromXMl(xml, namespace)); } return document.asXML(); } /** * 在指定的元素后面插入xml字符串 * * @param filepath * xml文件路径 * @param xpath * 指定元素的xpath,为空表示在文档根元素最后插入 * @param xml * 待插入的xml字符串 * @param isSave * 修改后是否保存文件 * @return 插入成功后的xml文件内容字符串 */ public static String insertAfter(String filepath, String xpath, String xml, boolean isSave) { String result = insertAfter(filepath, new String[]{ xpath }, new String[]{ xml }, isSave); return result; } /** * 在指定的元素后面插入xml字符串 * * @param filepath * xml文件路径 * @param xpaths * 指定元素的xpath,为空表示在文档根元素最后插入 * @param xmls * 待插入的xml字符串 * @param isSave * 是否保存更改后的文件 * @return 插入成功后的xml文件内容字符串 */ public static String insertAfter(String filepath, String[] xpaths, String[] xmls, boolean isSave) { String result = null; try { Document document = getDocumentFromFilepath(filepath); if (xpaths != null && xpaths.length > 0) { for (int i = 0; i < xpaths.length; i++) { insertAfter(document, xpaths[i], xmls[i]); } } if (isSave) { FileUtils.forceDelete(new File(filepath)); saveFile(document, filepath); } result = document.asXML(); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return result; } /** * 向指定的xml文档对象中指定位置后面插入xml * * @param document * xml文档对象 * @param xpath * 插入位置xpath * @param xml * 插入内容xml * @return xml文档最终内容字符串 */ @SuppressWarnings("unchecked") public static String insertAfter(Document document, String xpath, String xml) { String namespace = document.getRootElement().getNamespaceURI(); if (!StringUtils.isEmptyString(xpath) && !StringUtils.isEmptyString(xml)) { List<Element> list = ((Element) document.selectSingleNode(xpath)).getParent().elements(); for (int j = 0; j < list.size(); j++) { if (list.get(j).matches(xpath)) { if (j < list.size() - 1) { list.addAll(j + 1, getElementListFromXMl(xml, namespace)); } else { list.addAll(getElementListFromXMl(xml, namespace)); } break; } } } else { document.getRootElement().elements().addAll(getElementListFromXMl(xml, namespace)); } return document.asXML(); } /** * 根据xml字符串获得Element元素列表 * * @param xml * xml字符串 * @param namespace * 命名空间 * @return Element列表 */ @SuppressWarnings("unchecked") private static List<Element> getElementListFromXMl(String xml, String namespace) { String root = RandomUtils.randomString(5); xml = "<_" + root + (StringUtils.isEmptyString(namespace) ? "" : " xmlns=\"" + namespace + "\"") + ">" + xml + "</_" + root + ">"; List<Element> result = new ArrayList<Element>(); List<Element> elementList = getDocumentFromString(xml).getRootElement().elements(); for (int i = 0; i < elementList.size(); i++) { result.add(elementList.get(i).createCopy()); } return result; } }