4.java程序员必知必会类库之xml解析库
前言
百度百科解释
可扩展标记语言 (Extensible Markup Language, XML) ,标准通用标记语言的子集,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。 XML是标准通用标记语言 可扩展性良好,内容与形式分离,遵循严格的语法要求,保值性良好等优点。
XML是一种独立于软件和硬件的工具,用于存储和传输数据。XML代表可扩展标记语言,是一种与HTML非常相似的标记语言,被设计用于存储和传输数据,XML被设计为自描述的, XML是W3C推荐标准
1 笔者理解
如上所述,项目里设计之初是为了存储和传输数据,但是经过互联网的迅速迭代发展,因为xml报文的各种问题,xml在数据传输上面已经逐渐被另外一种报文协议----json所取代。
1.1 xml值得学习的原因
xml还是值得花费精力学习一下,有以下几点原因
- 尽管迅速发展的互联网导致很多新的项目报文交互用的是json,但是xml在一些老项目中仍然存在,当维护到老项目的时候,需要xml相关知识技能
- xml在报文传输上面不占优势,但是因为xml可自定义语法规范,避免使用框架的人笔误等原因配置错误,在很多开源框架的配置选择上面xml是很活跃的,常见的有mybatis,spring,zookeeper等等,基本很多流行的框架里面,都可以看到xml。
- 甚至笔者自己的公司,在做框架配置选择的时候,也是选择xml定义语法规范,使用者在配置的时候,会有提示和校验,提高开发和排查问题效率
1.2 xml语法
1.2.1 语法简介
下面我们通过一个简单的例子,介绍xml的语法
<note><from>张大妈</from><to>小明</to><title encoding="gbk">放学回家吃饭</title><body>今天做了,红烧肉,放学别贪玩,赶紧回家吃饭.</body>
</note>
通过上面例子可以看到,XML保存的不只是数据,还有数据之间的结构。
<标记 属性名="属性值">元素内容</标记>
在上面的例子中:
- note,title 叫做标签,标记
- encoding 叫属性
- gbk 叫属性值
- 放学回家吃饭 叫元素内容
xml语法有以下约束:
- 所有XML元素都必须有结束标签
- XML标签对大小写敏感
- XML必须正确的嵌套
- 同级标签以缩进对齐
- 元素名称可以包含字母、数字或其他的字符
- 元素名称不能以数字或者标点符号开始
- 元素名称中不能含空格
- 属性值用双引号包裹
- 一个元素可以有多个属性
- 属性值中不能直接包含<、“、&(不建议:‘、>)
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.3.1.2 缺点
- 比较重,XML文件庞大,文件格式复杂,传输占带宽
- 服务器端和客户端都需要花费大量代码来解析XML,导致服务器端和客户端代码变得异常复杂且不易维护
- 客户端不同浏览器之间解析 XML 的方式不一致,需要重复编写很多代码
- 服务器端和客户端解析 XML 花费较多的资源和时间
1.3.2 json
1.3.2.1 优点
- 与xml相比,灵活性很高,数据格式比较简单,易于读写,格式都是压缩的,占用带宽小
- 易于解析,客户端JavaScript可以简单的通过eval()进行JSON数据的读取
- 因为JSON格式能直接为服务器端代码使用,大大简化了服务器端和客户端的代码开发量,且完成任务不变,并且易于维护
- 主要用于系统间报文交互
1.3.2.2 缺点
- json 比xml 灵活,易于使用,这是优点,但是换个角度也会是缺点,因为没有想过规范约束,只要满足json的语法规则都可以执行通过,如果需要在json上面做一些配置限制,就没有xml方便(现在市面上也有一些插件,尝试实现json的配置约束管理,但是还不流行)
- 不利于用于配置文件管理
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文件步骤
- 创建解析器工厂对象
- 解析器工厂对象创建解析器对象
- 解析器对象指定XML文件创建Document对象
- 以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那样建立一个完整的文档树,而是在读取文档时激活一系列事件,这些事件被推给事件处理器,然后由事件处理器提供对文档内容的访问。常见的事件处理器有三种基本类型:
- 用于访问XML DTD内容的DTDHandler;
- 用于低级访问解析错误的ErrorHandler;
- 用于访问文档内容的ContentHandler,这也是最普遍使用的事件处理器。解析器读取输入文档并在处理文档时将每个事件推给文档处理器(MyContentHandler)。
2.2.2 sax优点
- 与DOM相比,SAX解析器能提供更好的性能优势,它提供对XML文档内容的有效低级访问。SAX模型最大的优点是内存消耗小,因为整个文档无需一次加载到内存中,这使SAX解析器可以解析大于系统内存的文档。
- 无需像在DOM中那样为所有节点创建对象。最后,SAX“推”模型可用于广播环境,能够同时注册多个ContentHandler,并行接收事件,而不是在一个管道中一个接一个地进行处理。
- 只需要单遍读取内容的应用程序可以从SAX解析中大大受益。很多B2B和EAI应用程序将XML用做封装格式,接收端用这种格式简单地接收所有数据。这就是SAX明显优于DOM的地方:因高效而获得高吞吐率。在SAX 2.0 中有一个内置的过滤机制,可以很轻松地输出一个文档子集或进行简单的文档转换。
2.2.3 sax缺点
- 必须实现多个事件处理程序以便能够处理所有到来的事件,同时你还必须在应用程序代码中维护这个事件状态,因为SAX解析器不能交流元信息,如DOM的父/子支持,所以你必须跟踪解析器处在文档层次的哪个位置。
- 文档越复杂,应用逻辑就越复杂。虽然没有必要一次将整个文档加载到内存中,但SAX解析器仍然需要解析整个文档,这点和DOM一样。
- 也许SAX面临的最大问题是它没有内置如XPath所提供的那些导航支持。再加上它的单遍解析,使它不能支持随机访问。这一限制也表现在命名空间上: 对有继承名字空间的元素不做注解。这些限制使SAX很少被用于操作或修改文档。
2.2.4 sax解析文件步骤
- 得到SAX解析工厂(SAXParserFactory)
- 由解析工厂生产一个SAX解析器(SAXParser)
- 由xmlParser获取一个xmlReader
- 传入输入流和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优点
- 性能优异
- 功能强大
- 极端易用
2.3.3 dom4j缺点
- 因为使用的是第三方包,代码可移植性差,需要目标项目也使用dom4j
- 和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区别