XML详解:第三部分 XML解析

XML详解:第三部分 XML解析
声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将追究法律责任!原文链接:http://www.cnblogs.com/jiangzhengjun/p/4290918.html

XML解析

如果不同厂商开发的XML解析器提供的API都依赖于自向的产品,那么应用程序要使用解析器,就只能使用特定于厂商的API。假如以后应用程序需要更换解析器,那么就只能重新编写代码了。庆幸的是,目前几乎所有的解析器都对两套标准的API提供了支持,这两套API就是DOM和SAX。

DOM(Document Object Model)的缩写,即文档对象模型,是W3C组织推荐的处理XML的标准接口。SAX是Simple API for XML的缩写,它不是某个官方机构的标准,但它是XML社区事实上的标准。虽然只是“民间”的标准,但是它在XML中的应用不比DOM少,几乎所有XML解析器都支持它。

DOM和SAX只是定义了一些接口,以及某些接口的默认实现(什么事情也不做),一个应用程序要想利用DOM或SAX访问XML文档,还需要一个实现了DOM或SAX的解析器,即实现DOM和SAX中定义的接口,提供DOM和SAX定义的功能。

Apache的Xerces是一个使用非常广泛的解析器,它提供了DOM和SAX的调用接口。SAX的标准解析接口为org.xml.sax.XMLReader,而Xerces中提供的解析接口的实现类为org.apache.xerces.parsers.SAXParser,在应用程序中可以采用如下方式访问XML文档:

org.xml.sax.XMLReader sp = new org.apache.xerces.parsers.SAXParser();

现有一个问题,虽然我们使用的是标准的DOM和SAX接口,但不同解析器的实现类是不同的,如果要使用另外一种解析器,仍然需要修改应用程序,只不过修改的代码量较少。有没有办法让我们在更换解析器时,不用对已发布程序做任何改变呢?JAXP API可以帮我们实现这一点。为了屏蔽具体厂商的实现,让开发人员以一种标准的方式对XML进行编程,SUN制定了JAXP(Java API for XML Processing)规范。JAXP没有提供解析XML的新方法,也没有为XML的处理提供新的功能,它只是在解析器之上封闭了一个抽象层,允许开发人员以独立于厂商的API调用访问XML数据。

JAXP开发包由javax.xml包及其子包、org.w3c.dom包及其子包、org.xml.sax包及其子包组成。在javax.xml.parsers包中,定义了几个工厂类,用于加载DOM和SAX的实现类,JAXP由接口、抽象类和一些辅助类组成,符合JAXP规范的解析器实现其中的接口和抽象类,开发人员只要使用JAXP的API编程,底层的解析器就可以任意更换。

应用程序àJAXP的接口与抽象类àXerces的JAXP实现àXerces的DOM或SAX解析器

有了JAXP,开发人员就可以随意更换解析器的实现,而不需要修改应用程序。

使用DOM解析XML

DOM是独立于程序语言的,W3C组织以IDL(接口中定义语言)的形式定义了DOM中接口。某种语言要实现DOM,需要将DOM接口转换为本语言中的对应结构。W3C提借了Java和ECMAScript这两种语言的实现。

Node对象是DOM结构中最基本的对象,代表了文档树中的一个节点,通常直接使用Node很少,一般使用Document、Element、Attr、Text等Node对象的子对象来操作文档。虽然在Node接口中定义了对节点进行操作的通用方法,但是有一些Node对象的子对象,如Text对象并不存在子节点。

XML详解:第三部分 XML解析

1、  Node接口主要是对它的子节点进行增、删、获取。

2、  Node接口中定义了各种节点的类型常量,可以用它们来判断是哪种节点。

3、  void normalize():将该节点所有的后代文本节点,包括属性节点,调整为规范化的形式,这仅是以结构(如:元素、注释、处理指令、CDATA段、实体引用)来分隔文本节点,也就是说,在节点所在的这棵树下,既不存在相邻的文本节点,也不存在空的文本节点。

Node节点的getNodeName、getNodeValue 和 getAttributes 的值将根据以下节点类型的不同而不同。

Interface(节点类型)

getNodeName(节点名字)

getNodeValue(节点值)

getAttributes(节点属性)

Attr

与 Attr.name 相同

与 Attr.value 相同

null

CDATASection

"#cdata-section"

与 CharacterData.data 相同,CDATA 节的内容

null

Comment

"#comment"

与 CharacterData.data 相同,该注释的内容

null

Document

"#document"

null

null

DocumentFragment

"#document-fragment"

null

null

DocumentType

与 DocumentType.name 相同

null

null

Element

与 Element.tagName 相同

null

NamedNodeMap

Entity

entity name

null

null

EntityReference

引用的实体名称

null

null

Notation

notation name

null

null

ProcessingInstruction

与 ProcessingInstruction.target 相同

与 ProcessingInstruction.data 相同

null

Text

"#text"

与 CharacterData.data 相同,该文本节点的内容

null

DOM树中的节点类型

XML中最常见的节点类型是:文档、元素、文本和属性节点,在DOM API中对应的接口是Document、Element、Text和Attr。

文档节点Document

文档节点是文档树的根节点,也是文档中其他所有节点的父节点。要注意的是,文档节点并不是XML文档的根元素,因为在XML文档中,处理指令、注释等内容可以出现在根元素以外,所以在构造DOM树时,根元素并不适合作为根节点,于是就有了文档节点,而根元素则作为文档节点的子节点。XML详解:第三部分 XML解析

4、  通过 Element getDocumentElement()方法得到XML文档的根元素。

5、  通过createXX相应的方法创建不同类型的节点。

6、  Element getElementById(String elementId):通过给出的ID类型的属性值elementId来查找对应用的元素。一个ID类型的属性值唯一标识了XML文档中的一个元素。除非特别定义,名字为“ID”或者“id”的属性,其类型并不是ID类型。

7、  NodeList getElementsByTagName(String tagname):以文档顺序返回标签名字为tagname的所有元素。如果参数为“*”,则返回所有的元素。

8、  NodeList getElementsByTagNameNS(String namespaceURI,String localName):按照指定的名称空间URI和元素的本地名返回所有匹配的元素。如果参数namespaceURI为“*”,则表示所有的名称空间,同样,如果localName为“*”,则匹配所有元素。

元素节点Element

XML详解:第三部分 XML解析

文本节点Text

XML详解:第三部分 XML解析

属性节点Attr

属性实际上是属于某个元素的,所以属性节点不是元素的子节点。因而在DOM中,属性节点没有被作为文档树的一部分,所以在属性节点上调用getParentNode、getPreviousSibling、getNextSibling时返回null。

XML详解:第三部分 XML解析

NodeList接口

XML详解:第三部分 XML解析

Node

NodeList getChildNodes();

Document、Element

NodeList getElementsByTagName(String tagname);

NodeList getElementsByTagNameNS(String namespaceURI, String localName);

NamedNodeMap接口

XML详解:第三部分 XML解析

Node

NamedNodeMap getAttributes();

DocumentType

NamedNodeMap getEntities();

NamedNodeMap getNotations();

DOM解析器工厂和DOM解析器

在javax.xml.parsers包中,定义了DOM解析器工厂类DocumentBuilderFactory,用来产生DOM解析器。DocumentBuilderFactory工厂类是一个抽象类,在这个类中提供了一个静态方法newInstance()方法,用来创建一个工厂类的实例。

DocumentBuilderFactory是个抽象类,那它的newInstance()产生的是哪个实现呢?采用JAXP编程可以任意更换解析器的关键就在于这个工厂类。DocumentBuilderFactory抽象类的实现由遵照JAXP规范的解析器提供商来给出的。解析器提供商为自身的解析器编写一个从DocumentBuilderFactory类继承的工厂类,然后由这个工厂类实例负责产生解析器对象。

那么DocumentBuilderFactory的newInstance()方法是如何找到解析器提供商给出的工厂类呢?可通过下面3种途径依次查找解析器工厂类:

1、  首先查看是否设置了javax.xml.parsers.DocumentBuilderFactory系统属性。这又可通过两种方式来设置这个系统属性:

System.setProperty("javax.xml.parsers.DocumentBuilderFactory",

"org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");

建议不要使用此种方式,因为如果更换解析器,程序就要修改。另一种是在用java.exe执行程序时,通过-D选项来设置系统属性:

java -Djavax.xml.parsers.DocumentBuilderFactory=oracle.xml.jaxp.JXDocument DOMTest

2、  查找JRE目录中lib子目录下的jaxp.properties文件,如果存在,则读取该配置文件:

javax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl

3、  在classpath环境变量所指定JAR文件中,查找META-INF/services目录下的javax.xml.parsers.DocumentBuilderFactory文件,使用这个文件中所指定的工厂类名来构造工厂的实例,这种方式被多数解析器提供商所采用,在他们的发布的包包含解析器的JAR包中,往往会提供这个的文件,我们来看看Apache提供的解析器实现包xercesImpl.jar:

XML详解:第三部分 XML解析

其中javax.xml.parsers.DocumentBuilderFactory文件的内容如下:

org.apache.xerces.jaxp.DocumentBuilderFactoryImpl

如果上面3种途径都没有找到解析器工厂类,就使用平台默认的解析器工厂。从JAXP1.2开始,SUN公司对Apache的Xerces解析器重新包装了一下,并将org.apache.xerces包名改为了com.sun.org.apache.xerces.internal,然后在JAXP的开发包中一起提供,作为默认的解析器。

在得到工厂实例后,就可以通过DocumentBuilderFactory的newDocumentBuilder()方法来创建DOM解析器实例了:

DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();

DocumentBuilder db = dbf.newDocumentBuilder();//得到具体厂商的XML解析器

Document d = db.parse("text.xml");//通过解析器解析得到文档对象

DocumentBuilderFactory另外几个重要的方法:

l  public void setValidating(boolean validating):指定解析器是否难被解析的文档,默认为false。注,该方法只能DTD验证有效。如果使用Schema验证,则可设为false后,使用setSchema(Schema)方法将一个模式与解析关联。

l  public void setSchema(Schema schema):如果要使用模式对XML文档进行验证,需要使用该方法。Schema对象由javax.xml.validation.SchemaFactory工厂类创建。

l  public void setIgnoringElementContentWhitespace(boolean whitespace):是否要忽略元素内容中的空白。默认是false。按照XML1.0的推荐标准,元素内容中的空白必须由解析器保留,但当根据DTD进行验证时,解析器可以知道文档的特定部分不会支持空格(如具有元素型内容的元素),所以这一区域的任何空格都“可忽略的”。注,这一标志只有通过setValidating打开验证功能后才有效。

使用DOM解析XML文档的实例

--students.xml

<?xml version="1.0" encoding="GB2312"?>

<?xml-stylesheet type="text/xsl" href="students.xsl"?>

<students>

<student sn="01">

<name>张三</name>

</age>

</student>

<student sn="02">

<name>李四</name>

</age>

</student>

</students>

--end

遍历文档

publicclass DOMPrinter {

/**

* 输出节点的类型、名字和值。

*/

publicstaticvoid printNodeInfo(String nodeType, Node node) {

System.out.println(nodeType + "\t" + node.getNodeName() + " : "

+ node.getNodeValue());

}

/**

* 采用递归调用,输出给定节点下的所有后代节点。

* 注:为了简单起见,只对处理指令节点、元素节点、

* 属性节点和文本节点进行了处理。

*/

publicstaticvoid traverseNode(Node node) {

short nodeType = node.getNodeType();

switch (nodeType) {

case Node.PROCESSING_INSTRUCTION_NODE:

printNodeInfo("处理指令", node);

break;

case Node.ELEMENT_NODE:

printNodeInfo("元素", node);

NamedNodeMap attrs = node.getAttributes();

int attrNum = attrs.getLength();

for (int i = 0; i < attrNum; i++) {

Node attr = attrs.item(i);

printNodeInfo("属性", attr);

}

break;

case Node.TEXT_NODE:

printNodeInfo("文本", node);

break;

default:

break;

}

Node child = node.getFirstChild();

while (child != null) {

// 递归调用

traverseNode(child);

child = child.getNextSibling();

}

}

publicstaticvoid main(String[] args) {

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

try {

DocumentBuilder db = dbf.newDocumentBuilder();

Document doc = db.parse(new File("students.xml"));

// Document接口提供了三个方法,分别用于获取XML声明的三个部分:

// 版本、文档编码、独立文档。

// 调用这三个方法需要的JDK版本最小是1.5

System.out.println("<?xml='" + doc.getXmlVersion() + "' encoding='"

+ doc.getXmlEncoding() + "' standalone='"

+ doc.getXmlStandalone() + "'?>");

traverseNode(doc);

} catch (ParserConfigurationException e) {

e.printStackTrace();

} catch (SAXException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

}

}

添加、删除、修改和保存

publicclass DOMConvert {

publicstaticvoid main(String[] args) {

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

try {

DocumentBuilder db = dbf.newDocumentBuilder();

Document doc = db.parse("students.xml");

// ------------------添加节点------------------

// 创建表示一个学生信息的各元素节点。

Element eltStu = doc.createElement("student");

Element eltName = doc.createElement("name");

Element eltAge = doc.createElement("age");

// 创建<student>元素的sn属性节点。

Attr attr = doc.createAttribute("sn");

attr.setValue("03");

// 创建代表学生信息的文本节点。

Text txtName = doc.createTextNode("王五");

Text txtAge = doc.createTextNode("19");

// 将文本节点添加为对应的元素节点的子节点。

eltName.appendChild(txtName);

eltAge.appendChild(txtAge);

// 将name和age节点添加为student节点的子节点。

eltStu.appendChild(eltName);

eltStu.appendChild(eltAge);

// 为<student>元素添加sn属性节点。

eltStu.setAttributeNode(attr);

// 得到XML文档的根元素。

Element eltRoot = doc.getDocumentElement();

// 将student节点添加为根元素的子节点。

eltRoot.appendChild(eltStu);

NodeList nl = doc.getElementsByTagName("student");

// ------------------删除节点------------------

Node nodeDel = nl.item(0);

nodeDel.getParentNode().removeChild(nodeDel);

// ------------------修改节点------------------

// 注意:NodeList对象是活动的,所以前面删除节点的操作会影响到NodeList对象,

// NodeList的节点是

// 的节点。

Element eltChg = (Element) nl.item(0);

Node nodeAgeChg = eltChg.getElementsByTagName("age").item(0);

nodeAgeChg.getFirstChild().setNodeValue("22");

// 输出修改后的学生信息。

for (int i = 0; i < nl.getLength(); i++) {

Element elt = (Element) nl.item(i);

Node nodeName = elt.getElementsByTagName("name").item(0);

Node nodeAge = elt.getElementsByTagName("age").item(0);

String name = nodeName.getFirstChild().getNodeValue();

String age = nodeAge.getFirstChild().getNodeValue();

System.out.println("-----------------学生信息-----------------");

System.out.println("编号:" + elt.getAttribute("sn"));

System.out.print("姓名:");

System.out.println(name);

System.out.print("年龄:");

System.out.println(age);

System.out.println();

}

// ------------------保存修改结果------------------

// 利用文档节点创建一个DOM输入源。

DOMSource source = new DOMSource(doc);

// 以converted.xml文件构造一个StreamResult对象,用于保存转换后结果。

StreamResult result = new StreamResult(new File("converted.xml"));

// 得到转换器工厂类的实例。

TransformerFactory tff = TransformerFactory.newInstance();

// 创建一个新的转换器,用于执行恒等转换,

// 即直接将DOM输入源的内容复制到结果文档中。

Transformer tf = tff.newTransformer();

tf.setOutputProperty(OutputKeys.INDENT, "yes");//缩进

tf.setOutputProperty(OutputKeys.ENCODING, "gb2312");//编码

// 执行转换。

tf.transform(source, result);

} catch (ParserConfigurationException e) {

e.printStackTrace();

} catch (SAXException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

} catch (TransformerConfigurationException e) {

e.printStackTrace();

} catch (TransformerException e) {

e.printStackTrace();

}

}

}

使用SAX解析XML

XMLReader(解析器接口)

SAX解析器接口和事件处理器接口在org.xml.sax包中定义,XMLReader是SAX2.0解析器必须实现的接口(SAX1.0解析器实现Parser接口,该接口已不再使用)。

l  setEntityResolver(EntityResolver resolver):注册一个实体解析器。

l  setDTDHandler(DTDHandler handler):注册一个DTD事件处理器。

l  setContentHandler(ContentHandler handler):注册一个内容事件处理器。

l  parse(InputSource input):解析XML文档。

ContentHandler(内容事件处理器接口)

SAXAPI定义了许多事件,这些事件分别由事件处理器中相应的方法去响应。在SAX1.0前使用的是DocumentHandler接口,SAX2.0中已被ContentHandler取代。

startPrefixMapping(String prefix, String uri):在一个前缀URI名称空间映射范围的开始时被调用。

<students xmlns:stu="http://www.sunin.org/students">

</stuents>

SAX解析器解析到<stuedents>元素时,就会调用startPrefixMapping方法,将stu传递给prefiex参数,将http://www.sunin.org/students传递给uri参数,然后产生<students>元素的startElement事件。在产生<students>元素的endElement事件后,解析器将调用endPrefixMapping方法。

SAX解析器工厂

与Dom类似,JAXP也为SAX解析器提供了工厂类——SAXParserFactory类。与DOM解析器工厂类不同的是,SAXParserFactory类的newInstance()方法查找的工厂类属性为:java.xml.parsers.SAXParserFactory,如果我们使用Apache的Xerces解析器,可以配置如下:

java.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl

JAXP中定义的SAX解析器类是SAXParser,获取SAXParser类的实例与获取DocumentBuilder类的实例相似,在得到工厂类的实例后,通过SAXParserFactory 实现类实例的newSAXParser()方法得到 SAX解析器实例:public abstract SAXParser newSAXParser()

你可以调用SAXParser或者XMLReader中的parser()方法业解析文档,效果是完全一样的。不过在SAXParser中的parse()方法能接受更多的参数。可以对不同的XML文档数据源进行解析,因此使用起来比XMLReader要方便一些。

另外,与DOM不同的是,SAX本身也提供了创建XMLReader对象的工厂类,在org.xml.sax.helpers包中提供了XMLReaderFactory类,该类createXMLReader用于创建XMLReader对象。

实际上,SAXParser是JAXP对XMLReader实现类的一个包装类,在SAXPars中定义了getXMLReader()方法,用于返回它内部的XMLReader实例:abstract  XMLReader getXMLReader()

使用SAX解析XML

解析并完整输出XML文档

/**

* 使用SAX解析XML文档,实际上就是编写事件处理器。

* 为了简化事件处理器接口的实现,我们让SAXPrinter类继承DefaultHandler帮助类

*/

publicclass SAXPrinter extends DefaultHandler {

// SAX解析器开始解析文档时,将会调用这个方法

publicvoid startDocument() throws SAXException {

// 输出XML声明。

System.out.println("<?xml version='1.0' encoding='GB2312'?>");

}

// SAX解析器读取了处理指令,将会调用这个方法

publicvoid processingInstruction(String target, String data)

throws SAXException {

// 输出文档中的处理指令。

System.out.println("<?" + target + " " + data + "?>");

}

// SAX解析器读取了元素的开始标签后,将会调用这个方法

publicvoid startElement(String uri, String localName, String qName,

Attributes attrs) throws SAXException {

// 输出元素的开始标签及其属性。

System.out.print("<" + qName);

int len = attrs.getLength();

for (int i = 0; i < len; i++) {

System.out.print(" ");

System.out.print(attrs.getQName(i));

System.out.print("=\"");

System.out.print(attrs.getValue(i));

System.out.print("\"");

}

System.out.print(">");

}

// SAX解析器读取了字符数据后,将会调用这个方法

publicvoid characters(char[] ch, int start, int length)

throws SAXException {

// 输出元素的字符数据内容。

System.out.print(new String(ch, start, length));

}

// SAX解析器读取了元素的结束标签后,将会调用这个方法

publicvoid endElement(String uri, String localName, String qName)

throws SAXException {

// 输出元素的结束标签。

System.out.print("</" + qName + ">");

}

publicstaticvoid main(String[] args) {

SAXParserFactory spf = SAXParserFactory.newInstance();

SAXParser sp = null;

try {

sp = spf.newSAXParser();

File file = new File("students.xml");

sp.parse(file, new SAXPrinter());

} catch (ParserConfigurationException e) {

e.printStackTrace();

} catch (SAXException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

}

}

使用ErrorHandler处理解析中的错误

DefaultHandler既现实了ContentHandler接口,又实现了ErrorHandler接口。

publicclass ErrorProcessor extends DefaultHandler {

publicvoid warning(SAXParseException ex) throws SAXException {

System.err.println("[Warning] " + getLocationString(ex) + ": "

+ ex.getMessage());

}

publicvoid error(SAXParseException ex) throws SAXException {

System.err.println("[Error] " + getLocationString(ex) + ": "

+ ex.getMessage());

}

publicvoid fatalError(SAXParseException ex) throws SAXException {

System.err.println("[Fatal Error] " + getLocationString(ex) + ": "

+ ex.getMessage());

}

/**

* 获取导致错误或者警告的文本结束位置的行号和列号。

* 如果是实体引发错误,还获取它的公共标识符和系统标识符。

*/

private String getLocationString(SAXParseException ex) {

StringBuffer str = new StringBuffer();

String publicId = ex.getPublicId();

if (publicId != null) {

str.append(publicId);

str.append(" ");

}

String systemId = ex.getSystemId();

if (systemId != null) {

str.append(systemId);

str.append(':');

}

str.append(ex.getLineNumber());

str.append(':');

str.append(ex.getColumnNumber());

return str.toString();

}

/**

* 输出元素的结束标签,以便于查看不同类型的错误对文档解析的影响。

*/

publicvoid endElement(String uri, String localName, String qName)

throws SAXException {

System.out.println("</" + qName + ">");

}

publicstaticvoid main(String[] args) {

try {

/*

*  利用XMLReaderFactory工厂类,创建XMLReader对象。

*  注,这里没有使用JAXP中定义的SAX解析器工厂类和解析器类,

*  而是使用了SAX本身定义的XMLReaderFactory工厂类与XMLRader

*  解析器。当然最好是使用JAXP中的工厂类。

*/

XMLReader xmlReader = XMLReaderFactory.createXMLReader();

// 打开解析器的验证功能。

xmlReader

.setFeature("http://xml.org/sax/features/validation", true);

ErrorProcessor ep = new ErrorProcessor();

xmlReader.setErrorHandler(ep);

xmlReader.setContentHandler(ep);

// InputSource类位于org.xml.sax包中,表示XML实体的输入源。

// 它可以用java.io.InputStream对象来构造,更多信息请参看JDK的API文档

InputSource is = new InputSource(

new FileInputStream("students.xml"));

xmlReader.parse(is);

} catch (SAXException e) {

System.out.println(e.toString());

} catch (IOException e) {

System.out.println(e.toString());

}

}

}

如果被解析的XML不符合对应的模式文档(DTD或Schema)或不存在模式文档时,都会报错

上一篇:bignum 大数模板


下一篇:在windows环境中用eclipse搭建hadoop开发环境