《第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>
 <name>Helvetica</name>
 <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">
<!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"
 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点差别:
- 1.必须打开对命名空间的支持;factory.setNameSpaceAware(true);
- 2.必须通过如下的“魔咒”来准备好处理Schema的工厂
- final String JAXP_SCHEMA_LANGUAGE ="http://java.sun.com/xml/jaxp/properties/schemalanguage";
- final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
- factory.setAttribute(JAXP_SCHEMA_LANGUAGE,JAXP_SCHEMA_LANGUAGE);
- 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"\>
则设置了命令空间的话,将会得到以下信息
- 限定名 = xsd:scheama,可由getNodeName和getTagName方法返回
- 命令空间 URI = http://www.w3.org/2001/XMLSchema,可由getNamespaceURI方法返回
- 本地名 = schema,由getLocalName方法返回
流机制解析器
树形解析器会完整地读入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格式。