使用dom4j解析xml文件及selectNodes取不到值问题解决

一、dom4j解析xml文件基本方法

1. 引入相关依赖

<!-- dom4j -->
<dependency>
    <groupId>org.dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>2.1.3</version>
</dependency>
<!-- 使用xpath相关需引入 -->
<dependency>
    <groupId>jaxen</groupId>
    <artifactId>jaxen</artifactId>
    <version>1.1.6</version>
</dependency>

2. dom4j常用解析

2.1 常用API说明

方法说明
Element getRootElement();获取XML文件的根节点
String getName();返回标签的名称
List < Element > elements();获取标签所有的子标签
String arrtributeVallue(String name) ;获取指定属性名称的属性值
String getText();获取标签的文本
String elementText(String name);获取指定名称的子标签的文本,返回子标签文本的值

2.2 代码示例

2.2.1 xml文件示例

<?xml version="1.0" encoding="UTF-8" ?>
<!--文档声明
    XML的文档声明是可选的,也就是可以不写,但是日常生活开发中大家都会写
    XML文档声明如果写了,它必须放在XML文档的第一行第一列,必须以<?xml开头 以?>结尾,而且必须包含两个属性
    一个是version,表示XML的版本
    一个是encoding,表示XML的编码
-->
<!--
    元素是XML的重要组成部分,元素也被称为标签
    每个XML文件必须要有一个根标签
    标签有开始标签和结束标签组成,开始标签和结束标签可以写标签,也可以是文本字符串
    标签可以嵌套使用,但是不能随便嵌套
    标签名必须遵守命名规则和命名规范
-->
<!--
    属性是标签的组成部分,属性只能定义在开始标签中,不能定义在结束标签中
    属性定义的格式:属性名=属性值,属性值需要使用""包含起来
    开始标签中可以定义多个属性,但是多个属性的属性名不能相同
    属性名必须遵守命名规则和命名规范
-->
<users>
    <user id="10001" country="Chinese" source="Android">
        <id>10001</id>
        <name>admin</name>
        <password>111111</password>
    </user>

    <user id="10002" country="Chinese" source="ios">
        <id>10002</id>
        <name>tony</name>
        <password>666666</password>
    </user>

</users>

2.2.2 代码示例

public class Dom4jParseUserXmlTest {
    public static void main(String[] args) {
        File xmlFile = new File("D:\\001.xml");
        //创建解析器对象
        SAXReader saxReader=new SAXReader();
        try {
            Document document = saxReader.read(xmlFile);
            Element rootElement = document.getRootElement();
            System.out.println("1.------->users.xml文件的根节点的名字是:"+rootElement.getName());

            System.out.println("2.------->获取根标签users的子标签列表");
            List<Element> usersSubElementList = rootElement.elements();
            for (Element userElement : usersSubElementList) {
                System.out.println("users标签的子标签的名字是"+ userElement.getName());
                System.out.println("users标签的子标签的id属性值是"+ userElement.attributeValue("id"));
                System.out.println("users标签的子标签的country属性值是"+ userElement.attributeValue("country"));
                System.out.println("3.------->获取user的子标签列表");
                List<Element> userSubElementList = userElement.elements();
                for (Element userSubElement : userSubElementList) {
                    System.out.println("user标签下的子标签名为:"+userSubElement.getName());
                    System.out.println("user标签下的子标签文本是:"+userSubElement.getText());
                }
            }
            //获取users标签的第一个user标签
            Element firstUserElement = rootElement.element("user");
            //第一个user标签的子标签password的文本内容
            String password = firstUserElement.attributeValue("password");
            System.out.println(password);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }
}

3. dom4j结合XPath解析

3.1 常用API说明

方法说明
Node selectSingleNode(String xpathExpression);根据XPath表达式获取单个标签(元素/节点)
List < Node > selectNodes(String xpathExpression);根据XPath表达式获取多个标签(元素/节点)

3.2 XPath语法

绝对路径表示方式:
以/开头的路径表示绝对路径,绝对路径是从根元素开始写。例如:/元素/子元素/子子元素……;
相对路径的表达方式:
相对于当前节点的元素继续查找的节点,不以/开头,…/表示上一个元素,./表示当前元素;
全文搜索路径方式:
例如://子元素,//子元素//子子元素,//子元素/子子元素。
注意:
//表示无论中间有多少层,直接获取所有子元素所有满足条件的元素
/表示只找一层
谓语(条件筛选形式)
例如: //元素[@attr1=value]

3.3 示例代码

public class Dom4jXPathParseUserXmlTest {
    public static void main(String[] args) {
        File xmlFile = new File("D:\\001.xml");
        SAXReader saxReader=new SAXReader();
        try {
            Document document= saxReader.read(xmlFile);
            //拿到第一个用户的密码
            System.out.println("1.------>使用绝对路径方式来查找元素");
            Element element = (Element) document.selectSingleNode("/users/user/password");
            String password = element.getText();
            System.out.println(password);

            System.out.println("2.------>使用相对路径查找元素");
            //element是当前获取的password元素
            Element name = (Element) element.selectSingleNode("../name");
            System.out.println("第一个用户的姓名为"+name.getText());

            System.out.println("3.------>使用全局搜索的方式");
            //获取所有的id元素的文本
            List<Node> idNodeList = document.selectNodes("//id");
            for (Node node : idNodeList) {
                Element idElement=(Element) node;
                System.out.println(idElement.getText());
             }
            System.out.println("4.------>谓语形式");
            //获取id=10002的用户信息
            Element idElement = (Element) document.selectSingleNode("//user[@id='10002']");
            List<Element> elements = idElement.elements();
            for (Element element1 : elements) {
                System.out.println(element1.getName()+"="+element1.getText());
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }
}

二、selectNodes取不到值问题解决

1. 问题描述

本人在解析大疆无人机航线文件(.kmz格式)时,遇到xml文件中【wpml:】开头的标签能通过selectNodes方法获取到值,而非【wpml:】开头的标签则获取不到值。

xml文件简化如下:

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:wpml="http://www.dji.com/wpmz/1.0.3">
  <Document>
    <wpml:author>496190712@qq.com</wpml:author>
    <wpml:createTime>1684810374696</wpml:createTime>
    <wpml:updateTime>1684811716307</wpml:updateTime>
    <wpml:missionConfig>
      <wpml:droneInfo>
        <wpml:droneEnumValue>67</wpml:droneEnumValue>
        <wpml:droneSubEnumValue>1</wpml:droneSubEnumValue>
      </wpml:droneInfo>
      <wpml:payloadInfo>
        <wpml:payloadEnumValue>53</wpml:payloadEnumValue>
        <wpml:payloadSubEnumValue>0</wpml:payloadSubEnumValue>
        <wpml:payloadPositionIndex>0</wpml:payloadPositionIndex>
      </wpml:payloadInfo>
    </wpml:missionConfig>
    <Folder>
      <Placemark>
        <Point>
          <coordinates>
            117.1263,31.83024
          </coordinates>
        </Point>
        <wpml:index>0</wpml:index>
        <wpml:ellipsoidHeight>70</wpml:ellipsoidHeight>
        <wpml:height>70</wpml:height>
      </Placemark>
      <Placemark>
        <Point>
          <coordinates>
            117.124931382143,31.8308748391917
          </coordinates>
        </Point>
        <wpml:index>1</wpml:index>
        <wpml:ellipsoidHeight>110</wpml:ellipsoidHeight>
        <wpml:height>110</wpml:height>
    </Folder>
  </Document>
</kml>

我需要取到【Placemark-Point-coordinates】节点取不到,而【Placemark-wpml:height】节点却可以取到。

2. 问题解决

经过查询找到原因是xml文件带有命名空间(xmlns属性),直接将标签名作为xpath参数传入方法中是取不到值的。

而我注意到这个xml文件有2个命名空间

①【xmlns="http://www.opengis.net/kml/2.2"】②【xmlns:wpml="http://www.dji.com/wpmz/1.0.3"】

前文那些有前缀【wpml:】的标签可以读取到可能是因为它们对应的命名空间是②,而且标签名前缀与之对应。没有该前缀的标签对应的命名空间是①,而它们标签名与命名空间属性也没有对应的规则,所以读取不到。(纯属个人猜测,本人对xml了解甚少,若有大佬知道原因的,可以评论告知,感谢!)

从代码层面上解决问题的方法可以分为3种:

2.1 将命名空间添加到所有xpath前面

        // kmz文件属于一种压缩文件,适用于ZipInputStream
        try(ZipInputStream unzipFile = new ZipInputStream(file.getInputStream(), StandardCharsets.UTF_8)) {
            ZipEntry nextEntry = unzipFile.getNextEntry();
            while (ObjectUtil.isNotEmpty(nextEntry)) {
                // 判断文件是否为航线文件,如果不是则跳过
                boolean isWaylines = ("wpmz/template.kml").equals(nextEntry.getName());
                if (!isWaylines) {
                    nextEntry = unzipFile.getNextEntry();
                    continue;
                }
                // 获取航点信息
                SAXReader reader = new SAXReader();
                Document document = reader.read(unzipFile);
                // 添加命名空间,作用域在整个文档
                Map<String, String> xmlMap = new HashMap<>();
                // 因为有2个命名空间属性,所以添加2个
                xmlMap.put("s", "http://www.opengis.net/kml/2.2");
                xmlMap.put("w", "http://www.dji.com/wpmz/1.0.3");
                reader.getDocumentFactory().setXPathNamespaceURIs(xmlMap);
                List<Node> nodes = document.selectNodes("//s:" + "Placemark");
                nodes.forEach(node -> {
                    String coor = node.valueOf("//s:" + "coordinates");
                    String height = node.valueOf("w:" + "height");
                    log.info("坐标:" + coor + " 高度:" + height);
                });
                break;
            }
        } catch (Exception e) {
            log.error("解析航线文件失败!", e);
        }

2.1 将命名空间添加到指定xpath前面

        // kmz文件属于一种压缩文件,适用于ZipInputStream
        try(ZipInputStream unzipFile = new ZipInputStream(file.getInputStream(), StandardCharsets.UTF_8)) {
            ZipEntry nextEntry = unzipFile.getNextEntry();
            while (ObjectUtil.isNotEmpty(nextEntry)) {
                // 判断文件是否为航线文件,如果不是则跳过
                boolean isWaylines = ("wpmz/template.kml").equals(nextEntry.getName());
                if (!isWaylines) {
                    nextEntry = unzipFile.getNextEntry();
                    continue;
                }
                // 获取航点信息
                SAXReader reader = new SAXReader();
                Document document = reader.read(unzipFile);
                // 添加命名空间,作用域在xPath上
                Map<String, String> xmlMap = new HashMap<>();
                // 另一个命名空间因为有对应规则,所以不用添加
                xmlMap.put("s", "http://www.opengis.net/kml/2.2");
                XPath xPath = document.createXPath("//s:" + "Placemark");
                xPath.setNamespaceURIs(xmlMap);
                List<Node> nodes = xPath.selectNodes(document);

                XPath xPath1 = document.createXPath("//s:" + "coordinates");
                xPath1.setNamespaceURIs(xmlMap);

                nodes.forEach(node -> {
                    String coor = xPath1.valueOf(node);
                    String height = node.valueOf("wpml:height");
                    log.info("坐标:" + coor + " 高度:" + height);
                });
                break;
            }
        } catch (Exception e) {
            log.error("解析航线文件失败!", e);
        }

2.3 通过标签名层层获取Element对象

        // kmz文件属于一种压缩文件,适用于ZipInputStream
        try(ZipInputStream unzipFile = new ZipInputStream(file.getInputStream(), StandardCharsets.UTF_8)) {
            ZipEntry nextEntry = unzipFile.getNextEntry();
            while (ObjectUtil.isNotEmpty(nextEntry)) {
                // 判断文件是否为航线文件,如果不是则跳过
                boolean isWaylines = ("wpmz/template.kml").equals(nextEntry.getName());
                if (!isWaylines) {
                    nextEntry = unzipFile.getNextEntry();
                    continue;
                }
                // 获取航点信息
                SAXReader reader = new SAXReader();
                Document document = reader.read(unzipFile);
                // 先获取根节点,再层层获取所需节点
                List<Element> elementList = document.getRootElement().element("Document").element("Folder").elements("Placemark");
                elementList.forEach(element -> {
                    String coor = element.element("Point").element("coordinates").getTextTrim();
                    String height = element.valueOf("wpml:height");
                    log.info("坐标:" + coor + " 高度:" + height);
                });
                break;
            }
        } catch (Exception e) {
            log.error("解析航线文件失败!", e);
        }

参考文档:

Dom4j解析XML(详解)_dom4j解析xml文件_散一世繁华,颠半世琉璃的博客-CSDN博客

https://www.cnblogs.com/vipsoft/p/16448316.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值