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文档的正文包含根元素根元素包含其他一些元素。如:
<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文档的主要要素,以下是你会遇到的其他一些标记的说明:
字符引用的形式是十进制值或十六进制值。
实例引用的形式是&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:
通过GetDocumentElement方法将返回文档根元素:
Element root = doc.getDocumentElement();
getChildNodes方法将返回一个类型为NodeList的集合,包含了所有的子元素。其中item方法将得到指定索引项,getLength方法则提供项的总数,以下代码枚举所有子元素:
注意:分析子元素要很仔细。如下面文档 :
你期望font有两个子元素,但解析器却报告有5个:
<font>和<name>之间的空白字符
name元素
</name>和<size>之间的空白字符
size元素
</size>和</font>之间的空白字符
如果只希望得到子元素,可以通过以下代码忽略空白字符:
如果你的文档在有DTD(下面会讲到),那么解析器会知道哪些元素没有文本节点子元素,而且它会帮你禁止空白字符。
也可以通过getFirstChild得到第一个子元素,用getNextSiblingt得到下一个兄弟节点,可用以下代码遍历子节点:
当你分析name和size元素时,想检索到它们包含的文本字符串,而这些文本字符串本身包含在Text类型的子节点中。既然知道这些Text节点是唯一子元素,可以用getFirstChild方法而不用再遍历一个NodeList,然后可以用getData方法检索存储在Text节点中的字符串。
如果要枚举节点属性,可调用getAttributes方法,返回一个NamedNodeMap对象,其中包含描述属性的节点对象:
String unit = element.getAttribute("unit");
验证XML文档
如果要规范文档结构,可以提供一个文档类型定义(DTD),DTD包含了用于解释文档是如何构成的规则 ,这些规则规范了每个元素的合法子元素和属性。如:
<!ELEMENT font(name,size)>
这个规则表明,一个font元素总是有两个子元素,分别是name和size。
具体DTD语法请参看Java核心技术或W3C。
对下一个XML文档 :
程序报告了manager元素并没有在DTD中声明并忽略空白字符。
使用XPath定位信息
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);
使用命名空间
Java语言使用包来避免名字冲突。XML也有类似的命名空间机制 ,用于元素名和属性名。
名字空间是由统一资源标识符(URI)来标识,如:http://www.w3.org/2001/XMLSchema
下面是一个典型例子:
注意:只有子元素继承了它们父元素的命名空间,而不带显式别名前缀的属性不是命名空间的一部分,如:
默认地,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内容修改为:
再实现一个NamespaceContext接口,它做的工作是将文档中提取命名空间:
测试代码如下:
http://www.test
employee
xsd:employee
流机制解析器
当XML文档很大时,并且处理算法非常简单,可能在运行时解析节点,而不必看到所有的树形结构时,使用DOM可能显得效率低下,这时,应使用流机制解析器。
SAX解析器
SAX解析器在解析XML输入的构件时就报告事件,但不会以任何方式存储文档,而由事件处理器处理数据。实际上,DOM解析器是在SAX解析器的基础上建立起来的,它在接收到解析器事件时建立DOM树。
在使用SAX解析器,需要一个处理器来定义不同的解析器事件的事件动作,ContentHandler接口定义了若干个回调方法,下面是最重要的几个:
startElement和endElement在每当遇到起始事终止标签时调用
characters每当遇到字符数据时调用
startDocument和endDocument分别在文档开始和结束各调用一次。
如解析如下片断时:
1.startElement ,元素名:font
2.startElement, 元素名:size , 属性:units="pt"
3.characters, 内容:36
4.endElement, 元素名:size
5.endDocument, 元素名:font
处理器必须覆盖这些方法,让它们执行在解析文件时想要执行的动作。
注意:与DOM解析器一样,命名空间处理特性默认关闭。
如果使用下面代码处理上面带有命名空间的staff.xml
使用StAX解析器
StAX解析器是一种“拉解析器(pull parser)”,与安装事件处理器不同,只需要使用下面这样的基本循环来迭代所有的事件:
解析器将产生下面的事件:
1.START_ELEMENT, 元素名:font
2.CHARACTERS, 内容:空白字符
3.START_ELEMENT, 元素名:size
4.CHARACTERS, 内容:36
5.END_ELEMENT, 元素名:size
6.CHARACTERS, 内容:空白字符
7.END_ELEMENT, 元素名:font
下面是一个实现:
生成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树
使用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当前的版本还没有任何对产生缩进输出的支持。
下面是一个实例:
XSL转换
XSL转换 机制可以指定将XML文档转换为其他格式的规则,例如,纯文本,XHTML或其他任何XML格式。XSLT通常用于将一个机器可读的XML格式转译为另一种机器可读的XML格式,或者将XML转译为适于人类阅读的表示格式。
具体方法请参看Java核心技术。
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文档的主要要素,以下是你会遇到的其他一些标记的说明:
字符引用的形式是十进制值或十六进制值。
实例引用的形式是&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核心技术。
相关文章推荐
- 读书笔记——JAVA核心技术-卷II
- Java 核心技术-卷2----第2章 XML
- Java 核心技术读书笔记
- JAVA 多现程编程核心技术读书笔记
- java 核心技术卷1读书笔记
- Java多线程编程核心技术读书笔记(1)-Thread基础
- Java核心技术读书笔记
- Java核心技术-读书笔记 概览
- 读书笔记——Java多线程编程核心技术
- Java核心技术读书笔记
- Get方法中不要返回可变对象的引用 【Java2核心技术第一卷 读书笔记】
- [读书笔记]java核心技术
- java内部类总结 Java核心技术读书笔记
- 【笔记】Java核心技术 卷1 基础知识 第一章 读书笔记
- 多线程编程核心技术读书笔记(一):Java多线程技能
- Java多线程编程核心技术读书笔记(3)-线程通信
- java 网络 socket编程 Java核心技术读书笔记
- Java核心技术 卷1 读书笔记 (3 Java基本程序设计结构)
- 《Java 核心技术卷一》读书笔记
- Java核心技术(卷I)读书笔记 第七~九章