0%

Java 补丁-XML

概述

相对 HTML 的区别:

  • 大小写敏感,故 <h1> 和 <H1> 为不同 XML 标签
  • 不可省略结束标签
  • 没有结束标签的元素必须以 / 结尾
  • 属性值必须用引号包围,包括数值
  • 所有属性必须有属性值

标记

字符引用:

  • &#十进制 &#x十六进制

实体引用:

  • &name
  • &lt 小于;&gt 大于;&amp &;&quot 引号;可在 DTD 中定义其他实体引用。

CDATA Section:

  • **
  • 特殊的字符数据形式,囊括含有 < > & 等字符的字符串,而不必解释为标记。
  • <![CDATA[ & > are delimiters ]]>

处理指令:

  • **
  • 在处理 XML 的应用程序中使用。

注释:

  • **
  • 注释不应该含有 – 字符串。

规范

避免混合式内容,即元素包含子元素和文本。

属性只用于修改值的解释,而不是指定值。优先使用元素。例:

1
2
3
4
5
6
<font name="Helvetica" size="18 pt" />

<font>
<name>Helvetica</name>
<size unit="pt">36</size>
</font>

解析文档

首先从 DocumentBuilderFactory 得到 DocumentBuilder 对象:

1
2
DocumentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();

通过 builder 从文件,URL,输入流读入文档:

1
2
3
4
5
File f = ...;
URL u = ...;
InputStream in = ...;

Document doc = builder.parse(f);

若使用输入流读取,解析器无法定位以该文档为相对路径而被引用的文档。

调用 getDocumentElement 方法对文档内容分析,并返回根元素:

1
Element root = doc.getDocumentElement();

getTagName 方法可以返回元素的标签名。获得元素的子元素(元素,文本,注释,节点)使用 getChildNodes 方法,将返回一个 NodeList 类型的集合。item 方法得到指定索引值的项,getLength 方法获得项的总数。故枚举子元素:

1
2
3
4
5
NodeList children = root.getChildNodes();
for(int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
...
}

元素与元素之间的空白字符也算作子元素,忽略空白字符:

1
2
3
4
5
6
7
for(int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child instanceof Element) {
Element childElement = (Element) child;
...
}
}

获得元素后,从 Text 类型的子节点中获取文本字符串,若节点可定位(如 Text 节点是唯一的子元素),可使用 getFirstChild getLastChild 而不必遍历新的 NodeList ,然后用 getData 方法获取节点中的字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
for(int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child instanceof Element) {
Element childElement = (Element) child;
Text textNode = (Text) childElement.getFirstChild();
String text = textNode.getData().trim();
// trim 删除数据前后的空白字符
if (childElement.getTagName().equals("name"))
name = text;
else if (childElement.getTagName().equals("size"))
size = Interger.parseInt(text);
}
}

若要遍历子节点集(不是 getChildNodes 来的 NodeList),结合 getNextSibling 方法(获取下一个兄弟节点):

1
2
3
for (Node childNode = element.getFirstChild();
childNode != null;
childNode = childNode.getNextSibling()) { ... }

若要枚举节点的属性,调用 getAttributes 方法,其返回一个 NamedNodeMap 对象,包含了描述属性的 Node 对象。与遍历 NodeList 的方式相同,用 getNodeName getNodeValue 方法获取属性名和属性值:

1
2
3
4
5
6
7
8
9
NamedNodeMap attributes = element.getAttributes();
String value = element.getAttribute("unit");
// 知道属性名可以直接获取对应值
for (int i = 0; i < attributes.getLength(); i++) {
Node attribute = attributes.item(i);
String name = attribute.getNodeName();
String value = attribute.getNodeValue;
...
}

验证 XML 文档

DTD

若在 XML 文档内部提供 DTD,规则应当纳入到 DOCTYPE 声明的 [] 中,且 DOCTYPE 必须匹配根元素的名字,如:

1
2
3
4
<!DOCTYPE configuration [
<!ELEMENT configuration ...>
... ]>
<configuration></configuration>

更多是存储在外部,使用 SYSTEM 声明指定一个 DTD 文件:

1
2
<!DOCTYPE configuration SYSTEM "path">
<!DOCTYPE configuration SYSTEM "URL">

各种规则

ELEMENT 规则指定某个元素可以拥有的子元素,允许正则表达式。

规则 含义
E* 0 或多个 E
E+ 1 或多个 E
E? 0 或 1 个 E
E1|E2|…|En E1,E2… 中的一个
E1,E2,…,En E 按序排列
#PCDATA 文本
(#PCDATA|E1…|En)* 0 或多个文本且 E 以任意顺序排列
ANY 允许任何子元素
EMPTY 不允许有子元素

当元素需要包含文本时,只有两种合法情况:

  • 只包含文本 #PCDATA
  • 包含任意顺序的文本和标签 (#PCDATA|E1…|En)*

ATTLIST 规则指定元素属性,语法 <!ATTLIST element attribute type default>

类型 含义
CDATA 任意字符串
(A1|A2|…|An) 字符串属性为其中之一
NMTOKEN NMTOKENS 1 或多个名字标记
ID 1 个唯一的 ID
IDREF IDREFS 1 或多个对唯一 ID 的引用
ENTITY ENTITIES 1 或多个未解析的实体
默认值 含义
#REQUIRED 属性必需
#IMPLIED 属性可选
A 属性可选,若未指定则为 A
#FIXED A 属性必须为未指定或 A,解析器都报 A

例:

1
2
<!ATTLIST font style (plain|bold|italic) "plain">
<!ATTLIST size unit CDATA #IMPLIED>

使用

通知工厂启用验证,此工厂生成的所有文档生成器都将根据 DTD 验证输入:

1
factory.setValidating(true);

忽略元素节点之间的空白字符:

1
factory.setIgnoringElementContentWhitespace(true);

这样就不用再通过循环剔除空白字符了:

1
2
3
<!ELEMENT font (name,size)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT size (#PCDATA)>
1
2
Element name = (Element) children.item(0);
Element size = (Element) children.item(1);

XML Schema

占坑

XPath

概述

相比遍历 DOM 树,XPath 访问节点更加简单,通过对 /configration/database/username 求值获取 username 的值。

也可以描述节点集,如 /gridbag/row 描述根元素 gridbag 下的所有子元素 row,通过 [] 操作符选择元素:/gridbag/row[1] 表示第一行。

@ 操作符获取属性值:/gridbag/row[1]/cell[1]/@anchor 。

配合 XPath 的函数进行精细要求。

使用

首先从 XPathFactory 创建 XPath 对象:

1
2
XPathFactory xpfactory = XPathFactory.newInstance();
path = xpfactory.nextXPath();

然后用 evaluate 方法计算 XPath 表达式(用于获取文本):

1
String name = path.evaluate("/configuration/database/username", doc);

若 XPath 表达式产生了一组节点:

1
NodeList nodes = (NodeList) path.evaluate("/gridbag/row", doc, XPathConstants.NODESET);

若仅一个节点:

1
Node node = (Node) path.evaluate("/gridbag/row[1]", doc, XPathContants.NODE);

若是数字:

1
int num = ((Number) path.evaluate("count(/...)", doc, XPathConstants.NUMBER)).intValue();

从以前获得的节点开始:

1
result = path.evaluate(expression, node);

命名空间

概述

XML 使用 URL 标识命名空间,避免名字冲突。此处 URL 只用作标示字符串,而非某个文件定位符。

子元素也可以提供其命名空间:

1
2
3
4
5
<element xmlns="namespaceURL1"
chilren
<children xmlns="namespaceURL2">
</children>
</element>

设置前缀表示命名空间(xsd 为例):

1
2
3
<xsd:schema xmlns:xsd="URL">
<xsd:element ... />
</xsd:schema>

使用

默认 Java XML 库的 DOM 解析器关闭命名空间处理,使用 DocumentBuilderFactory 类的 setNamespaceAware 方法:

1
factory.setNamespaceAware(true);

该工厂产生的所有生成器都将支持命名空间:

  • 带前缀限定名 (例 xsd:schema) getNodeName getTagName

  • 命名空间 URI getNamespaceURI

  • 无命名空间或前缀的本地名 (例 schema) getLocalName

流机制解析器

DOM 解析器完整读入 XML 文档将其转换为树型的数据结构。若文档很大,或不需要看到完整结构,则相对低效。

SAX 解析器

DOM 解析器在 SAX 解析器的基础上构建,其在接收到解析器事件(SAX 解析输入数据时报告)时构建 DOM 树。而 SAX 由事件处理器建立数据结构,并且不在意元素的上下文环境。

ContentHandler 接口定义了部分解析器会调用的方法,如:

1
2
3
4
<font>
<name>xxx</name>
<size units="pt">36</size>
</font>
  • startElement - font
  • startElement - name
  • characters - xxx
  • endElement - name
  • ……

在处理器中覆盖这些方法以执行我们所希望的动作。

首先获得 SAX 解析器:

1
2
3
SAXParserFactory factory = SAXParserFactory.newInstance();
// factory.setNamespaceAware(true); 打开命名空间处理
SAXParser parser = factory.newSAXParser();

然后获得处理器,覆盖 startElement 方法为例,namespaceURI 和 lname 提供命名空间和本地非限定名,qname 以 prefix:localname 提供限定名:

1
2
3
4
5
6
7
DefaultHandler handler = new
DefaultHandler() {
public void startElement(String namespaceURI, String lname, String qname, Attributes attrs)
throw SAXException {
...
}
};

然后处理文档:

1
parser.parse(文件/字符串/输入流, handler);

StAX 解析器

不用管事件处理部分,用循环迭代所有事件。命名空间处理默认启用:

1
2
3
4
5
6
XMLInputFactory factory = XMLInputFactory.newInstance();
// factory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, false);
XMLStreamReader parser = factory.createXMLStreamReader(输入流);
while (parser.hasNext()) {
...
}

使用 XMLStreamReader 中的方法分析当前(parser.next)元素属性值,如:

1
2
String units = parser.getAttributeValue(null, "units");
// 获取当前元素 units 属性