Java解析XML

《java世界观》文集说明

  • 本文集记载的所有内容均是java的学习笔记,主要重点记录概念,可能不会为每个概念列举完整的代码例子,更多代码例子请移步《java种田记》文集
  • 本文主要涉及内容来自开课吧新职课-JavaEE开发工程师V2.0
  • 若有错漏之处,欢迎各位指正

XML简介

XML,可扩展标记语言(eXtensible Markup Language),主要用于网络数据传输,数据存储,及配置文件等。
这里简单介绍一下XML的基础语法:

  • XML文档声明 <?xml version="1.0" encoding="UTF-8"?>
  • XML文档由一对对标记组成
  • 属性可以在开始标记中被描述,属性以 键="值" 的方式存储,多个属性之间由空格分割
  • XML文档中必须有且只有一个根标记
  • 标记可以嵌套,但不允许交叉

以下是一个例子

<?xml version="1.0" encoding="UTF-8"?>
<drinks>
    <drink id="1">
        <name>寒天爱玉</name>
        <ice>少冰</ice>
        <sugar>少糖</sugar>
        <addons>
            <addon>奶盖</addon>
            <addon>椰果</addon>        
        </addons>
    </drink>
    <drink id="2">
        <name>奶盖乌龙</name>
        <ice>去冰</ice>
        <sugar>七分糖</sugar>
        <addons>
            <addon>珍珠</addon>
            <addon>椰果</addon>        
        </addons>
    </drink>
</drinks>

XML的解析方式

SAX解析

SAX, Simple API for XML

  • 事件驱动的解析机制
  • SAX解析器会逐行阅读,当读取到一个标记的开始、属性、内容、结束时触发事件
  • 需要编写事件触发的相应处理程序
优点
  • 无需等待数据全部加载完毕,立即开始分析
  • 逐行加载,节省内存
  • 不必解析整个文档,可以在满足某个条件时停止解析
缺点
  • 单向解析,无法对之前的数据再进行操作
  • 逐行解析无法得知复杂的元素的层次,只能维护父子关系的节点
  • 只读解析,无法修改内容

DOM解析

DOM,文档对象模型(Document Object Model),是w3c官方制定的用以处理XML文档的标准编程接口。DOM将XML文档解析成树状结构,程序员可以通过操作此文档树来获取、修改、删除数据。

优点
  • 文档被加载至内存,允许对数据和结构进行更改
  • 支持双向解析数据
缺点
  • 文档全部被加载至内存中,消耗资源大

Java中的DOM解析

对比SAX解析和DOM解析,后者的缺点可以忽略不计,因此主要使用DOM解析的方式处理XML文档

JDOM

如上文所说,DOM解析本身是面向所有平台的接口,DOM提供给Java的接口也只是达到了能够实现的最低通用标准,并没有特地考虑到Java的特性并作出相应的优化设计,因此Java程序员直接使用DOM的接口则会感觉到不便。
JDOM则是为了解决这一问题,由Brett McLaughlin和Jason Hunter开发出来的开源项目,该项目致力于提供完善方便的用Java解析XML的解决方案。

优点
  • 实现了许多工具类,比起直接使用DOM的API,简便了XML的解析过程
  • 加入了大量java集合,方便java程序员进行开发
缺点
  • 不是面向接口设计,没有较好的灵活性
  • 优化并不是很好,性能没有那么优异

DOM4J

DOM4J也是为Java设计的用于解析XML的开源API(Application Programming Interface,应用程序接口)
和JDOM相比,DOM4J的性能更为优异,功能更加强大,使用更加便捷,因此也更为常用

代码实现步骤
  1. 下载并引入dom4j.jar
  2. 创建XML读取工具对象 SAXReader sr = new SAXReader();
  3. 通过XML读取工具对象读取文档对象(加载到内存的整个XML文档) Document doc = sr.read(<文档对象>);,这里read(<文档对象>)方法可以接收输入流,也可以直接接收文件或URL
  4. 通过文档对象获得根元素对象再进行后续操作 Element root = doc.getRootElement();
元素对象 Element 常用操作
  • 获取节点名称 String getName();
  • 获取节点内容 String getText();
  • 设置节点内容 void setText(String text);
  • 根据子节点的名称,获取匹配到的第一个子节点对象 Element element(String name)
  • 获取所有的子节点对象 List<Element> elements();
  • 获取节点的属性值 String attributeValue(String attributeName);
  • 获取子节点的内容 String elementText(String childName);
  • 添加子节点 Element addElement(String s)
  • 添加属性 Element addAttribute(String name, String value);
示例
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.util.List;

/**
 * @author 蜗先生
 * @date 2020/9/22
 */
public class Main {
    public static void main(String[] args) throws DocumentException {
        // 创建XML读取工具对象,并获取文档对象,文档内容在文章开头
        Document doc = new SAXReader().read(new File("drinks.xml"));
        // 获取根元素
        Element root = doc.getRootElement();
        
        // 解析示例
        // 打印根元素名称
        System.out.println(root.getName());
        // 打印所有元素信息
        List<Element> elements = root.elements();
        for (Element drink : elements) {
            System.out.println(drink.attributeValue("id"));
            System.out.println(drink.elementText("name"));
            System.out.println(drink.elementText("ice"));
            System.out.println(drink.elementText("sugar"));
            List<Element> addons = drink.element("addons").elements();
            for (Element addon : addons) {
                System.out.println(addon.getText());
            }
        }
    }
}

运行结果

drinks
1
寒天爱玉
少冰
少糖
奶盖
椰果
2
奶盖乌龙
去冰
七分糖
珍珠
椰果

Process finished with exit code 0
XPATH 路径表达式

XPath是一种用来确定XML文档中某部分位置的语言,它可以便捷的查询元素,dom4j中也提供了对XPath的支持

表达式 描述
nodename 选取此节点的所有子节点。
/ 从根节点选取。
// 从当前节点选取所有后代节点。
. 选取当前节点。
.. 选取当前节点的父节点。
@ 选取属性。

@ 属性使用方式:

  • [@attributeName='value']
  • [@attributeName>'value']
  • [@attributeName<'value']
  • [@attributeName!='value']

DOM4J中的XPATH使用方式:

  • 需要导入jaxen.jar
  • Element element = (Element) document.selectSignleNode(<XPath路径表达式>);Node node = document.selectSignleNode(<XPath路径表达式>);
  • List<Element> elements = document.selectNodes(<XPath路径表达式>);

注:
Node selectSingleNode(String var1);List selecNodes(String var1);
均为接口org.dom4j.Node的方法,而org.dom4j.Documentorg.dom4j.Element均继承了此接口(当然,此接口被许多其他类实现),此接口内的方法还包括前文提到的void setText(String var1)String getText()
更多内容建议查阅API文档(github来源 第三方来源)或直接阅读源码

代码示例

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.util.List;

/**
 * @author 蜗先生
 * @date 2020/9/22
 */
public class Main {
    public static void main(String[] args) throws DocumentException {
        // 创建XML读取工具对象,并获取文档对象,文档内容在文章开头
        Document doc = new SAXReader().read(new File("drinks.xml"));
        
        // XPath解析示例
        // 查找所有addon元素并打印信息
        List<Element> addons = doc.selectNodes("//addon");
        for (Element addon : addons) {
            System.out.println(addon.getText());
        }

        System.out.println("--------------------");
        
        // 查找id为2的饮料的addon元素并打印信息
        addons = doc.selectNodes("//drink[@id='2']//addon");
        for (Element addon : addons) {
            System.out.println(addon.getText());
        }
    }
}

运行结果

奶盖
椰果
珍珠
椰果
--------------------
珍珠
椰果

Process finished with exit code 0
生成/修改XML
  1. 获取现有文档对象(见上文),或创建空文档对象 Document doc = DocumentHelper.createDocument();
  2. 添加或修改节点或内容(见上文)
  3. 创建储存新文档的文件输出流 FileOutputStream fos = new FileOutputStream(<filePath>);
  4. 将文件输出流转换成XML文档输出流 XMLWrite xw = new XMLWriter(fos);
  5. 写出文档 xw.write(doc);
  6. 释放资源 xw.close(); fos.close();

代码示例

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

/**
 * @author 蜗先生
 * @date 2020/9/22
 */
public class Main {
    public static void main(String[] args) throws DocumentException, IOException {
        // 创建XML读取工具对象,并获取文档对象,文档内容在文章开头
        Document doc = new SAXReader().read(new File("drinks.xml"));
        
        // 修改文档
        // 给id为1的饮料添加addon珍珠
        Element element = (Element) doc.selectSingleNode("//drink[@id=1]//addons");
        element.addElement("addon").setText("珍珠");
        
        // 添加id为3的饮料及相应信息
        element = doc.getRootElement();
        Element drink = element.addElement("drink");
        drink.addAttribute("id", "3");
        drink.addElement("name").setText("红枣桂圆");
        drink.addElement("ice").setText("热饮");
        drink.addElement("sugar").setText("正常");
        Element addons = drink.addElement("addons");
        // 以下配料纯属举例,请不要轻易尝试黑暗料理
        addons.addElement("addon").setText("椰果");
        addons.addElement("addon").setText("小芋圆");
        
        // 修改完毕,写出文档并关闭资源
        FileWriter fileWriter = new FileWriter("drinks2.xml");
        XMLWriter xmlWriter =  new XMLWriter(fileWriter);
        
        xmlWriter.write(doc);

        xmlWriter.close();
        fileWriter.close();
    }
}

生成的drinks2.xml文件内容
*由于直接生成的内容挤在一行不便阅读,以下内容进行过美化格式处理

<?xml version="1.0" encoding="UTF-8"?>
<drinks>
    <drink id="1">
        <name>寒天爱玉</name>
        <ice>少冰</ice>
        <sugar>少糖</sugar>
        <addons>
            <addon>奶盖</addon>
            <addon>椰果</addon>
            <addon>珍珠</addon>
        </addons>
    </drink>
    <drink id="2">
        <name>奶盖乌龙</name>
        <ice>去冰</ice>
        <sugar>七分糖</sugar>
        <addons>
            <addon>珍珠</addon>
            <addon>椰果</addon>
        </addons>
    </drink>
    <drink id="3">
        <name>红枣桂圆</name>
        <ice>热饮</ice>
        <sugar>正常</sugar>
        <addons>
            <addon>椰果</addon>
            <addon>小芋圆</addon>
        </addons>
    </drink>
</drinks>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339