`
hackbomb
  • 浏览: 216441 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

使用 StAX 解析 XML

    博客分类:
  • Xml
阅读更多

StAX 概述

从一开始,Java API for XML Processing (JAXP) 就提供了两种方法来处理 XML:文档对象模型(DOM)方法是用标准的对象模型表示 XML 文档;Simple API for XML (SAX) 方法使用应用程序提供的事件处理程序来处理 XML。JSR-173 提出了一种面向流的新方法:Streaming API for XML (StAX)。其最终版本于 2004 年 3 月发布,并成为了 JAXP 1.4(将包含在即将发布的 Java 6 中)的一部分。

如其名称所暗示的那样,StAX 把重点放在流上。实际上,StAX 与其他方法的区别就在于应用程序能够把 XML 作为一个事件流来处理。将 XML 作为一组事件来处理的想法并不新颖(事实上 SAX 已经提出来了),但不同之处在于 StAX 允许应用程序代码把这些事件逐个拉出来,而不用提供在解析器方便时从解析器中接收事件的处理程序。

StAX 实际上包括两套处理 XML 的 API,分别提供了不同程度的抽象。基于指针的 API 允许应用程序把 XML 作为一个标记(或事件)流来处理;应用程序可以检查解析器的状态,获得解析的上一个标记的信息,然后再处理下一个标记,依此类推。这是一种低层 API,尽管效率高,但是没有提供底层 XML 结构的抽象。较为高级的基于迭代器的 API 允许应用程序把 XML 作为一系列事件对象来处理,每个对象和应用程序交换 XML 结构的一部分。应用程序只需要确定解析事件的类型,将其转换成对应的具体类型,然后利用其方法获得属于该事件的信息。

基本原理

为了使用这两类 API,应用程序首先必须获得一个具体的 XMLInputFactory。根据传统的 JAXP 风格,要用到抽象工厂模式;XMLInputFactory 类提供了静态的 newInstance 方法,它负责定位和实例化具体的工厂。配置该实例可设置定制或者预先定义好的属性(其名称在类 XMLInputFactory 中定义)。最后,为了使用基于指针的 API,应用程序还要通过调用某个 createXMLStreamReader 方法获得一个 XMLStreamReader。如果要使用基于事件迭代器的 API,应用程序就要调用 createXMLEventReader 方法获得一个 XMLEventReader(如清单 1 所示)。

清单 1. 获取和配置默认的 XMLInputFactory
               
// get the default factory instance
XMLInputFactory factory = XMLInputFactory.newInstance();
// configure it to create readers that coalesce adjacent character sections
factory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
XMLStreamReader r = factory.createXMLStreamReader(input);
// ...
 
XMLStreamReader 和 XMLEventReader 都允许应用程序迭代底层的 XML 流。两种方法的差别在于如何公开解析后的 XML InfoSet 信息片段。XMLStreamReader 就像一个指针,指在刚刚解析过的 XML 标记的后面,并提供了方法获得更多关于该标记的信息。这种方法节约内存,因为不用创建新的对象。但是,业务应用程序开发人员可能会发现 XMLEventReader 更直观一些,因为它实际上就是一个标准的 Java 迭代器,将 XML 变成了事件对象流。每个事件对象都封装了它所表示的特定 XML 结构固有的信息。本系列的第二部分将详细讨论这种基于事件迭代器的 API。

使用哪种风格的 API 取决于具体情况。和基于指针的 API 相比,基于事件迭代器的 API 具有更多的面向对象特征。因此更便于应用于模块化的体系结构,因为当前的解析器状态反映在事件对象中,应用程序组件在处理事件的时候不需要访问解析器/读 取器。此外,还可以使用 XMLInputFactory 的 createXMLEventReader(XMLStreamReader) 方法从 XMLStreamReader 创建 XMLEventReader。

StAX 还定义了一种序列化器 API,Java 标准 XML 处理支持中一直缺少的一种特性。和解析一样,也包含两种风格的流式 API:处理标记的底层 XMLStreamWriter 和处理事件对象的高层 XMLEventWriter。XMLStreamWriter 提供了写入单个 XML 记号(比如开始和关闭标记或者元素属性)的方法,不检查这些标记是否格式良好。另一方面,XMLEventWriter 允许应用程序向输出中添加完整的 XML 事件。第 3 部分将详细讨论 StAX 序列化器 API。

为什么使用 StAX?

开始学习一种新的处理 XML 的 API 之前,可能要问是否值得这样做。事实上,StAX 所采用的基于拉的方法和其他方法相比有一些突出的优点。首先,不管使用哪种 API 风格,都是应用程序调用读取器(解析器)而不是相反。通过保留解析过程的控制权,可以简化调用代码来准确地处理它预期的内容。或者发生意外时停止解析。此 外,由于该方法不基于处理程序回调,应用程序不需要像使用 SAX 那样模拟解析器的状态。

StAX 仍然保留了 SAX 相对于 DOM 的优点。通过把重心从结果对象模型转移到解析流本身,从理论上说应用程序能够处理无限的 XML 流,因为事件固有的临时性,不会在内存中累积起来。对于那些使用 XML 作为消息传递协议而非表示文档内容的那些应用程序尤其重要,比如 Web 服务或即时消息应用程序。比方说,如果只是将其转换成特定于应用程序的对象模型然后就将其丢弃,那么为 Web 服务路由器 servlet 提供一个 DOM 就没有多少用处。使用 StAX 直接转化成应用程序模型效率更高。对于 Extensible Messaging and Presence Protocol(XMPP)客户机,根本不能使用 DOM,因为 XMPP 客户机/服务器流是随着用户输入的消息实时生成。等待流的关闭标签(以便最终建立 DOM)就意味着等待整个会话结束。通过把 XML 作为一系列的事件来处理,应用程序能够以最合适的方式响应每个事件(比如显示收到的即时消息等等)。

由于其双向性,StAX 也支持链式处理,特别是在事件层上。接收事件(无论什么来源)的能力被封装在 XMLEventConsumer(XMLEventWriter 的扩展)接口中。因此,可以模块化地编写应用程序从 XMLEventReader(也是一个普通的迭代器,可以按迭代器处理)读取和处理 XML 事件、然后传递给事件消费者(如果需要可以进一步扩展处理链)。在第 2 部分将看到,也可使用应用程序提供的筛选器(实现了 EventFilter 接口的类)来定制 XMLEventReader 或者使用 EventReaderDelegate 修饰已有的 XMLEventReader。

总而言之,和 DOM 以及 SAX 相比,StAX 使应用程序更贴近底层的 XML。使用 StAX,应用程序不仅可以建立需要的对象模型(而不需要处理标准 DOM),而且可以随时这样做,而不必等到解析器回调。

 

基于指针的 API

如果使用基于指针的 API,应用程序通过在 XML 标记流中移动逻辑指针来处理 XML。基于指针的解析器实质上是一个状态机,在事件的驱动下从一个良好定义的状态转移到另一个状态。这里的触发事件是随着应用程序使用适当的方法推动解 析器在标记流中前进而解析出来的 XML 标记。在每个状态,都可使用一组方法获得上一个事件的信息。一般来说,并非每个状态下都能使用所有的方法。

使用基于指针的方法,应用程序首先必须通过调用其 createXMLStreamReader 方法从 XMLInputFactory 得到 XMLStreamReader。该方法有多个版本,支持不同类型的输入。比方说,可以创建 XMLStreamReader 解析 plain java.io.InputStream、java.io.Reader 或者 JAXP Source(javax.xml.transform.Source)。从理论上说,后一种办法很容易和其他 JAXP 技术交互,比如 SAX 和 DOM。


清单 2. 创建 XMLStreamReader 解析 InputStream
               
URL url = new URL(uri);
InputStream input = url.openStream();
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader r = factory.createXMLStreamReader(uri, input);
// process the stream
// ...
r.close();
input.close();


XMLStreamReader 接口基本上定义了基于指针的 API(虽然标记常量在其超类型 XMLStreamConstants 接口中定义)。之所以称为基于指针,是因为读取器就像是底层标记流上的指针。应用程序可以沿着标记流向前推进指针并分析当前指针所在位置的标记。

XMLStreamReader 提供了多种方法导航标记流。为了确定当前指针所指向的标记(或事件)的类型,应用程序可以调用 getEventType()。该方法返回接口 XMLStreamConstants 中定义的一个标记常量。移动到下一个标记,应用程序可以调用 next()。该方法也返回解析的标记的类型,如果接着调用 getEventType() 则返回的值相同。只有当方法 hasNext() 返回 true 时(就是说还有其他标记需要解析)才能调用该方法(以及其他移动读取器的方法)。


清单 3. 使用 XMLStreamReader 处理 XML 的常用模式
               
// create an XMLStreamReader
XMLStreamReader r = ...;
try {
 int event = r.getEventType();
 while (true) {
 switch (event) {
 case XMLStreamConstants.START_DOCUMENT:
 // add cases for each event of interest
 // ...
 }

 if (!r.hasNext())
 break;
 
 event = r.next();
 }
} finally {
 r.close();
}


还与其他几种方法可以移动 reader。 nextTag() 方法将跳过所有的空白、注释或处理指令,直到遇到 START_ELEMENT 或 END_ELEMENT。该方法在解析只含元素的内容时很有用,如果在发现标记之前遇到非空白文本(不包括注释或处理指令),就会抛出异常。 getElementText() 方法返回元素的开始和关闭标签(即 START_ELEMENT 和 END_ELEMENT)之间的所有文本内容。如果遇到嵌套的元素就会抛出异常。

请注意,这里的 “标记” 和 “事件” 可以互换使用。虽然基于指针的 API 的文档说的是事件,但把输入源看成标记流很方便。而且不容易造成混乱,因为还有一整套基于事件的 API(那里的事件是真正的对象)。不过,XMLStreamReader 的事件本质上并非都是标记。比方说,START_DOCUMENT 和 END_DOCUMENT 事件不需要对应的标记。前一个事件是解析开始之前发生,后者则在没有更多解析工作要做的时候发生(比如解析完成最后一个元素的关闭标签之后,读取器处于 END_ELEMENT 状态,但是如果没有发现更多的标记需要解析,读取器就会切换到 END_DOCUMENT 状态)。

处理 XML 文档

在每个解析器状态,应用程序都可通过可用的方法获得相关信息。比如,无论当前是什么类型的事件,getNamespaceContext() 和 getNamespaceURI() 方法可以获得当前有效的名称空间上下文和名称空间 URI。类似的,getLocation() 可以获得当前事件的位置信息。方法 hasName() 和 hasText() 可以分别判断当前事件是否有名称(比如元素或属性)或文本(比如字符、注释或 CDATA)。方法 isStartElement()、isEndElement()、isCharacters() 和 isWhiteSpace() 可以方便地确定当前事件的性质。最后,方法 require(int, String, String) 可以声明预期的解析器状态;除非当前事件是指定的类型,并且本地名和名称空间(如果给出的话)与当前事件匹配,否则该方法将抛出异常。


清单 4. 如果当前事件是 START_ELEMENT 使用有关的属性方法
               
if (reader.getEventType() == XMLStreamConstants.START_ELEMENT) {
 System.out.println("Start Element: " + reader.getName());
 for(int i = 0, n = reader.getAttributeCount(); i < n; ++i) {
 QName name = reader.getAttributeName(i);
 String value = reader.getAttributeValue(i);
 System.out.println("Attribute: " + name + "=" + value);
 }
}


创建之后,XMLStreamReader 将从 START_DOCUMENT 状态开始(即 getEventType() 返回 START_DOCUMENT)。处理标记的时候应考虑到这一点。和迭代器不同,不需要先移动指针(使用 next())来进入合法的状态。同样地,当读取器转换到最终状态 END_DOCUMENT 之后,应用程序也不应再移动它。在这种状态下,hasNext() 方法将返回 false。

START_DOCUMENT 事件提供了获取关于文档本身信息的方法,如 getEncoding()、getVersion() 和 isStandalone()。应用程序也可调用 getProperty(String) 获得命名属性的值,不过一些属性仅在特定状态做了定义(比方说,如果当前事件是 DTD,则属性 javax.xml.stream.notations 和 javax.xml.stream.entities 分别返回所有的符号和实体声明)。

在 START_ELEMENT 和 END_ELEMENT 事件中,可以使用和元素名称以及名称空间有关的方法(如 getName()、getLocalName()、getPrefix() 和 getNamespaceXXX()),在 START_ELEMENT 事件中还可使用与属性有关的方法(getAttributeXXX())。

ATTRIBUTE 和 NAMESPACE 也被识别为独立的事件,虽然在解析 典型的 XML 文档时不会用到。但是当 ATTRIBUTE 或 NAMESPACE 节点作为 XPath 查询结果返回时可以使用。

和基于文本的事件(如 CHARACTERS、CDATA、COMMENT 和 SPACE),可使用各种 getTextXXX() 方法取得文本。可以分别使用 getPITarget() 和 getPIData() 检索 PROCESSING_INSTRUCTION 的目标和数据。ENTITY_REFERENCE 和 DTD 也支持 getText(),ENTITY_REFERENCE 还支持 getLocalName()。

解析完成后,应用程序关闭读取器并释放解析过程中获得的资源。请注意这样并没有关闭底层的输入源。

清单 5 提供了一个完整的例子,使用基于指针的 API 处理 XML 文档。首先取得 XMLInputFactory 的默认实例并创建一个 XMLStreamReader 解析给定的输入流。然后不断检查读取器的状态,根据当前事件的类型报告某些信息(比如在 START_ELEMENT 状态下报告元素名及元素属性)。最后,遇到 END_DOCUMENT 时关闭读取器。


清单 5. 使用 XMLStreamReader 解析 XML 文档的完整例子
               
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader r = factory.createXMLStreamReader(input);
try {
 int event = r.getEventType();
 while (true) {
 switch (event) {
 case XMLStreamConstants.START_DOCUMENT:
 out.println("Start Document.");
 break;
 case XMLStreamConstants.START_ELEMENT:
 out.println("Start Element: " + r.getName());
 for(int i = 0, n = r.getAttributeCount(); i < n; ++i)
 out.println("Attribute: " + r.getAttributeName(i)
 + "=" + r.getAttributeValue(i));
 
 break;
 case XMLStreamConstants.CHARACTERS:
 if (r.isWhiteSpace())
 break;
 
 out.println("Text: " + r.getText());
 break;
 case XMLStreamConstants.END_ELEMENT:
 out.println("End Element:" + r.getName());
 break;
 case XMLStreamConstants.END_DOCUMENT:
 out.println("End Document.");
 break;
 }
 
 if (!r.hasNext())
 break;

 event = r.next();
 }
} finally {
 r.close();
}


XMLStreamReader 的高级用法

通过调用 XMLInputFactory 的带有基本读取器的 createFilteredReader 方法和一个应用程序定义的筛选器(即实现 StreamFilter 的类实例),可以创建筛选过的 XMLStreamReader。导航筛选过的读取器时,读取器每次移动到下一个标记之前都会询问筛选器。如果筛选器认可了当前事件,就将其公开给筛选过 的读取器。否则跳过这个标记并检查下一个,依此类推。这种方法可以让开发人员创建一个仅处理解析内容子集的基于指针的 XML 处理程序,并与针对不同的扩展的内容模型的筛选器结合使用。

执行更复杂的流操作,可以创建 StreamReaderDelegate 的子类并重写合适的方法。然后使用这个子类的实例包装基本 XMLStreamReader,从而为应用程序提供一个修改过的基本 XML 流的视图。可通过这种技术对 XML 流执行简单的转换,比如筛掉或者替换特定的标记,甚至增加新的标记。

清单 6 用定制的 StreamReaderDelegate 包装了基本 XMLStreamReader,重写了 next() 方法来跳过 COMMENT 和 PROCESSING_INSTRUCTION 事件。使用该读取器时,应用程序不用担心会遇到这种类型的标记。


清单 6. 使用定制的 StreamReaderDelegate 筛选注释和处理指令
               
URL url = new URL(uri);
InputStream input = url.openStream();

XMLInputFactory f = XMLInputFactory.newInstance();
XMLStreamReader r = f.createXMLStreamReader(uri, input);
XMLStreamReader fr = new StreamReaderDelegate(r) {
 public int next() throws XMLStreamException {
 while (true) {
 int event = super.next();
 switch (event) {
 case XMLStreamConstants.COMMENT:
 case XMLStreamConstants.PROCESSING_INSTRUCTION:
 continue;
 default:
 return event;
 }
 }
 }
};

try {
 int event = fr.getEventType();
 while (true) {
 switch (event) {
 case XMLStreamConstants.COMMENT:
 case XMLStreamConstants.PROCESSING_INSTRUCTION:
 // this should never happen
 throw new IllegalStateException("Filter failed!");
 default:
 // process XML normally
 }

 if (!fr.hasNext())
 break;

 event = fr.next();
 }
} finally {
 fr.close();
}

input.close();


基于指针处理之外的其他技术

可以看到,基于指针的 API 主要是为了提高效率。所有的状态信息可以直接从流读取器获得,不需要创建额外的对象。非常适用于性能和低内存占用至关重要的应用程序。

人们早就认识到了拉式 XML 解析的好处。事实上,StAX 本身源于一种称为 XML Pull Parsing 的方法。XML Pull Parser API 类似于 StAX 所提供的基于指针的 API,可以通过分析解析器的状态获得上一个解析事件的信息,然后移动到下一个,依此类推。但没有提供基于事件迭代器的 API。这是一种非常轻型的方法,特别适合资源受限的环境,比如 J2ME。但是,很少有实现提供企业级特性如验证,因此 XML Pull 一直未受到企业 Java 开发人员的关注。

基于以往拉式解析器实现的经验,StAX 的创建者选择了在基于指针的 API 之外增加一种面向对象的 API。虽然 XMLEventReader 接口看起来似乎很简单,但是基于事件迭代器的方法具有一个基于指针的方法不具备的重要优点。通过将解析器事件变成一级对象,从而让应用程序可以采用面向对 象的方式处理它们。这样做有助于模块化和不同应用程序组件之间的代码重用。


清单 7. 使用 StAX XMLEventReader 解析 XML
               
XMLInputFactory inputFactory = XMLInputFactory.newInstance();
XMLEventReader reader = inputFactory.createXMLEventReader(input);
try {
 while (reader.hasNext()) {
 XMLEvent e = reader.nextEvent();
 if (e.isCharacters() && ((Characters) e).isWhiteSpace())
 continue;
 
 out.println(e);
 }
} finally {
 reader.close();
}

分享到:
评论

相关推荐

    用Stax组装及解析XML

    使用StAX解析XML文档则需要借助`XMLInputFactory`和`XMLStreamReader`类完成。下面是一个简单的例子: ```java XMLInputFactory factory = XMLInputFactory.newInstance(); XMLStreamReader reader = factory....

    stax 解析xml demo project

    STAX解析XML的过程是通过事件驱动的,即在解析XML时,解析器会触发一系列事件,如遇到元素开始、元素结束、文本节点等,开发者注册监听这些事件,然后在事件触发时进行相应的处理。相比于DOM(Document Object ...

    kettle转换xml(XML Input Stream (StAX))

    这个实例展示了如何使用StAX解析XML并将其数据写入目标系统。 总结来说,Kettle的XML Input Stream (StAX) 提供了一种高效、内存友好的方式来处理XML数据。通过合理配置,我们可以轻松地从XML文件中提取数据,然后...

    stax+jaxb进行xml解析

    1. **StAX解析流程**: - 创建XMLInputFactory实例,这是解析XML的起点。 - 使用XMLInputFactory创建XMLStreamReader,这是读取XML事件的对象。 - 通过XMLStreamReader逐个处理事件,如StartElement、EndElement...

    java解析xml例子

    以下是一个使用StAX解析XML的例子: ```java import java.io.FileInputStream; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamReader;...

    kettle解析xml多层分组嵌套数据,StAX方法,完整解析案例(包含xml文件以及ktr文件)

    kettle 解析xml数据,xml多层分组嵌套,xml stax方法,完整解析案例使用(包含xml文件以及ktr文件)。ETL大数据迁移,数据清洗。XML Input Stream (StAX) 方法

    java txt文件解析xml格式添加到数据库

    2. 使用StAX解析XML: StAX允许程序以迭代方式处理XML,可以逐个读取事件,如下所示: ```java import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream....

    java通用解析XML工具类

    以下是一个使用StAX解析XML的示例: ```java import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamReader; import java.io.File; public ...

    kettle 解析xml xml文档,配合kettle 解析xml stax方法,完整解析案例使用

    与上一个版本一起使用可以可以起到提高效率的目的。本人解析xml的真实文档

    使用JDOM解析XML文件

    本篇文章将深入探讨如何使用JDOM解析XML文件。 首先,我们需要理解JDOM的基本结构。JDOM通过Document对象表示整个XML文档,Element代表XML元素,Attribute表示元素属性,Text表示元素内的文本内容。这些类构成了...

    java中解析xml

    以下是一个使用StAX解析XML的例子: ```java import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamReader; import java.io.FileInputStream;...

    使用StAX进行高效的XML处理中文版

    ### 使用StAX进行高效的XML处理 #### 概述 StAX(Streaming API for XML)是一种新的XML解析方式,它提供...无论是对于需要处理大量数据的应用程序还是需要高效解析XML的应用场景,StAX都是一种值得考虑的技术选择。

    Stax组装及解析XML的例子

    Axis2在性能上的提升也得益于采用StAX解析XML。 总之,StAX提供了一种高效、低内存开销的方式来处理XML,尤其适用于处理大型XML文档和需要高性能的应用场景。通过组装和解析XML,开发人员可以灵活地在应用程序之间...

    使用java解析XML文件,解析完之后将解析结果导入mysql数据库中

    可以考虑使用SAX或者StAX解析器,它们是事件驱动的,只在需要时处理XML数据,减少内存占用。 5. **安全性**: - 防止SQL注入:使用预编译的`PreparedStatement`可以避免SQL注入攻击,确保参数安全。 - 输入验证:...

    Java_XML生成与解析的四种方法

    使用StAX解析XML文档 StAX提供了一个`XMLStreamReader`接口,用于读取XML流中的事件。 **示例代码**: ```java public class StaxDemo { public void parseXml(String fileName) { try (XMLInputFactory factory ...

    java使用dom解析xml

    如果内存资源有限或文件过大,可以考虑使用 SAX 或者 StAX 解析器,它们是基于事件驱动的,只在需要时读取数据,从而降低了内存需求。 至于"AndroidDomXml"这个文件名,可能是一个 Android 平台上的 XML 解析示例。...

    java平台中使用DOM解析xml文件

    使用DOM解析XML的基本步骤如下: 1. **导入必要的库**:在Java中,DOM解析功能主要由`javax.xml.parsers`和`org.w3c.dom`包提供。因此,首先需要在代码中导入这些库: ```java import javax.xml.parsers....

    java使用stax技术操作XML文档.doc

    另一方面,基于迭代器的API使用XMLEventReader,它将XML解析为一系列的事件对象,每个对象封装了特定的XML结构信息。XMLEventReader实现了Java的迭代器接口,使得处理XML事件变得更加直观和模块化。与基于指针的API...

    java 解析xml 多级

    Java作为一种广泛使用的后端编程语言,提供了多种方式来解析XML文档,使得处理多级嵌套的数据变得可能。本文将详细讲解如何在Java中解析XML,特别是针对多级结构的情况。 首先,Java提供了两种主要的API来处理XML:...

Global site tag (gtag.js) - Google Analytics