目录

XML 可扩展标记语言

XML 是可扩展标记语言(eXtensible Markup Language)的缩写,是一种标记语言。XML是从标准通用标记语言(SGML)中简化修改出来的。它主要用到的有可扩展标记语言、可扩展样式语言(XSL)、XBRL和XPath等。

它是是一种数据表示格式,可以描述非常复杂的数据结构,常用于传输和存储数据。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE note SYSTEM "book.dtd">
<book id="1">
    <name>Java核心技术</name>
    <author>Cay S. Horstmann</author>
    <isbn lang="CN">1234567</isbn>
    <tags>
        <tag>Java</tag>
        <tag>Network</tag>
    </tags>
    <pubDate/>
</book>

XML有几个特点:一是纯文本,默认使用UTF-8编码,二是可嵌套,适合表示结构化数据。如果把XML内容存为文件,那么它就是一个XML文件。此外,XML内容经常通过网络作为消息传输。

用途

XML设计是用来传送和携带数据信息,不用于表现和展示数据,HTML则用来表现数据,所以XML用途的焦点是在于说明数据是什么以及携带数据信息。

  • 富文档(Rich Documents)- 自定文件描述并使其更丰富
    • 属于文件为主的XML技术应用
    • 标记是用来定义一份资料应该如何呈现
  • 元数据(Metadata)- 描述其它文件或网络资讯
    • 属于资料为主的XML技术应用
    • 标记是用来说明一份资料的意义
  • 配置文档(Configuration Files)- 描述软件设置的参数

重要术语

处理器(Processor)与应用(application)

XML处理器(Processor,也称作XML parser)分析标记语言并传递结构化信息给应用(application)。

标记(Markup)与内容(content)

XML文档的字符分为标记(Markup)与内容(content)两类。标记通常以<开头,以>结尾;或者以字符& 开头,以;结尾。不是标记的字符就是内容。但是CDATA部分,分解符号<![CDATA[]]>是标记,二者之间的文本为内容。 最外界的空白符是标记。

标签(Tag)

一个tag属于标记结构,以<开头,以>结尾。Tag名字是大小写敏感,不能包括任何字符 !"#$%&’()*+,/;<=>?@[]^`{|}~, 也不能有空格符, 不能以"-“或”.“或数字开始。可分为三类:

  • start-tag,如;
  • end-tag,如;
  • empty-element tag,如.

元素(Element)

元素是文档逻辑组成,或者在start-tag与匹配的end-tag之间,或者仅作为一个empty-element tag。例如:<greeting>Hello, world!</greeting>。另一个例子是: <line-break />

单个根(root)元素包含所有的其他元素。

属性(Attribute)

属性是一种标记结构,在start-tag或empty-element tag内部的“名字-值对”。例如:<img src="madonna.jpg" alt="Madonna" />。每个元素中,一个属性最多出现一次,一个属性只能有一个值。

如果属性有多个值,这需要采取XML协议以外的方式来表示,如采用逗号或分号间隔,对于CSS类或标识符的名字可用空格来分隔。

XML 声明(declaration)

XML文档如果以XML declaration开始,则表述了文档的一些信息。如<?xml version="1.0" encoding="UTF-8"?>

结构

每个XML文档都由XML声明开始,在上面的代码中的第一行就是XML声明,<?xml version="1.0"?>。这一行代码会告诉解析器或浏览器,这个文件应该按照XML规则进行解析。

但是,根元素到底叫<小纸条>还是<小便条>,则是由文档类型定义(DTD)或XML纲要(XML Schema)定义的。如果DTD规定根元素必须叫<小便条>,那么若写作<小纸条>就不符合要求。这种不符合DTD或XML纲要的要求的XML文档,被称作不合法的XML,反之则是合法的XML。

XML文件的第二行并不一定要包含文档元素;如果有注释或者其他内容,文档元素可以迟些出现。

最常见的PI(processing instruction,像XML序言, 却是不同类型的语法)是用来指定XML文件的样式表, 这个PI一般会直接放在XML序言之后,通常由Web浏览器使用,来将XML数据以特殊的样式显示出来。

XML的结构有一个缺陷,那就是不支持分帧(framing)。当多条XML消息在TCP上传输的时候,无法基于XML协议来确定一条XML消息是否已经结束。

解析

因为XML是一种树形结构的文档,它有两种标准的解析API:

DOM:一次性读取XML,并在内存中表示为树形结构; SAX:以流的形式读取XML,使用事件回调。

DOM

DOM是Document Object Model的缩写,DOM模型就是把XML结构作为一个树形结构处理,从根节点开始,每个节点都可以包含任意个子节点。 注意到最顶层的document代表XML文档,它是真正的“根”,而虽然是根元素,但它是document的一个子节点。

Java提供了DOM API来解析XML,它使用下面的对象来表示XML的内容:

  • Document:代表整个XML文档;
  • Element:代表一个XML元素;
  • Attribute:代表一个元素的某个属性。

DOM可在内存中完整表示XML数据结构; DOM解析速度慢,内存占用大。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
InputStream input = Main.class.getResourceAsStream("/book.xml");
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(input);

void printNode(Node n, int indent) {
    for (int i = 0; i < indent; i++) {
        System.out.print(' ');
    }
    switch (n.getNodeType()) {
    case Node.DOCUMENT_NODE: // Document节点
        System.out.println("Document: " + n.getNodeName());
        break;
    case Node.ELEMENT_NODE: // 元素节点
        System.out.println("Element: " + n.getNodeName());
        break;
    case Node.TEXT_NODE: // 文本
        System.out.println("Text: " + n.getNodeName() + " = " + n.getNodeValue());
        break;
    case Node.ATTRIBUTE_NODE: // 属性
        System.out.println("Attr: " + n.getNodeName() + " = " + n.getNodeValue());
        break;
    default: // 其他
        System.out.println("NodeType: " + n.getNodeType() + ", NodeName: " + n.getNodeName());
    }
    for (Node child = n.getFirstChild(); child != null; child = child.getNextSibling()) {
        printNode(child, indent + 1);
    }
}

SAX

SAX 是Simple API for XML的缩写,它是一种基于流的解析方式,边读取XML边解析,并以事件回调的方式让调用者获取数据。因为是一边读一边解析,所以无论XML有多大,占用的内存都很小。

SAX解析会触发一系列事件:

  • startDocument:开始读取XML文档;
  • startElement:读取到了一个元素,例如
  • characters:读取到了字符;
  • endElement:读取到了一个结束的元素,例如
  • endDocument:读取XML文档结束。

用SAX API解析XML,关键代码 SAXParser.parse() 除了需要传入一个InputStream外,还需要传入一个回调对象,这个对象要继承自DefaultHandler:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
InputStream input = Main.class.getResourceAsStream("/book.xml");
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser saxParser = spf.newSAXParser();
saxParser.parse(input, new MyHandler());


class MyHandler extends DefaultHandler {
    public void startDocument() throws SAXException {
        print("start document");
    }

    public void endDocument() throws SAXException {
        print("end document");
    }

    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        print("start element:", localName, qName);
    }

    public void endElement(String uri, String localName, String qName) throws SAXException {
        print("end element:", localName, qName);
    }

    public void characters(char[] ch, int start, int length) throws SAXException {
        print("characters:", new String(ch, start, length));
    }

    public void error(SAXParseException e) throws SAXException {
        print("error:", e);
    }

    void print(Object... objs) {
        for (Object obj : objs) {
            System.out.print(obj);
            System.out.print(" ");
        }
        System.out.println();
    }
}

可以使用栈结构保存元素名称,每遇到一个startElement()入栈,每遇到一个endElement()出栈,这样,读到characters()时才知道当前读取的文本是哪个节点的。可见,使用SAX API仍然比较麻烦。

  • SAX是一种流式解析XML的API;
  • SAX通过事件触发,读取速度快,消耗内存少;
  • 调用方必须通过回调方法获得解析过程中的数据。

Jackson

Jackson 能直接从XML文档解析成一个JavaBean https://github.com/FasterXML/jackson-dataformat-xml

1
2
3
4
// https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-xml
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.13.2'
// https://mvnrepository.com/artifact/com.fasterxml.woodstox/woodstox-core
implementation 'com.fasterxml.woodstox:woodstox-core:6.2.8'
1
2
3
4
5
6
7
InputStream input = Main.class.getResourceAsStream("/book.xml");
JacksonXmlModule module = new JacksonXmlModule();
XmlMapper mapper = new XmlMapper(module);
Book book = mapper.readValue(input, Book.class);
System.out.println(book.id);
System.out.println(book.name);
System.out.println(book.author);