可扩展标记语言 (Extensible Markup Language, XML) ,标准通用标记语言的子集,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。 XML是标准通用标记语言 可扩展性良好,内容与形式分离,遵循严格的语法要求,保值性良好等优点。
XML是一种独立于软件和硬件的工具,用于存储和传输数据。XML代表可扩展标记语言,是一种与HTML非常相似的标记语言,被设计用于存储和传输数据,XML被设计为自描述的, XML是W3C推荐标准
如上所述,项目里设计之初是为了存储和传输数据,但是经过互联网的迅速迭代发展,因为xml报文的各种问题,xml在数据传输上面已经逐渐被另外一种报文协议----json所取代。
xml还是值得花费精力学习一下,有以下几点原因
下面我们通过一个简单的例子,介绍xml的语法
<note>
<from>张大妈from>
<to>小明to>
<title encoding="gbk">放学回家吃饭title>
<body>
今天做了,红烧肉,放学别贪玩,赶紧回家吃饭.
body>
note>
通过上面例子可以看到,XML保存的不只是数据,还有数据之间的结构。
<标记 属性名="属性值">元素内容标记>
在上面的例子中:
xml语法有以下约束:
上面提到xml编辑有很多约束,其中要求在属性值中不能直接包含< >等,因为这些符号会被xml识别为标签,那么如果我们真实需求就是属性值中包含这些特殊字符,此时只能通过转义符替代特殊字符,使得xml能正确识别。转义在很多语言或者语法规则中都有涉及,比如html,json等。
当属性值包含的特殊字符太多时,我们逐个字符转义比较麻烦,此时有另外一种方式可以选择:
可以使用CDATA节,如:
<description>
以及的使用]]>
description>
CDATA 部分中的所有内容都会被解析器忽略。CDATA 部分由 “” 结束:
菜鸟教程关于命名空间介绍
下面我们介绍使用开源类库编辑,修改,查询xml。
基于DOM解析的xml分析器是将其转换为一个对象模型的集合,在内存中形成一个dom树,用树这种数据结构对信息进行储存。通过DOM接口,应用程序可以在任何时候访问xml文档中的任何一部分数据,因此这种利用DOM接口访问的方式也被称为随机访问。
dom树在内存中,速度快
在解析大文档的时候,消耗大量内存
<dependency>
<groupId>org.w3cgroupId>
<artifactId>domartifactId>
<version>2.3.0-jaxb-1.0.6version>
dependency>
下面测试以这个xml报文为例
<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>
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;
@BeforeClass
public 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树
}
//查询
@Test
public 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:元素节点静态常量,表示1
if (typeNode.getNodeType() == Element.ELEMENT_NODE) {
Element type = (Element) typeNode;
String typeValue = type.getAttribute("name");
System.out.println("\t" + typeValue);
}
}
}
}
//保存xml
@Test
public 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();
}
}
//添加节点
@Test
public 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();
}
@Test
public 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文件内容的方法
@Test
public 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();
}
}
SAX解析不像DOM那样建立一个完整的文档树,而是在读取文档时激活一系列事件,这些事件被推给事件处理器,然后由事件处理器提供对文档内容的访问。常见的事件处理器有三种基本类型:
<Book>
<book id="testId">
<name>斗破苍穹name>
<author>天蚕土豆author>
book>
<book id="testId2">
<name>神墓name>
<author>辰东author>
book>
Book>
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;
@BeforeClass
public static void init() throws Exception{
//1.得到SAX解析工厂(SAXParserFactory)
SAXParserFactory factory = SAXParserFactory.newInstance();
//2. 由解析工厂生产一个SAX解析器(SAXParser)
SAXParser saxParser = factory.newSAXParser();
// 获取xmlReader
xmlReader = saxParser.getXMLReader();
}
@Test
public 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;
@Override
public 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);
}
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
currentName = qName;
// qName是element名字,类似Book
if (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));
}
}
@Override
public 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;
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
", name='" + name + '\'' +
", author='" + author + '\'' +
'}';
}
}
}
1.Dom4j是一个简单、灵活的开放源代码的库。Dom4j是由早期开发JDOM的人分离出来而后独立开
发的。与JDOM不同的是,dom4j使用接口和抽象基类,虽然Dom4j的API相对要复杂一些,但它提
供了比JDOM更好的灵活性。
2.Dom4j是一个非常优秀的Java XML API,具有性能优异、功能强大和极易使用的特点。现在很
多软件采用的Dom4j,例如Hibernate,包括sun公司自己的JAXM也用了Dom4j。
3.使用Dom4j开发,需下载dom4j相应的jar文件。
<dependency>
<groupId>org.dom4jgroupId>
<artifactId>dom4jartifactId>
<version>2.1.3version>
dependency>
<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>
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 = " ";
document = DocumentHelper.parseText(text);
//3.主动创建空document对象.
document = DocumentHelper.createDocument();
//创建根节点
Element root = document.addElement("members");
}
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();
}
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);
}
前面提到过,xml的一个优点是可以自定义约束规则,让使用者基于约束配置xml ,减少开发测试过程因为xml 编辑问题导致的文件解析错误。这就是通过DTD实现的,这个在很多框架中都有用到,比如Spring
关于XSD更多介绍
参考文献:
xml和json比较
dom操作xml
SAX解析xml
DTD和XSD区别