您的位置:首页 > 编程语言 > Java开发

XML Java核心技术 读书笔记

2014-03-28 09:05 393 查看
XML文档的结构

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

<?xml version="1.0"?>或

<?xml version="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">

文档类型定义是确保文档正确的一个重要机制,但是这不是必需的。

最后,XML文档的正文包含根元素根元素包含其他一些元素。如:

<?xml version="1.0"?>
<!DOCTYPE configuration ...>
<configuration>
<title>
<font>
<name>Helvetica</name>
<size>36</size>
</font>
</title>
...
</configuration>
注意:在设计XML文档结构时,最好使元素只包含子元素或只包含文本,也就是应该避免以下情况 :

<font>

Helvetica

<size>36</size>

</font>

在XML规范中,这叫混合式内容。如果避免了混合内容,可以简化解析过程。

XML元素可以包含属性,如<size unit="pt">36</size>

属性的灵活性比元素差,关于使用元素或属性的一个通常经验法则是,属性只应该在修改值的解释时使用,而不是在指定值时使用。

注意:在HTML中属性的使用规则很简:凡是不显示在网页上的都是属性,如<a href="http://java.sum.com">Java Technology</a>;然而,这个规则对于大多数XML并不那么管用。因为XML文件的数据并非像通常意义那样是让人浏览的。元素和文本是XML文档的主要要素,以下是你会遇到的其他一些标记的说明:

字符引用的形式是&#十进制值或&#x十六进制值。
实例引用的形式是&name。以下实例引用

<

>

&

"

'

它们表示:小于,大于 ,&,引号,省略号等字符。可以在DTD中定义其他的实体引用。

CDATA部分用<![ 和 ]]>来限定界限。它们是字符数据的一种特殊形式。你可以使用它们来包含那些含有<,>,&之类字符的字符串,而不必将它们解释为标记,如:<![ CDATA[ <&> are my favorite delimiters ] ]>,CDATA部分不能包含字符吕]]>。它常被用做将传统数据纳入XML文档的一种特殊方法。
处理指令是指那些专门在处理XML文档的应用程序中使用的指令,它们将用<? 和 ?>来限定其界限,例如:<?xml-stylesheet href="mystyle.css" type="text/css"?>

每个XML都以下一个处理指令开头:<?xml version="1.0"?>

注释用 <!- 和 -->限定其界限,例如:<!-- This is a comment. -->注释不能含有字符串--。注释只是为了给文档的读者提供信息,其中绝不含有隐藏的命令,命令是由处理指令来实现。

解析XML文档

Java库提供了两个XML解析器:

像文档对象模型(Document Object Model,DOM)解析器这样的树型解析器,它们将读入的XML文档转换成树结构。

像用于XML的简单API(Simple API for XML,SAX)解析器这样的流机制解析器,它们在读入XML文档时生成相应的事件。

DOM解析器对于实现我们的大多数目的都很容易。但如果处理很长的文档,使用它生成树结构将会消耗大量内存,或者如果你只是对于某些元素感兴趣,而不关心它们的上下文,那么你应该考虑使用流机制解析器。

DOM解析器的接口已经被W3C标准化了。Java中org.w3c.dom包中包含了接口类型的定义,如Document和Element等。可通过以下代码来获得Document:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder buider = factory.newDocumentBuilder();
File f = new File("...xml");
Document doc = buider.parse(f);


通过GetDocumentElement方法将返回文档根元素:

Element root = doc.getDocumentElement();

getChildNodes方法将返回一个类型为NodeList的集合,包含了所有的子元素。其中item方法将得到指定索引项,getLength方法则提供项的总数,以下代码枚举所有子元素:

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


注意:分析子元素要很仔细。如下面文档 :

<font>
<name>Helvetica</name>
<size>36</size>
</font>


你期望font有两个子元素,但解析器却报告有5个:

<font>和<name>之间的空白字符

name元素

</name>和<size>之间的空白字符

size元素

</size>和</font>之间的空白字符

如果只希望得到子元素,可以通过以下代码忽略空白字符:

NodeList children = root.getChildNodes();
for(int i = 0; i < children.getLength(); i++){
Node child = children.item(i);
if(child instanceof Element){
Element childElement = (Element)child;
...
}
}

如果你的文档在有DTD(下面会讲到),那么解析器会知道哪些元素没有文本节点子元素,而且它会帮你禁止空白字符。

也可以通过getFirstChild得到第一个子元素,用getNextSiblingt得到下一个兄弟节点,可用以下代码遍历子节点:

for(Node chiNode = root.getFirstChild(); chiNode != null; chiNode = chiNode.getNextSibling()){
...
}


当你分析name和size元素时,想检索到它们包含的文本字符串,而这些文本字符串本身包含在Text类型的子节点中。既然知道这些Text节点是唯一子元素,可以用getFirstChild方法而不用再遍历一个NodeList,然后可以用getData方法检索存储在Text节点中的字符串。

NodeList children = root.getChildNodes();
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();
...
}
}
注意:getData的返回值调用trim方法是个好主意,如下XML:

<size>
36
</size>
那么,解析器将会把所有的换行符和空格都包含到文本节点中去。调用trim方法可以把实际数据前后的空白字符删掉。

如果要枚举节点属性,可调用getAttributes方法,返回一个NamedNodeMap对象,其中包含描述属性的节点对象:

NamedNodeMap attributes = element.getAttributes();
for(int i = 0; i < attributes.getLength(); i++){
Node attribute = attributes.item(i);
String name = attribute.getNodeName();
String value = attribute.getNodeValue();
...
}
或者,如果知道属性名,则可以直接得到相应属性值:

String unit = element.getAttribute("unit");

验证XML文档

如果要规范文档结构,可以提供一个文档类型定义(DTD),DTD包含了用于解释文档是如何构成的规则 ,这些规则规范了每个元素的合法子元素和属性。如:

<!ELEMENT font(name,size)>

这个规则表明,一个font元素总是有两个子元素,分别是name和size。

具体DTD语法请参看Java核心技术或W3C。

对下一个XML文档 :

<?xml version="1.0"?>
<!DOCTYPE staff[
<!ELEMENT staff (employee)*>
<!ELEMENT employee (name,salary)>
<!ATTLIST employee nationality CDATA "china">
<!ELEMENT name (#PCDATA)>
<!ELEMENT salary (#PCDATA)>
]>
<staff>
<employee nationality="china">
<name>bin</name>
<salary>500</salary>
</employee>
<manager nationality="china">
<name>bin</name>
<salary>500</salary>
</manager>
</staff>
使用以下代码进行解析:

import java.io.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

public class XMLDTDStudy {
public static void main(String[] args) {
try{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(true);	//打开验证特性
factory.setIgnoringElementContentWhitespace(true);	//设置为匆略文本节点的空白字符
DocumentBuilder buider = factory.newDocumentBuilder();
File f = new File("staffWithDTD.xml");
Document doc = buider.parse(f);
Element root = doc.getDocumentElement();
NodeList children = root.getChildNodes();
for(int i = 0; i < children.getLength(); i++){
Node child = children.item(i);
System.out.println(i + ":  " + child.getNodeName());
}
}catch(ParserConfigurationException e){
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}catch(SAXException e){
e.printStackTrace();
}
}
}
得到结果如下:


程序报告了manager元素并没有在DTD中声明并忽略空白字符。

使用XPath定位信息

<?xml version="1.0"?>
<staff>
<employee nationality="china">
<name>bin</name>
<salary>500</salary>
</employee>
<employee>
<name>zhou</name>
<salary>800</salary>
</employee>
</staff>

XPath可以描述XML文档中的一组节点,如“/staff/employee”则描述了根元素staff的子元素中所有的employee元素。可以用[]操作符选择特定元素:“/staff/employee[1]”表示选择第一行(索引号从1开始)

使用@操作可以得到属性值,如:"/staff/employee[1]/@nationality"得到第一个员工的国籍china

XPath有很多有用的函数,如:"count(/staff/employee)"返回根元素staff的子元素中employee元素的数量。

Java SE5.0增加了一个API计算XPath表达式,先从XPathFactory对象创建一个XPath对象。

XPathFactory xpfactory = XPathFactory.newInstance();
XPath path = xpfactory.newXPath();

然后,调用evaluate方法计算XPath对象表达 :

String name = path.evaluate("/staff/employee[1]/salary", doc);
如果XPath表达式产生一组节点,则如下调用:

NodeList nodes = (NodeList)path.evaluate("/staff/employee", doc,XPathConstants.NODESET);
如果结果只有一个节点,则如下调用:

Node node = (Node) path.evaluate("/staff/employee[1]", doc,XPathConstants.NODE);
如果结果是一个数字,则使用

int salary = ((Number) path.evaluate("/staff/employee[1]/salary", doc,XPathConstants.NUMBER)).intValue();
不必从文档的根节点开始搜索,可以从任意一个节点或节点列表开始,如果你有前一个计算得到的一个节点node,就可以调用:

result = path.evaluate(expression,node);

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder buider = factory.newDocumentBuilder();
File f = new File("staff.xml");
Document doc = buider.parse(f);

XPathFactory xpfactory = XPathFactory.newInstance();
XPath path = xpfactory.newXPath();
NodeList nodes = (NodeList)path.evaluate("/staff/employee", doc,XPathConstants.NODESET);
for(int i = 0; i < nodes.getLength(); i++){
System.out.println(path.evaluate("name", nodes.item(i)));	//输出每个员工的名字
}

System.out.println(path.evaluate("/staff/employee[1]/salary", doc));	//输出第一个员工国籍
System.out.println(path.evaluate("count(/staff/employee)", doc));	//得到员工总数
int salary = ((Number) path.evaluate("/staff/employee[1]/salary", doc,XPathConstants.NUMBER)).intValue();	//得到第一个员工的工资

} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
catch(XPathExpressionException e){
e.printStackTrace();
}


使用命名空间

Java语言使用包来避免名字冲突。XML也有类似的命名空间机制 ,用于元素名和属性名。

名字空间是由统一资源标识符(URI)来标识,如:http://www.w3.org/2001/XMLSchema

下面是一个典型例子:

<xsd:shecma xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="gridbag" type="GridBagType">
...
</xsd>
下面的属性:xmlns:alias="namespaceURI"用于定义命名空间和别名,上面例子中别名为xsd。这样,xsd:schema实际上指的是“命名空间http://www.w3.org/2001/XMLSchema中的schema”

注意:只有子元素继承了它们父元素的命名空间,而不带显式别名前缀的属性不是命名空间的一部分,如:

<configuration xmlns="http://www.horstmann.com/corejava"
xmlns:si="http://www.bipm.fr/enus/3_SI/si.html">
<size value="210" si:unit="mm"/>
...
</configuration>
在这个示例中,元素configuration和size是URI http://www.horstmann.com/corejava的命名空间的一部分。属性si:unit是URI http://www.bipm.fr/enus/3_SI/si.html命名空间的一部分,然而,属性值不是任何命名空间的一部分。

默认地,Sun公司的DOM解析器是关闭了命名空间处理特性的。要打开命名空间处理特性,可以调用DocumentBuilderFactory类的setNamespaceAware方法:

factory.setNamespaceAware(true);

这样工作生产的所有生成器都支持命名空间了。每个节点有三个属性:

带有别名前缀的限定名,由getNodeName和getTagName等方法返回。
命名空间URI,由getNamescapceURI方法返回
不带别名前缀和命名空间的本地名,由getLocalName方法返回。

如解析以下元素:

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

会得到:

限定名为xsd:shecma
命名空间URI为http://www.w3.org/2001/XMLSchema
本地名为shecma

注意:如果命名空间特性被关闭,getLocalName和getNamespaceURI方法将返回null。

如果XPath要解析有命名空间的XML,还需要一些工作,

首先将XML内容修改为:

<?xml version="1.0"?>
<xsd:staff xmlns:xsd="http://www.test">
<xsd:employee nationality="china">
<name>bin</name>
<salary>500</salary>
</xsd:employee>
</xsd:staff>


再实现一个NamespaceContext接口,它做的工作是将文档中提取命名空间:

import java.util.Iterator;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import org.w3c.dom.Document;

public class UniversalNamespaceResolver implements NamespaceContext {
private Document sourceDocument;

public UniversalNamespaceResolver(Document document) {
sourceDocument = document;
}

public String getNamespaceURI(String prefix) {
if (prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) {
return sourceDocument.lookupNamespaceURI(null);
} else {
return sourceDocument.lookupNamespaceURI(prefix);
}
}

public String getPrefix(String namespaceURI) {
return sourceDocument.lookupPrefix(namespaceURI);
}

public Iterator getPrefixes(String namespaceURI) {
// not implemented yet
return null;
}
}


测试代码如下:

import java.io.*;
import javax.xml.parsers.*;
import javax.xml.xpath.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

public class DomStudy {

public static void main(String[] args) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
try {
DocumentBuilder buider = factory.newDocumentBuilder();
File f = new File("staff.xml");
Document doc = buider.parse(f);

XPathFactory xpfactory = XPathFactory.newInstance();
XPath path = xpfactory.newXPath();
path.setNamespaceContext(new UniversalNamespaceResolver(doc));	//设置XPath的命名空间

NodeList nodes = (NodeList)path.evaluate("/xsd:staff/xsd:employee", doc,XPathConstants.NODESET);
for(int i = 0; i < nodes.getLength(); i++){
Node node = nodes.item(i);
System.out.println(node.getNamespaceURI());
System.out.println(node.getLocalName());
System.out.println(node.getNodeName());
}
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
catch(XPathExpressionException e){
e.printStackTrace();
}
}
}
得到结果:
http://www.test
employee

xsd:employee

流机制解析器

当XML文档很大时,并且处理算法非常简单,可能在运行时解析节点,而不必看到所有的树形结构时,使用DOM可能显得效率低下,这时,应使用流机制解析器。

SAX解析器

SAX解析器在解析XML输入的构件时就报告事件,但不会以任何方式存储文档,而由事件处理器处理数据。实际上,DOM解析器是在SAX解析器的基础上建立起来的,它在接收到解析器事件时建立DOM树。

在使用SAX解析器,需要一个处理器来定义不同的解析器事件的事件动作,ContentHandler接口定义了若干个回调方法,下面是最重要的几个:

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

如解析如下片断时:

<font>
<size units="ps">36</size>
</font>
解析器确保产生以下调用:

1.startElement ,元素名:font

2.startElement, 元素名:size , 属性:units="pt"

3.characters, 内容:36

4.endElement, 元素名:size

5.endDocument, 元素名:font

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

注意:与DOM解析器一样,命名空间处理特性默认关闭。

如果使用下面代码处理上面带有命名空间的staff.xml

import java.io.*;
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;

public class SAXStudy {
public static void main(String[] args) {
DefaultHandler handler = new DefaultHandler(){	//定义一个DefaultHandler,并覆盖startElement方法,输出相关信息
public void startElement(String uri,  String localName,
String qName,Attributes attributes)throws SAXException{
System.out.println("URI:" + uri + "  LocalName:" + localName + "   qName:" + qName );
}
};

SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
try{
SAXParser saxParser = factory.newSAXParser();
InputStream in = new FileInputStream("staff.xml");
saxParser.parse(in, handler);
in.close();
}
catch(ParserConfigurationException e){
e.printStackTrace();
}
catch(SAXException e){
e.printStackTrace();
}
catch(FileNotFoundException e){
e.printStackTrace();
}
catch(IOException e){
e.printStackTrace();
}
}
}


使用StAX解析器

StAX解析器是一种“拉解析器(pull parser)”,与安装事件处理器不同,只需要使用下面这样的基本循环来迭代所有的事件:

InputStream in = new FileInputStream("staff.xml");
XMLInputFactory factory  = XMLInputFactory.newFactory();
XMLStreamReader parser = factory.createXMLStreamReader(in);
while(parser.hasNext()){
int event = parser.next();
call parser methods to obtain event details
}
如解析以下片断:

<font>
<size units="ps">36</size>
</font>


解析器将产生下面的事件:

1.START_ELEMENT, 元素名:font

2.CHARACTERS, 内容:空白字符

3.START_ELEMENT, 元素名:size

4.CHARACTERS, 内容:36

5.END_ELEMENT, 元素名:size

6.CHARACTERS, 内容:空白字符

7.END_ELEMENT, 元素名:font

下面是一个实现:

import java.io.*;
import javax.xml.namespace.QName;
import javax.xml.stream.*;

public class StAXTest {

public static void main(String[] args) {
try{
InputStream in = new FileInputStream("staff.xml");
XMLInputFactory factory  = XMLInputFactory.newFactory();
XMLStreamReader parser = factory.createXMLStreamReader(in);
while(parser.hasNext()){
int event = parser.next();
if(event == XMLStreamConstants.START_ELEMENT){
QName qname = parser.getName();
System.out.println(qname.toString());
}
}
}
catch(FileNotFoundException e){
e.printStackTrace();
}
catch(XMLStreamException e){
e.printStackTrace();
}
}
}


生成XML文档

通过调用DocumentBuilder类的newDocument方法得到一个空文档:

Document doc = builder.newDocument();

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

Element rootElement = doc.createElement(rootName);

Element childElement = doc.createElement(childName);

使用createTextNode方法构建文本节点:

Text textNode = doc.createTextNode(textContents);

使用以下方法给文档加上根元素,给父结节加上子节点:

doc.appendChild(rootElement);

rootElement.appendChild(childElement);

childElement.appendChild(textNode);

调用Element类的setAttribute方法设置元素属性:

rootElement.setAttrbute(name,value);

将doc输出到文件中,可以使用Transformer类,通过transform输出doc树

import java.io.File;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.*;

public class WriteXML {
public static void main(String[] args) {
try{
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = builder.newDocument();

Element staff = doc.createElementNS("http://www.test", "xsd:staff");	//创建根结点
Element employee = doc.createElement("xsd:employee");	//创建employee结点

Element nameElem = doc.createElement("name");	//创建name结点
Text nameText = doc.createTextNode("bin");	//创建文本结点
Element salaryElem = doc.createElement("salary");
Text salaryText = doc.createTextNode("500");

//将结果组织到doc树中
doc.appendChild(staff);
staff.appendChild(employee);
employee.appendChild(nameElem);
nameElem.appendChild(nameText);
employee.appendChild(salaryElem);
salaryElem.appendChild(salaryText);

//将doc树输出到文件中
Transformer t = TransformerFactory.newInstance().newTransformer();
//设置输出格式
t.setOutputProperty(OutputKeys.METHOD,"xml");
t.setOutputProperty(OutputKeys.INDENT, "yes");

File f = new File("writerStaff.xml");
t.transform(new DOMSource(doc), new StreamResult(f));
}
catch(ParserConfigurationException e){
e.printStackTrace();
}
catch(TransformerConfigurationException e){
e.printStackTrace();
}
catch(TransformerException e){
e.printStackTrace();
}
}
}


使用StAX写XML文档

StAX API使我们可以直接将XML树写出,先构建一个XMLStreamWriter:

XMLOutputFactory factory = XMLOutputFactory.newInstance();

XMLStreamWriter writer = factory.createXMLStreamWriter(out);

要产生XML文件头,调用:

writer.writeStartDocument();

然后要产生元素则调用 :

writer.writeStartElement(name);

添加属性需要调用:

writer.writeAttribute(name,value);

写出字符则调用:

writer.writeCharacters(text);

要写出没有子节点的元素可调用:

writer.writeEmptyElement);

在添加完所有子节点后,调用:

writer.writeEndElement();

这会导致当前元素被关闭

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

writer.writeEndDocument();

调用将关闭所有的元素。

注意:与使用DOM/XSLT方式一样,不必担心属性值和字符数据的转义字符。并且,StAX当前的版本还没有任何对产生缩进输出的支持。

下面是一个实例:

import java.io.*;
import javax.xml.stream.*;

public class StAXWriterXML {
public static void main(String[] args) {
try{
FileOutputStream out = new FileOutputStream("StAXWriterStaff.xml");
XMLOutputFactory factory = XMLOutputFactory.newInstance();
XMLStreamWriter writer = factory.createXMLStreamWriter(out);

writer.writeStartDocument();
writer.writeStartElement("staff");
writer.writeStartElement("employee");
writer.writeStartElement("name");
writer.writeCharacters("bin");
writer.writeEndElement();
writer.writeEndElement();
writer.writeEndDocument();

writer.close();
out.close();
}
catch (XMLStreamException e) {
e.printStackTrace();
}
catch(FileNotFoundException e){
e.printStackTrace();
}
catch(IOException e){
e.printStackTrace();
}
}
}


XSL转换

XSL转换 机制可以指定将XML文档转换为其他格式的规则,例如,纯文本,XHTML或其他任何XML格式。XSLT通常用于将一个机器可读的XML格式转译为另一种机器可读的XML格式,或者将XML转译为适于人类阅读的表示格式。

具体方法请参看Java核心技术。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: