> 文章列表 > 4.java程序员必知必会类库之xml解析库

4.java程序员必知必会类库之xml解析库

4.java程序员必知必会类库之xml解析库

前言

百度百科解释

可扩展标记语言 (Extensible Markup Language, XML) ,标准通用标记语言的子集,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。 XML是标准通用标记语言 可扩展性良好,内容与形式分离,遵循严格的语法要求,保值性良好等优点。
XML是一种独立于软件和硬件的工具,用于存储和传输数据。XML代表可扩展标记语言,是一种与HTML非常相似的标记语言,被设计用于存储和传输数据,XML被设计为自描述的, XML是W3C推荐标准

1 笔者理解

如上所述,项目里设计之初是为了存储和传输数据,但是经过互联网的迅速迭代发展,因为xml报文的各种问题,xml在数据传输上面已经逐渐被另外一种报文协议----json所取代。

1.1 xml值得学习的原因

xml还是值得花费精力学习一下,有以下几点原因

  1. 尽管迅速发展的互联网导致很多新的项目报文交互用的是json,但是xml在一些老项目中仍然存在,当维护到老项目的时候,需要xml相关知识技能
  2. xml在报文传输上面不占优势,但是因为xml可自定义语法规范,避免使用框架的人笔误等原因配置错误,在很多开源框架的配置选择上面xml是很活跃的,常见的有mybatis,spring,zookeeper等等,基本很多流行的框架里面,都可以看到xml
  3. 甚至笔者自己的公司,在做框架配置选择的时候,也是选择xml定义语法规范,使用者在配置的时候,会有提示和校验,提高开发和排查问题效率

1.2 xml语法

1.2.1 语法简介

下面我们通过一个简单的例子,介绍xml的语法

<note><from>张大妈</from><to>小明</to><title encoding="gbk">放学回家吃饭</title><body>今天做了,红烧肉,放学别贪玩,赶紧回家吃饭.</body>
</note>

通过上面例子可以看到,XML保存的不只是数据,还有数据之间的结构。

<标记 属性="属性值">元素内容</标记>

在上面的例子中:

  1. note,title 叫做标签,标记
  2. encoding 叫属性
  3. gbk 叫属性值
  4. 放学回家吃饭 叫元素内容

xml语法有以下约束:

  1. 所有XML元素都必须有结束标签
  2. XML标签对大小写敏感
  3. XML必须正确的嵌套
  4. 同级标签以缩进对齐
  5. 元素名称可以包含字母、数字或其他的字符
  6. 元素名称不能以数字或者标点符号开始
  7. 元素名称中不能含空格
  8. 属性值用双引号包裹
  9. 一个元素可以有多个属性
  10. 属性值中不能直接包含<、“、&(不建议:‘、>)

1.2.2 转义字符

上面提到xml编辑有很多约束,其中要求在属性值中不能直接包含< >等,因为这些符号会被xml识别为标签,那么如果我们真实需求就是属性值中包含这些特殊字符,此时只能通过转义符替代特殊字符,使得xml能正确识别。转义在很多语言或者语法规则中都有涉及,比如html,json等。
在这里插入图片描述
当属性值包含的特殊字符太多时,我们逐个字符转义比较麻烦,此时有另外一种方式可以选择:
可以使用CDATA节,如:

<description><![CDATA[讲解了元素<title>以及</title>的使用]]>
</description>

CDATA 部分中的所有内容都会被解析器忽略。CDATA 部分由 “<![CDATA[" 开始,由 "]]>” 结束:

1.2.3 命名空间

菜鸟教程关于命名空间介绍

1.3 xml和json比较

1.3.1 xml

1.3.1.1 优点

  1. 格式统一,符合标准
  2. 可以自定义相关报文规范语法,为很多开源框架所使用

1.3.1.2 缺点

  1. 比较重,XML文件庞大,文件格式复杂,传输占带宽
  2. 服务器端和客户端都需要花费大量代码来解析XML,导致服务器端和客户端代码变得异常复杂且不易维护
  3. 客户端不同浏览器之间解析 XML 的方式不一致,需要重复编写很多代码
  4. 服务器端和客户端解析 XML 花费较多的资源和时间

1.3.2 json

1.3.2.1 优点

  1. 与xml相比,灵活性很高,数据格式比较简单,易于读写,格式都是压缩的,占用带宽小
  2. 易于解析,客户端JavaScript可以简单的通过eval()进行JSON数据的读取
  3. 因为JSON格式能直接为服务器端代码使用,大大简化了服务器端和客户端的代码开发量,且完成任务不变,并且易于维护
  4. 主要用于系统间报文交互

1.3.2.2 缺点

  1. json 比xml 灵活,易于使用,这是优点,但是换个角度也会是缺点,因为没有想过规范约束,只要满足json的语法规则都可以执行通过,如果需要在json上面做一些配置限制,就没有xml方便(现在市面上也有一些插件,尝试实现json的配置约束管理,但是还不流行)
  2. 不利于用于配置文件管理

2 使用

下面我们介绍使用开源类库编辑,修改,查询xml。

2.1 dom解析

2.1.1 dom解析简介

基于DOM解析的xml分析器是将其转换为一个对象模型的集合,在内存中形成一个dom树,用树这种数据结构对信息进行储存。通过DOM接口,应用程序可以在任何时候访问xml文档中的任何一部分数据,因此这种利用DOM接口访问的方式也被称为随机访问。

2.1.2 dom解析优点

dom树在内存中,速度快

2.1.3 dom解析缺点

在解析大文档的时候,消耗大量内存

2.1.4 dom解析XML文件步骤

  1. 创建解析器工厂对象
  2. 解析器工厂对象创建解析器对象
  3. 解析器对象指定XML文件创建Document对象
  4. 以Document对象为起点操作DOM树

2.1.4.1 添加pom

 <dependency><groupId>org.w3c</groupId><artifactId>dom</artifactId><version>2.3.0-jaxb-1.0.6</version></dependency>

2.1.4.2 样例demoxml

     下面测试以这个xml报文为例
<?xml version="1.0" encoding="utf-8" standalone="no"?><phoneInfo><brand name="华为手机"><type name="华为荣耀"/><type name="HW123"/><type name="RY321"/></brand><brand name="小米手机"><type name="小米10"/><type name="红米"/><type name="Rednote"/></brand><brand name="苹果手机"><type name="iphone7" /><type name="iphone8" /><type name="iphone9" /></brand>
</phoneInfo>

2.1.4.3 查询,保存,增删改节点

package com.wanlong.xml;import org.junit.BeforeClass;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;/*** @author wanlong* @version 1.0* @description:* @date 2023/4/17 10:07*/public class DomTest {static Document document = null;@BeforeClasspublic static void initdocument() throws Exception {//1.创建解析器工厂对象DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();//2.解析器工厂对象创建解析器对象DocumentBuilder db = dbf.newDocumentBuilder();//3.解析器对象指定XML文件创建Document对象,这里可以写绝对路径document = db.parse("test.xml");//4. 以Document对象为起点操作DOM树}//查询@Testpublic void print() {NodeList brands = document.getElementsByTagName("brand");//遍历brands集合for (int i = 0; i < brands.getLength(); i++) {//获取brands集合元素,返回值Node节点类型Node node = brands.item(i);//向下转型:将Node节点类型转换成真正的类型Element元素节点Element brand = (Element) node;//getAttribute("属性名"):通过属性名返回属性值String nameValue = brand.getAttribute("name");System.out.println(nameValue);//getChildNodes():获取brand元素节点的子节点,这个子节点可能不止一个,返回的是NodeList集合NodeList types = brand.getChildNodes();//遍历子节点types集合for (int j = 0; j < types.getLength(); j++) {//获取子节点集合元素Node typeNode = types.item(j);//要做判断,确保获取的子节点是元素节点//ELEMENT_NODE:元素节点静态常量,表示1if (typeNode.getNodeType() == Element.ELEMENT_NODE) {Element type = (Element) typeNode;String typeValue = type.getAttribute("name");System.out.println("\\t" + typeValue);}}}}//保存xml@Testpublic void save() {String path="test2.xml";//第一步:创建引用//创建TransformerFactory对象引用TransformerFactory transformerFactory = TransformerFactory.newInstance();try {//创建Transformer对象引用Transformer transformer = transformerFactory.newTransformer();//在转换前先设置编码类型格式/** setOutputProperty(String name, String value):设置编码类型,包括属性、属性值* OutputKeys:提供可用于设置 Transformer的输出属性* OutputKeys.ENCODING:静态常量,等于encoding*/transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");//encoding="UTF-8"//第二步:把DOM转换为XML文件:transform(Source xmlSource, Result outputTarget)方法//DOMSource类是Source接口的实现类,创建指定DOM树DOMSource domSource = new DOMSource(document);//StreamResult类是Result接口的实现类,StreamResult(OutputStream)方法:需要传递的参数是字节流StreamResult result = new StreamResult(new FileOutputStream(path));//传递的参数是Source类型和Result类型transformer.transform(domSource, result);} catch (TransformerConfigurationException e) {e.printStackTrace();} catch (FileNotFoundException e) {e.printStackTrace();} catch (TransformerException e) {e.printStackTrace();}}//添加节点@Testpublic void addNode() {//创建brand节点,createElement():创建元素节点Element brandElement = document.createElement("brand");//setAttribute(String name,String value):设置属性名、属性值brandElement.setAttribute("name", "苹果手机");//创建type节点Element typeElement = document.createElement("type");typeElement.setAttribute("name", "iphone14");//appendChild(Node newChild):添加父子关系brandElement.appendChild(typeElement);//获取第一个元素节点:先获取元素节点集合,再获取一个元素Element phoneInfoElement = (Element) document.getElementsByTagName("phoneInfo").item(0);//添加父子关系phoneInfoElement.appendChild(brandElement);//添加完毕之后,此时的数据还在内存中,需要进一步保存到XML文件中save();}@Testpublic void updateNode() {//获取brand元素节点集合NodeList brands = document.getElementsByTagName("brand");//遍历集合for (int i = 0; i < brands.getLength(); i++) {//获取brandElement元素节点Element brandElement = (Element) brands.item(i);//通过属性名获得brand元素节点属性值String brandName = brandElement.getAttribute("name");//做个判断,如果属性值是苹果手机,重新设置属性值if (brandName.equals("苹果手机")) {brandElement.setAttribute("name", "IPhone");}}//修改好后,需要保存到指定的XML文件中save();}//删除//删除XML文件内容的方法@Testpublic void deleteXml() {NodeList brandList = document.getElementsByTagName("brand");//遍历brandList集合for (int i = 0; i < brandList.getLength(); i++) {Element brandElement = (Element) brandList.item(i);String brandName = brandElement.getAttribute("name");//如果属性名是IPhone,则删除这个brand节点if (brandName.equals("苹果手机")) {//通过父元素节点移除子节点brandElement.getParentNode().removeChild(brandElement);}}//删除后需要保存save();}
}

2.2 sax解析

2.2.1 sax解析简介

SAX解析不像DOM那样建立一个完整的文档树,而是在读取文档时激活一系列事件,这些事件被推给事件处理器,然后由事件处理器提供对文档内容的访问。常见的事件处理器有三种基本类型:

  1. 用于访问XML DTD内容的DTDHandler;
  2. 用于低级访问解析错误的ErrorHandler;
  3. 用于访问文档内容的ContentHandler,这也是最普遍使用的事件处理器。解析器读取输入文档并在处理文档时将每个事件推给文档处理器(MyContentHandler)。

2.2.2 sax优点

  1. 与DOM相比,SAX解析器能提供更好的性能优势,它提供对XML文档内容的有效低级访问。SAX模型最大的优点是内存消耗小,因为整个文档无需一次加载到内存中,这使SAX解析器可以解析大于系统内存的文档。
  2. 无需像在DOM中那样为所有节点创建对象。最后,SAX“推”模型可用于广播环境,能够同时注册多个ContentHandler,并行接收事件,而不是在一个管道中一个接一个地进行处理。
  3. 只需要单遍读取内容的应用程序可以从SAX解析中大大受益。很多B2B和EAI应用程序将XML用做封装格式,接收端用这种格式简单地接收所有数据。这就是SAX明显优于DOM的地方:因高效而获得高吞吐率。在SAX 2.0 中有一个内置的过滤机制,可以很轻松地输出一个文档子集或进行简单的文档转换。

2.2.3 sax缺点

  1. 必须实现多个事件处理程序以便能够处理所有到来的事件,同时你还必须在应用程序代码中维护这个事件状态,因为SAX解析器不能交流元信息,如DOM的父/子支持,所以你必须跟踪解析器处在文档层次的哪个位置。
  2. 文档越复杂,应用逻辑就越复杂。虽然没有必要一次将整个文档加载到内存中,但SAX解析器仍然需要解析整个文档,这点和DOM一样。
  3. 也许SAX面临的最大问题是它没有内置如XPath所提供的那些导航支持。再加上它的单遍解析,使它不能支持随机访问。这一限制也表现在命名空间上: 对有继承名字空间的元素不做注解。这些限制使SAX很少被用于操作或修改文档。

2.2.4 sax解析文件步骤

  1. 得到SAX解析工厂(SAXParserFactory)
  2. 由解析工厂生产一个SAX解析器(SAXParser)
  3. 由xmlParser获取一个xmlReader
  4. 传入输入流和handler给xmlReader,调用parse()解析

2.2.4.1 样例xml

<?xml version="1.0" encoding="UTF-8"?>
<Book><book id="testId"><name>斗破苍穹</name><author>天蚕土豆</author></book><book id="testId2"><name>神墓</name><author>辰东</author></book>
</Book>

2.2.4.2 sax解析实现查询

package com.wanlong.xml;import org.junit.BeforeClass;
import org.junit.Test;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;import java.util.ArrayList;
import java.util.List;/*** @author wanlong* @version 1.0* @description:* @date 2023/4/17 13:32*/
public class SaxTest {static  XMLReader xmlReader=null;@BeforeClasspublic static void init() throws Exception{//1.得到SAX解析工厂(SAXParserFactory)SAXParserFactory factory = SAXParserFactory.newInstance();//2. 由解析工厂生产一个SAX解析器(SAXParser)SAXParser saxParser = factory.newSAXParser();// 获取xmlReaderxmlReader = saxParser.getXMLReader();}@Testpublic void test() throws Exception{// 注册自定义解析器MyHandler myHander = new MyHandler();//传入输入流和handler给解析器,调用parse()解析xmlReader.setContentHandler(myHander);// 解析xml ,这里注意,如果用junit测试,相对路径的话,需要将文件放到测试目录根目录xmlReader.parse(this.getClass().getClassLoader().getResource("SaxTest.xml").getFile());// 获取解析结果List<Book> bookList =myHander.getBookList();System.out.println(bookList);}// 这个事件解析器要完成的职责就是如果读取到开始节点是Book,则创建一个list,然后如果是book节点,则创建一个Book实体,// 并且将id,name,author赋值给这个book实体,characters可以区分当前文本内容是name还是author是通过currentName去处理的class MyHandler extends DefaultHandler {private List<Book> bookList;private Book book;private String currentName;@Overridepublic void characters(char[] ch, int start, int length) throws SAXException {// characters处理节点text内容,类似 道斩乾坤 这种if (currentName.equals("name")) {String s = new String(ch, start, length);book.setName(s);} else if (currentName.equals("author")){String s = new String(ch, start, length);book.setAuthor(s);}}@Overridepublic void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {currentName = qName;// qName是element名字,类似Bookif (qName.equals("Book")) {bookList = new ArrayList<Book>();}if (qName.equals("book")) {book = new Book();// attributes 是element的属性,类似id这种String id = attributes.getValue("id");book.setId(Long.valueOf(id));}}@Overridepublic void endElement(String uri, String localName, String qName) throws SAXException {currentName = "";// endElement 表示这个节点解析结束if (qName.equals("book")) {bookList.add(book);}}public List<Book> getBookList() {return bookList;}public void setBookList(List<Book> bookList) {this.bookList = bookList;}}class Book {private Long id;private String name;private String author;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getAuthor() {return author;}public void setAuthor(String author) {this.author = author;}@Overridepublic String toString() {return "Book{" +"id=" + id +", name='" + name + '\\'' +", author='" + author + '\\'' +'}';}}
}

2.3 dom4j 解析(推荐)

2.3.1 dom4j解析简介

1.Dom4j是一个简单、灵活的开放源代码的库。Dom4j是由早期开发JDOM的人分离出来而后独立开
发的。与JDOM不同的是,dom4j使用接口和抽象基类,虽然Dom4j的API相对要复杂一些,但它提
供了比JDOM更好的灵活性。
2.Dom4j是一个非常优秀的Java XML API,具有性能优异、功能强大和极易使用的特点。现在很
多软件采用的Dom4j,例如Hibernate,包括sun公司自己的JAXM也用了Dom4j。
3.使用Dom4j开发,需下载dom4j相应的jar文件。

2.3.2 dom4j优点

  1. 性能优异
  2. 功能强大
  3. 极端易用

2.3.3 dom4j缺点

  1. 因为使用的是第三方包,代码可移植性差,需要目标项目也使用dom4j
  2. 和dom一样,需要读取完整文档在内存中形成dom树,对内存有要求

2.3.4 dom4j解析文件步骤

2.3.4.1 添加依赖

<dependency><groupId>org.dom4j</groupId><artifactId>dom4j</artifactId><version>2.1.3</version>
</dependency>

2.3.4.2 样例demo

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<phoneInfo><brand name="华为手机"><type name="华为荣耀"/><type name="HW123"/><type name="RY321"/></brand><brand name="小米手机"><type name="小米10"/><type name="红米"/><type name="Rednote"/></brand><brand name="苹果手机"><type name="iphone7" /><type name="iphone8" /><type name="iphone9" /></brand>
</phoneInfo>

2.3.4.3 api使用

2.3.4.3.1 创建document三种方式

public static void createDocument() throws Exception {//1.读取XML文件,获得document对象SAXReader reader = new SAXReader();document = reader.read(new File("input.xml"));//2.解析XML形式的文本,得到document对象.String text = "<?xml version=\\"1.0\\" encoding=\\"utf-8\\" standalone=\\"no\\"?><phoneInfo><brand name=\\"华为手机\\"><type name=\\"华为荣耀\\" /><type name=\\"HW123\\" /><type name=\\"RY321\\" /></brand></phoneInfo>";document = DocumentHelper.parseText(text);//3.主动创建空document对象.document = DocumentHelper.createDocument();//创建根节点Element root = document.addElement("members");
}
2.3.4.3.2 保存文件
public void saveXml(Document document) throws Exception {OutputFormat format = OutputFormat.createPrettyPrint();// 指定XML编码format.setEncoding("utf-8");XMLWriter writer = new XMLWriter(new FileWriter("output.xml"), format);writer.write(document);writer.close();
}
2.3.4.3.3 元素增删改查
public void processElement() {//获取文档的元素.Element root = document.getRootElement();//获取某个元素的指定名称的第一个子节点.Element element = root.element("brand");//获取某个元素的指定名称的所有子元素的集合List list = root.elements("brand");//添加一个指定名称的子元素Element childEle = root.addElement("brand");//删除某个元素指定的子元素root.remove(childEle);//属性Attribute操作,获取某个元素的指定名称的属性对象Attribute attr = element.attribute("name");//获取某个元素的指定名称的属性值String name = element.attributeValue("name");//给元素添加属性或更新其值element.addAttribute("id", "123");//删除某个元素的指定属性element.remove(attr);//文本Text的操作// 获取某个元素的文本内容String text = element.getText();//给某个元素添加或更新文本内容element.setText("测试内容");saveXml(document);
}

3 DTD和XSD

前面提到过,xml的一个优点是可以自定义约束规则,让使用者基于约束配置xml ,减少开发测试过程因为xml 编辑问题导致的文件解析错误。这就是通过DTD实现的,这个在很多框架中都有用到,比如Spring
关于XSD更多介绍

参考文献:
xml和json比较
dom操作xml
SAX解析xml
DTD和XSD区别