ASE265

《第3章 XML》

XML:可扩展标记语言(Extensible Markup Language,XML)
XML的格式非常直观,与HTML文件非常相似,既可以表达层次结构,又使得重复的元素不会被曲解
XML和HTML格式都是古老的标准通用标记语言(Standard Generalized Markup Language,SGML)的衍生语言

XML文档应当以一个文档头开始,例如:

<?xml version="1.0">

或者

<?xml verison="1.0" encoding="UTF-8">

严格来说,文档头是可选的,但是强烈推荐使用文档头
文档头之后通常是文档类型定义(Document Type Definition,DTD),例如

<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems,Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd"\>

文档类型定义(DTD)是确保文档正确的一个机制,但是它不是必需的。
最后,XML文档的正文包含根元素,根元素包含其他元素。如上图中的<configuration>就是根元素
在<configuration>有其子元素<title>,<title>元素下有font元素,font含有两个子元素
另:在设计XML文档结构时,最好让元素要么包含子元素,要么包含文本,即应避免这种情况:

<font>
    Helvetica
    <size>36\<size\>
<font\>

在XML规范中,这叫做混合式内容,而且解析起来不是很方便,故不推荐这种混合式内容
当然,XML元素可以包含属性,例如

<size unit="pt">36</size>

那么何时使用属性,何时使用元素呢?
一条经验法则:属性只应该用来修改值的解释,而不是用来指定值
元素和文本是XML文档“主要的支撑者”,这里还有一些其他的标记:
字符引用;实体引用;
CDATA部分(CDATA Section)用<![CDATA[和]]>来限定其界限
处理指令;由来限定其界限,如<?xml veriosn="1.0"?>
注释;用<!和-->限定其界限

解析XML文档

要处理XML文档,就要解析它。Java库提供了两种XML解析器:树形解析器和流机制解析器
树形解析器向文档对象模型一样,将读入的XML文件转换为树结构
对于树形解析器来说,要读入一个XML文档,首先需要一个Document对象,可以从DocumentBuilderFactory中的得到这个对象,例如:

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

现在可以从文件中读入某个文档:

File f = ...
Document doc = builder.parse(f);

或者,可以用一个URL:

URL u = ...
Document doc = builder.parse(u);

可以通过调用getDocumentElement方法来启动对文档内容的分析,它将返回根元素。

Element root = doc.getDocumentElement();

而getTagName方法可以返回元素的标签名,
若要得到该元素的子元素可以使用getChildNodes方法,这个方法返回一个类型为NodeList的集合
getData方法可以获取存储在Text节点的字符串
getFirstChild得到第一项子元素
getLastChild得到最后一项子元素
getNextSibling得到下一个兄弟节点
而若要枚举所有子元素则比较麻烦:

NodeList children = root.getChildNodes();
// getLength()方法提供了项的总数,而item方法将得到指定索引值的项
for(int i=0; i\<children.getlength();i++)
{
    Node child = child.item(i);
    ...
}

假设,我们正在处理一下文档:

<font>
&emsp;<name>Helvetica</name>
&emsp;<size>36</size>
</font>

看起来font有两个元素,但解析器却会告诉我们有5个:

  • <font>和<name>之间的空白字符
  • name元素
  • </name>和<size>之间的空白字符
  • size元素
  • </size>和</font>之间的空白字符

其对应的DOM树为:

当然,如果只希望得到子元素,那么需要读child进行判断,即需要忽略掉空白字符
可通过这样的语句来判断:if(child instanceof Element)

显然,这样的遍历其实挺麻烦的,而如果你的文档有DTD,那么对于遍历的处理可以更简单
这时,解析器知道哪些元素没有文本节点的子元素,而且它会帮你剔除空白字符
并且这也进一步反映了DTD的重要性

验证XML文档

1.DTD

用于验证XML文档的有两样东西:DTD和XML Schema
DTD的作用是定义XML文档的结构,它使用一系列合法的元素来定义文档结构,因此通过验证DTD就能知道该XML是否遵守DTD的语法规则
提供DTD的方式有很多种。可以直接在XML文件内部定义,例如

<?xml version="1.0"?>
<!DOCTYPE configuration [
    <!ELEMENT configuration...>
    more rules
    ...
]>
<configuration>
    ...
</configuration>

这些规则被纳入到DOCTYPE的声明中,位于[...]的限定块中。
文档类型必须匹配根元素的名字,例如我们的例子configuration

在文档内部提供DTD不是很普遍,大多数都是把DTD存储在外部
使用SYSTEM声明可以实现这个目标,例如:

<!DOCTYPE configuration SYSTEM "config.dtd">

//声明了对外部文件config.dtd的引用
或者

<!DOCTYPE  configuration SYSTEM "http://myserver.com/config.dtd">

ELEMENT规则用于指定某个元素可以拥有怎么样的子元素,它可以指定一个正则表达式
用于元素内容的规则

一个例子:声明menu元素包含0个或者多个item元素:

<!ELEMENT menu (item)*>

还可以指定描述合法的元素属性的规则,其通用语法为:

<!ATTLIST element attribute type default>

属性类型

属性的默认值

两个典型的属性规格说明:

<!ATTLIST font style (plain|bold|italic|bold-italic) "plain">

描述font元素的style属性有4个合法的属性值,默认值为plain

<!ATTLIST size unit CDATA #IMPLIED>

描述size元素的unit属性可以包含任意的字符序列

如何使用DTD呢?首先,通知文档生成工厂打开验证特性

factory.setValidating(true);

这样,该工厂生成的是所有文档生成器都将根据DTD来验证它们的输入,验证的最大好处是可以忽略元素内容中的空白字符
调用下面的代码:

factory.setIgnoringElementContentWhiteSpace(true);

这样,生成器将不会报告文本节点中的空白字符,可以通过代码直接访问元素
例如对之前要遍历的文档来说可以直接写成:

NodeList children = root.getChildNodes();
Element nameElement = (Element)children.item(0);
Element sizeElement - (Element)children.item(1);

这就是DTD如此有用的原因。

2.XML Schema

XML Schema,可以看作基于XML的DTD代替者,而且XML Schema比起DTD语法要复杂得多
如果要在文档中引用Schema文件,需要在根元素中添加属性,例如:

<?xml version="1.0"?>
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    &emsp;xsi:noNameSpaceSchemaLocation="config.xsd">
  ...
configuration>

这个声明说明Schema文件config.xsd会被用来验证该文档。这是没用使用命名空间的例子
Schema为每个元素都定义了类型,类型可以是简单类型也可以是复杂类型
简单类型已被内建到了XML Schema内,包括:

xsd:string
xsd:int
xsd:boolean

而且支持定义自己的简单类型(simpleType)和复杂类型(complexType)
在定义元素时需要指定元素的类型:

<xsd:element name="name" type="xsd:string"/>
<xsd:elemnt name="size" type="xsd:int"/>
<xsd:elemnt name="style" type="StyleType"/>

如果要指定属性,就使用xsd:attribute,例如

<xsd:attribute name="unit" type="xsd:string" use="optional" default="cm"\/>

解析带有Schema的XML文件和带有DTD的文件相似,但有3点差别:

使用XPath来定位信息

若是想要定位某个XML文档中的一段特定信息,那么使用XPath是最好的方法。
假设我们要定位的文档如下:

<configuration>
    ...
    <database>
        <username>dbuser</username>
        <password>secret</password>
        ...
    </database>
</configuration>

Java SE 5.0 增加了一个API来计算XPath的表达式。首先,需要从XPathFactory创建一个XPath对象

XPathFactory xpFactory = XPathFactory.newInstance();
path = xpFactory.newXPath();

然后调用evaluate方法来计算XPath表达式

String username = path.evaluate("/configuration/database/username",doc); //doc指XML文档对象

执行该代码,即可获得XML文档中的<username>元素的文本内容
可以使用@操作符可以获得属性值
可以使用[]操作符来选择特定元素

使用命名空间

Java语言使用包来避免名字冲突,XML也有类似的命令空间机制,用于元素名和属性名
XML命令空间的特殊之处在于使用HTTP的URL(Uniform Resource Identifier)作为命令空间的标识符,比如:
http://www.w3.org/2001/XMLSchema
uuid:1c759aed-b748-475c-ab68-10679700c4f2
urn:com:books-r-us

若要打开命名空间处理特性,需要调用DocumentBuilderFactory类的setNameSpaceAware方法

factory.setNameSpaceAware(true);

这样该工厂产生的所有生成器都支持命名空间了。
假设解析器看到了以下元素:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"\>

则设置了命令空间的话,将会得到以下信息

流机制解析器

树形解析器会完整地读入XML文档,然后将其转换为一个树形的数据结构
而流解析器则不需要完整地读入XML文档,可以边读入变解析节点
Java类库提供的流机制解析器:老而弥坚的SAX解析器和添加到Java SE 6中的更现代化的StAX解析器

使用SAX解析器

如何得到SAX解析器?

SAXParseFactory factory = SAXParseFactory.newInstance();
SAXParse parser = factory.newSAXParse();
处理文档
parser.parser(source,handle)

source可以是一个文件、一个URL字符串或者是一个输入流
handler属于DefaultHandler的一个子类
Defaulthandler类为ContnetHandler、DTDHandler、EntityRedolver、ErrorHandler这四个接口定义了空的方法

在使用SAX解析器时,需要一个处理器来为各种解析器事件定义事件动作
ContentHandeler接口定义了若干个解析文档时解析器会调用的回调方法,比较重要的几个:

  • startElement和endElement在每当遇到起始或终止标签时调用
  • characters在每当遇到字符数据时调用
  • startDocument和endDocument分别在文档开始和结束时各调用一次

处理器必须覆盖这些方法,让它们在执行解析文件时我们想要让它们执行的动作

使用StAX解析器

StAX解析器是一种“拉解析器”,与SAX不同
StAx是使用基本循环来迭代所有的事件

生成XML文档

1.使用DOM树来产生XML文件

用文档的内容构建一颗DOM树,然后再写出该树的所有内容
要建立一棵DOM树,需要从一个空的文档开始
调用DocumentBuilder类的newDocument可以得到一个空文档

Document doc = builder.newDocument();

使用createElement方法可以构建文档里的元素

Element root = doc.createElement(rootName);
Element child = doc.createElement(childName);

使用createTextNode方法可以构建文本节点

Text text = doc.createTextNode(textContents);

使用以下方法可以给文档添加根元素,给父节点添加子节点

doc.append(root)
root.append(child)
child.append(text)

设置元素属性

root.setAttribute(name,value);

若是需要创建带命名空间的文档,需要将工厂设置为命名空间感知的
然后使用createElementNS来创建所有节点

2.使用StAX写出XML文档

StAx API使我们可以直接将XML树写出,这需要从某个OutputStream中构建一个XMLStreamWritter

XMLOoutputFactory factory = XMLOutputFactory.newInstance();
XMLStreamWriter writer = factory.createXMLStreamwriter(out);

要产生XML文件头,调用

writer.writeStartDocument();

然后调用

writer.writeStartDocument(name);

添加属性需要调用

writer.writeAttribute(name,value);

可以再次调用writeStarteElement添加新的子节点
或者输出字符

writer.writeCharacters(text);

在写完所有节点后,调用

writer.writeEndElement(name);

这会导致当前元素被关闭
若是输出没有子节点的元素,可以调用

writer.writeEmptyElement(name);

最后,在文档的结尾,调用

writer.writeEndDocument();

XSL转换

XSL转换机制可以指定将XML文档转换为其他格式的规则
例如转换为纯文本、XHTML或任何其他的XML格式。

分类:

技术点:

相关文章: