dom4j基本使用与XPath不生效处理

文章目录

  • dom4j的XPath不生效问题
    • 使用XPath表达式前缀
    • 去除xml字符串中的namespace
    • 通过VisitorSupport去除Namespace节点
  • XPath表达式
    • 基本表达式
    • 位置表达式
    • 关系表达式
    • Element常用方法与说明

dom4j的XPath不生效问题

dom4j使用XPath的时候,发现不能获取到节点

最可能是命名空间的问题,因为如果节点上有Namespace,那么XPath中就应该使用namespace的前缀。

例如:

<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">
  <process isExecutable="true">process>
definitions>
@Test
public void base() throws DocumentException {
    SAXReader reader = new SAXReader();
    Document document = reader.read(new File("F:\\tmp\\camunda.xml"));
    Node versionNode = document.selectSingleNode("/definitions/process");
    System.out.println(versionNode);
}

上面的代码读到的就是空节点

怎么处理呢?3中方式

  1. XPath表达式中加上对应的前缀
  2. 去除xml字符串中的namespace
  3. 通过dom4j的VisitorSupport去除Namespace节点

使用XPath表达式前缀

@Test
public void prefixNamespace() throws DocumentException {
    SAXReader reader = new SAXReader();
    Map<String, String> map = new HashMap<>();
    reader.getDocumentFactory().setXPathNamespaceURIs(map);
    File file = new File("F:\\tmp\\camunda.xml");
    Document document = reader.read(file);
    String namespaceURI = document.getRootElement().getNamespaceURI();
    map.put("ns", namespaceURI);
    XPath xPath = document.createXPath("/ns:definitions/ns:process");
    System.out.println(xPath.selectSingleNode(document));
}

XPath表达式"/ns:definitions/ns:process"中的ns就是前缀,其中ns是自定义的,可以随便改和map中设置的一致即可。

改方式的问题是:

  1. 不同节点可能有不同的Namespace,就要每个节点都要处理
  2. ns前缀是所有节点都要加,而不是整个表达式前面就可以

去除xml字符串中的namespace

@Test
public void removeStringNamespace() throws DocumentException, IOException {
    SAXReader reader = new SAXReader();
    Path path = Paths.get("F:\\tmp\\camunda.xml");
    String xml = Files.readString(path);
    xml = xml .replaceAll("xmlns=\"[^\"]*\"","");
    System.out.println(xml);
    StringReader stringReader = new StringReader(xml);
    Document document = reader.read(stringReader);
    Node node = document.selectSingleNode("/definitions/process");
    System.out.println(node);
}

这种方式比较简单粗暴,但是去除xml中的命名空间的表达式需要兼容性好。

通过VisitorSupport去除Namespace节点

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Namespace;
import org.dom4j.VisitorSupport;
import org.dom4j.tree.DefaultElement;

/**
 * dom4j xpath 清理namespace
 */
public final class Dom4jNameSpaceCleaner extends VisitorSupport {
    public void visit(Document document) {
        ((DefaultElement) document.getRootElement())
                .setNamespace(Namespace.NO_NAMESPACE);
        document.getRootElement().additionalNamespaces().clear();
    }

    public void visit(Namespace namespace) {
        namespace.detach();
    }

    public void visit(Attribute node) {
        String content = node.toString();
        if (content.contains("xmlns")
                || content.contains("xsi:")) {
            node.detach();
        }
    }

    public void visit(Element node) {
        if (node instanceof DefaultElement) {
            ((DefaultElement) node).setNamespace(Namespace.NO_NAMESPACE);
        }
    }
}
@Test
public void dom4jNameSpaceCleaner() throws DocumentException {
    SAXReader reader = new SAXReader();
    Document document = reader.read(new File("F:\\tmp\\camunda.xml"));
    document.accept(new Dom4jNameSpaceCleaner());
    Node node = document.selectSingleNode("/definitions/process");
    System.out.println(node);
}

XPath表达式

基本表达式

表达式 描述
nodeName 获取当前节点的所有nodeName节点,不包含孙节点
/nodeName 从根节点开始匹配,就是根节点下的nodeName节点
//nodeName 获取所有nodeName节点,不考虑它们的位置
. 获取当前节点
获取当前节点的父节点
@ 获取属性
* 匹配任何节点节点
@* 匹配所有属性
/process/* 获取process所有子节点
//* 获取文档中的所有节点
//userTask[@id] 获取所有id属性的userTask节点
//userTask/incoming 获取userTask节点下的incoming节点
//incoming //outgoing
@Test
public void xpath() throws DocumentException, IOException {
    String xmlFileStr = "F:\\tmp\\camunda.xml";
    Document document = Dom4jXmlHelper.getDocument(xmlFileStr);
    Node process = document.selectSingleNode("/definitions/process");
    // 获取process下的所有userTask节点
    List<Node> nodes = process.selectNodes("userTask");
    for(Node node : nodes){
        System.out.println(node.getName());
    }

    // 获取process下的所有userTask节点下的incoming节点
    nodes = process.selectNodes("userTask/incoming");
    for(Node node : nodes){
        System.out.println(node.getName());
    }

    // 获取process下的所有userTask节点下的camunda:前缀节点,不考虑位置
    nodes = process.selectNodes("userTask//camunda:*");
    System.out.println("camunda:" + nodes.size());
    for(Node node : nodes){
        System.out.println(node.getName());
    }

    // 获取process下的所有id属性
    nodes = process.selectNodes("//@id");
    for(Node node : nodes){
        System.out.println(node.getStringValue());
    }
    
    // 获取process下的userTask节点下所有属性
    nodes = process.selectNodes("userTask//@*");
    for(Node node : nodes){
        System.out.println(node.getStringValue());
    }
}

位置表达式

表达式 说明
process/userTask[1]/@id 获取当前节点下的process节点下的第1个userTask节点的id属性
process/userTask[last()] 获取最后一个userTask节点
process/userTask[last()-1] 获取倒数第2个userTask节点
process/userTask[position()❤️] 获取前2个userTask节点
//userTask[@id] 获取有id属性的 userTask节点
//userTask[@id=‘Activity_0ut6bzp’] 获取属性id为Activity_0ut6bzp的userTask节点
process/userTask[camunda>10] 获取当前节点process节点下的userTask节点下的camunda节点值大于10的节点
process/userTask[camunda>10]/string 上一个的string子节点
@Test
public void xpathPosition() throws DocumentException, IOException {
    String xmlFileStr = "F:\\tmp\\camunda.xml";
    Document document = Dom4jXmlHelper.getDocument(xmlFileStr);
    Element root = document.getRootElement();
    List<Node> nodes = root.selectNodes("process/userTask[1]/@id");
    for (Node node : nodes) {
        System.out.println(node.getStringValue());
    }

    nodes = root.selectNodes("process/userTask[last()]/@id");
    for (Node node : nodes) {
        System.out.println(node.getStringValue());
    }
}

关系表达式

表达式 结果
self 获取当前节点
parent 获取当前节点的父节点
child 获取当前节点的所有子节点
ancestor 获取当前节点的所有先辈
ancestor-or-self 获取当前节点的所有先辈以及当前节点本身
attribute 获取当前节点的所有属性
descendant 获取当前节点的所有后代节点
descendant-or-self 获取当前节点的所有后代节点(子、孙等)以及当前节点本身
following 获取文档中当前节点的结束标签之后的所有节点
namespace 获取当前节点的所有命名空间节点
preceding 获取文档中当前节点的开始标签之前的所有节点
preceding-sibling 获取当前节点之前的所有同级节点
child::userTask 获取所有属于当前节点的子节点的userTask节点
attribute::id 获取当前节点的id属性
attribute: 获取当前节点的所有属性
child: 获取当前节点的所有子节点
child::text() 获取当前节点的所有文本子节点
child::node() 获取当前节点的所有子节点
descendant::userTask 获取当前节点的所有 userTask 后辈
ancestor::userTask 选择当前节点的所有 userTask 先辈
ancestor-or-self::userTask 获取当前节点的所有userTask先辈以及当前节点
child:/child::userTask 获取当前节点的所有 userTask 孙节点
 @Test
public void xpathRelation() throws DocumentException, IOException {
    String xmlFileStr = "F:\\tmp\\camunda.xml";
    Document document = Dom4jXmlHelper.getDocument(xmlFileStr);
    Element root = document.getRootElement();
    List<Node> nodes = root.selectNodes("process/attribute::*");
    for (Node node : nodes) {
        System.out.println(node.getStringValue());
    }
}

Element常用方法与说明

方法 说明
getNamespace() 节点所属的Namespace对象
getNamespacePrefix() 节点所属的Namespace对象的prefix
getNamespaceURI() 节点所属的Namespace对象的URI
getName() 节点的local name
getQualifiedName() 节点的qualified name
getText() 节点所含有的text内容,如果内容为空则返回一个空字符串而不是null
getTextTrim() 节点所含有的text内容,其中连续的空格被转化为单个空格,该方法不会返回null
attributeIterator() 节点属性的iterator,其中每个节点都是Attribute对象
attributeValue() 节点的某个指定属性所含的值
elementIterator() 节点的子节点的iterator,其中每个节点都是Element对象
element(name) 获取指定名称第1个子节点节点
elements(name) 获取指定名称所有子节点节点
elementText() 节点的的text
getParent 获取父节点
getPath() 节点的XPath表达式
isTextOnly() 是否该节点只含有text或是空节点
isRootElement() 是否该节点是XML树的根节点

使用非XPath方式,就得手动通过API去一层一层的查找。

 @Test
public void base() throws DocumentException {
    SAXReader reader = new SAXReader();
    Document document = reader.read(new File("F:\\tmp\\camunda.xml"));
    Element rootElement = document.getRootElement();
    Element process = rootElement.element("process");
    List<Element> elementList = process.elements("userTask");
    System.out.println(elementList.size());
    for(Element element : elementList){
        System.out.println(element.attribute("id").getValue());
    }
}

你可能感兴趣的:(java,xml,dom4j,xpath)