Java 中文官方教程 2022 版(四十)(1)https://developer.aliyun.com/article/1488164
资源、命名空间和错误
StAX 规范处理资源解析、属性和命名空间,以及错误和异常,如下所述。
资源解析
XMLResolver
接口提供了在 XML 处理期间解析资源的方法。应用程序在XMLInputFactory
上设置接口,然后该工厂实例创建的所有处理器都设置该接口。
属性和命名空间
属性由 StAX 处理器使用游标接口中的查找方法和字符串以及迭代器接口中的Attribute
和Namespace
事件报告。请注意,命名空间被视为属性,尽管在游标和迭代器 API 中,命名空间与属性分开报告。还要注意,命名空间处理对于 StAX 处理器是可选的。有关命名空间绑定和可选命名空间处理的完整信息,请参阅 StAX 规范。
错误报告和异常处理
所有致命错误都通过javax.xml.stream.XMLStreamException
接口报告。所有非致命错误和警告都使用javax.xml.stream.XMLReporter
接口报告。
读取 XML 流
正如在本课程前面所描述的,使用 StAX 处理器读取 XML 流的方式——更重要的是,您得到的内容——取决于您是使用 StAX 游标 API 还是事件迭代器 API,这两个部分描述了如何使用这两个 API 读取 XML 流。
使用 XMLStreamReader
StAX 游标 API 中的XMLStreamReader
接口只允许您以向前方向读取 XML 流或文档,每次只能读取信息集中的一个项目。以下方法可用于从流中提取数据或跳过不需要的事件:
- 获取属性的值
- 读取 XML 内容
- 确定一个元素是否有内容或为空
- 获取对属性集合的索引访问
- 获取对命名空间集合的索引访问
- 获取当前事件的名称(如果适用)
- 获取当前事件的内容(如果适用)
XMLStreamReader
的实例在任何时候都有一个当前事件,其方法在其上操作。当您在流上创建一个XMLStreamReader
实例时,初始当前事件是START_DOCUMENT
状态。然后可以使用XMLStreamReader.next
方法来跳到流中的下一个事件。
读取属性、属性和命名空间
XMLStreamReader.next
方法加载流中下一个事件的属性。然后,您可以通过调用XMLStreamReader.getLocalName
和XMLStreamReader.getText
方法来访问这些属性。
当XMLStreamReader
游标位于StartElement
事件上时,它读取事件的名称和任何属性,包括命名空间。可以使用索引值访问事件的所有属性,并且还可以通过命名空间 URI 和本地名称查找。但请注意,只有当前StartEvent
上声明的命名空间可用;之前声明的命名空间不会被保留,重新声明的命名空间也不会被移除。
XMLStreamReader 方法
XMLStreamReader
提供以下方法来检索有关命名空间和属性的信息:
int getAttributeCount(); String getAttributeNamespace(int index); String getAttributeLocalName(int index); String getAttributePrefix(int index); String getAttributeType(int index); String getAttributeValue(int index); String getAttributeValue(String namespaceUri, String localName); boolean isAttributeSpecified(int index);
也可以使用三种额外的方法访问命名空间:
int getNamespaceCount(); String getNamespacePrefix(int index); String getNamespaceURI(int index);
实例化一个 XMLStreamReader
这个示例取自 StAX 规范,展示了如何实例化一个输入工厂,创建一个读取器,并遍历 XML 流的元素:
XMLInputFactory f = XMLInputFactory.newInstance(); XMLStreamReader r = f.createXMLStreamReader( ... ); while(r.hasNext()) { r.next(); }
使用 XMLEventReader
StAX 事件迭代器 API 中的XMLEventReader
API 提供了将 XML 流中的事件映射到可以自由重用的分配的事件对象的方法,并且 API 本身可以扩展以处理自定义事件。
XMLEventReader
提供了四种方法来迭代解析 XML 流:
next
:返回流中的下一个事件nextEvent
:返回下一个类型化的 XMLEventhasNext
:如果流中有更多事件要处理,则返回 truepeek
:返回事件但不迭代到下一个事件
例如,以下代码片段说明了XMLEventReader
方法声明:
package javax.xml.stream; import java.util.Iterator; public interface XMLEventReader extends Iterator { public Object next(); public XMLEvent nextEvent() throws XMLStreamException; public boolean hasNext(); public XMLEvent peek() throws XMLStreamException; // ... }
要读取流上的所有事件然后打印它们,您可以使用以下方法:
while(stream.hasNext()) { XMLEvent event = stream.nextEvent(); System.out.print(event); }
读取属性
您可以从其关联的javax.xml.stream.StartElement
中访问属性,如下所示:
public interface StartElement extends XMLEvent { public Attribute getAttributeByName(QName name); public Iterator getAttributes(); }
您可以使用StartElement
接口上的getAttributes
方法来使用在该StartElement
上声明的所有属性的Iterator
。
读取命名空间
与读取属性类似,命名空间是通过调用StartElement
接口上的getNamespaces
方法创建的Iterator
来读取的。仅返回当前StartElement
的命名空间,并且应用程序可以通过使用StartElement.getNamespaceContext
来获取当前命名空间上下文。
写入 XML 流
StAX 是一个双向 API,游标和事件迭代器 API 都有自己的一套接口用于写入 XML 流。与读取流的接口一样,写入器 API 对于游标和事件迭代器之间存在显著差异。以下部分描述了如何使用这些 API 之一来写入 XML 流。
使用 XMLStreamWriter
StAX 游标 API 中的XMLStreamWriter
接口允许应用程序写回到 XML 流或创建全新的流。XMLStreamWriter 具有让您执行以下操作的方法:
- 写入格式良好的 XML
- 刷新或关闭输出
- 写入限定名称
请注意,XMLStreamWriter
实现不需要对输入执行格式良好性或有效性检查。虽然一些实现可能执行严格的错误检查,但其他可能不会。您实现的规则适用于XMLOutputFactory
类中定义的属性。
使用writeCharacters
方法转义字符,如&
、<
、>
和"
。绑定前缀可以通过传递前缀的实际值,使用setPrefix
方法,或设置默认命名空间声明的属性来处理。
以下示例取自 StAX 规范,展示了如何实例化输出工厂,创建写入器并写入 XML 输出:
XMLOutputFactory output = XMLOutputFactory.newInstance(); XMLStreamWriter writer = output.createXMLStreamWriter( ... ); writer.writeStartDocument(); writer.setPrefix("c","http://c"); writer.setDefaultNamespace("http://c"); writer.writeStartElement("http://c","a"); writer.writeAttribute("b","blah"); writer.writeNamespace("c","http://c"); writer.writeDefaultNamespace("http://c"); writer.setPrefix("d","http://c"); writer.writeEmptyElement("http://c","d"); writer.writeAttribute("http://c", "chris","fry"); writer.writeNamespace("d","http://c"); writer.writeCharacters("Jean Arp"); writer.writeEndElement(); writer.flush();
此代码生成以下 XML(新行不是规范性的):
<?xml version=’1.0’ encoding=’utf-8’?> <a b="blah" > <d:d d:chris="fry" />Jean Arp</a>
使用 XMLEventWriter
StAX 事件迭代器 API 中的XMLEventWriter
接口允许应用程序写回到 XML 流或创建全新的流。此 API 可以扩展,但主要 API 如下:
public interface XMLEventWriter { public void flush() throws XMLStreamException; public void close() throws XMLStreamException; public void add(XMLEvent e) throws XMLStreamException; // ... other methods not shown. }
XMLEventWriter
的实例是由XMLOutputFactory
的实例创建的。流事件被迭代地添加,一旦添加到事件写入器实例后,事件就不能被修改。
属性、转义字符、绑定前缀
StAX 实现需要缓冲最后一个StartElement
,直到在流中添加或遇到除Attribute
或Namespace
之外的事件。这意味着当您向流中添加Attribute
或Namespace
时,它会附加到当前的StartElement
事件。
您可以使用Characters
方法转义字符如&
、<
、>
和"
。
setPrefix(...)
方法可用于显式绑定输出时使用的前缀,而 getPrefix(...)
方法可用于获取当前前缀。请注意,默认情况下,XMLEventWriter
会将命名空间绑定添加到其内部命名空间映射中。前缀在绑定它们的事件对应的 EndElement
后会失效。
Oracle 的流式 XML 解析器实现
应用服务器 9.1 包含 Sun 微系统的 JSR 173(StAX)实现,称为 Sun Java 流式 XML 解析器(简称为流式 XML 解析器)。流式 XML 解析器是一个高速、非验证的、符合 W3C XML 1.0 和 Namespace 1.0 标准的流式 XML 拉取解析器,构建在 Xerces2 代码库之上。
在 Sun 的流式 XML 解析器实现中,Xerces2 的底层,特别是 Scanner 和相关类,已经重新设计为拉取方式。除了底层的更改外,流式 XML 解析器还包括额外的与 StAX 相关的功能和许多性能增强改进。流式 XML 解析器实现在 appserv-ws.jar
和 javaee.jar
文件中,这两个文件位于 install_dir/lib/
目录中。
JAXP 参考实现中包含了 StAX 代码示例,位于 INSTALL_DIR/jaxp-
version/samples/stax
目录中,展示了 Sun 的流式 XML 解析器实现的工作原理。这些示例在 示例代码 中有描述。
在继续使用示例代码之前,有两个关于流式 XML 解析器的方面需要注意:
- 报告 CDATA 事件
- 流式 XML 解析器工厂实现
下面将讨论这些主题。
报告 CDATA 事件
流式 XML 解析器中实现的 javax.xml.stream.XMLStreamReader
不报告 CDATA 事件。如果您有一个需要接收此类事件的应用程序,请配置 XMLInputFactory
来设置以下特定于实现的 report-cdata-event
属性:
XMLInputFactory factory = XMLInptuFactory.newInstance(); factory.setProperty("report-cdata-event", Boolean.TRUE);
流式 XML 解析器工厂实现
大多数应用程序不需要知道工厂实现类名。对于大多数应用程序,只需将 javaee.jar
和 appserv-ws.jar
文件添加到类路径即可,因为这两个 jar 文件在 META-INF/services
目录下提供了各种流式 XML 解析器属性的工厂实现类名,例如 javax.xml.stream.XMLInputFactory
、javax.xml.stream.XMLOutputFactory
和 javax.xml.stream.XMLEventFactory
,这是应用程序请求工厂实例时查找操作的第三步。有关查找机制的更多信息,请参阅 XMLInputFactory.newInstance
方法的 Javadoc。
但是,在某些情况下,应用程序可能希望了解工厂实现类名并显式设置属性。这些情况可能包括类路径中存在多个 JSR 173 实现,应用程序希望选择其中一个,也许是性能更好的一个,包含了关键的错误修复,或类似情况。
如果一个应用程序设置了SystemProperty
,那么这是查找操作的第一步,因此获取工厂实例相对于其他选项来说会更快;例如:
javax.xml.stream.XMLInputFactory --> com.sun.xml.stream.ZephyrParserFactory javax.xml.stream.XMLOutputFactory --> com.sun.xml.stream.ZephyrWriterFactor javax.xml.stream.XMLEventFactory --> com.sun.xml.stream.events.ZephyrEventFactory
示例代码
本节逐步介绍了 JAXP 参考实现包中包含的示例 StAX 代码。本节中使用的所有示例目录均位于INSTALL_DIR/jaxp-
version/samples/stax
目录中。
本节涵盖的主题如下:
- 示例代码组织
- 示例 XML 文档
- 游标示例
- 游标到事件示例
- 事件示例
- 过滤器示例
- 读写示例
- 写入示例
示例代码组织
INSTALL_DIR/jaxp-
version/samples/stax
目录包含六个 StAX 示例目录:
- 游标示例:
cursor
目录包含CursorParse.java
,演示如何使用XMLStreamReader
(游标)API 读取 XML 文件。 - 游标到事件示例:
cursor2event
目录包含CursorApproachEventObject.java
,演示应用程序如何在使用游标 API 时将信息作为XMLEvent
对象获取。 - 事件示例:
event
目录包含EventParse.java
,演示如何使用XMLEventReader
(事件迭代器)API 读取 XML 文件。 - 过滤器示例:
filter
目录包含MyStreamFilter.java
,演示如何使用 StAX 流过滤器 API。在此示例中,过滤器仅接受StartElement
和EndElement
事件,并过滤掉其余事件。 - 读写示例:
readnwrite
目录包含EventProducerConsumer.java
,演示了如何使用 StAX 生产者/消费者机制同时读取和写入 XML 流。 - 写入示例:
writer
目录包含CursorWriter.java
,演示如何使用XMLStreamWriter
以编程方式编写 XML 文件。
除了写入示例外,本节中的所有 StAX 示例均使用示例 XML 文档BookCatalog.xml
。
示例 XML 文档
大多数 StAX 示例类使用的示例 XML 文档BookCatalog.xml
是一个基于常见BookCatalogue
命名空间的简单图书目录。BookCatalog.xml
的内容如下:
<?xml version="1.0" encoding="UTF-8"?> <BookCatalogue > <Book> <Title>Yogasana Vijnana: the Science of Yoga</Title> <author>Dhirendra Brahmachari</Author> <Date>1966</Date> <ISBN>81-40-34319-4</ISBN> <Publisher>Dhirendra Yoga Publications</Publisher> <Cost currency="INR">11.50</Cost> </Book> <Book> <Title>The First and Last Freedom</Title> <Author>J. Krishnamurti</Author> <Date>1954</Date> <ISBN>0-06-064831-7</ISBN> <Publisher>Harper & Row</Publisher> <Cost currency="USD">2.95</Cost> </Book> </BookCatalogue>
游标示例
位于INSTALL_DIR/jaxp-
version/samples/stax/cursor/
目录中,CursorParse.java
演示了如何使用 StAX 游标 API 读取 XML 文档。在游标示例中,应用程序通过调用next()
指示解析器读取 XML 输入流中的下一个事件。
请注意,next()
只返回与解析器所处位置对应的整数常量。应用程序需要调用相关函数以获取与底层事件相关的更多信息。
您可以将这种方法想象成虚拟游标在 XML 输入流中移动。当虚拟游标位于特定事件时,可以调用各种访问器方法。
逐个事件地进行步进
在这个示例中,客户端应用程序通过在解析器上调用next
方法来拉取 XML 流中的下一个事件;例如:
try { for (int i = 0 ; i < count ; i++) { // pass the file name.. all relative entity // references will be resolved against this // as base URI. XMLStreamReader xmlr = xmlif.createXMLStreamReader(filename, new FileInputStream(filename)); // when XMLStreamReader is created, // it is positioned at START_DOCUMENT event. int eventType = xmlr.getEventType(); printEventType(eventType); printStartDocument(xmlr); // check if there are more events // in the input stream while(xmlr.hasNext()) { eventType = xmlr.next(); printEventType(eventType); // these functions print the information // about the particular event by calling // the relevant function printStartElement(xmlr); printEndElement(xmlr); printText(xmlr); printPIData(xmlr); printComment(xmlr); } } }
请注意,next
只是返回与当前游标位置下的事件对应的整数常量。应用程序调用相关函数以获取与底层事件相关的更多信息。当游标位于特定事件时,可以调用各种访问器方法。
返回字符串表示形式
因为next
方法只返回与底层事件类型对应的整数,通常需要将这些整数映射到事件的字符串表示形式;例如:
public final static String getEventTypeString(int eventType) { switch (eventType) { case XMLEvent.START_ELEMENT: return "START_ELEMENT"; case XMLEvent.END_ELEMENT: return "END_ELEMENT"; case XMLEvent.PROCESSING_INSTRUCTION: return "PROCESSING_INSTRUCTION"; case XMLEvent.CHARACTERS: return "CHARACTERS"; case XMLEvent.COMMENT: return "COMMENT"; case XMLEvent.START_DOCUMENT: return "START_DOCUMENT"; case XMLEvent.END_DOCUMENT: return "END_DOCUMENT"; case XMLEvent.ENTITY_REFERENCE: return "ENTITY_REFERENCE"; case XMLEvent.ATTRIBUTE: return "ATTRIBUTE"; case XMLEvent.DTD: return "DTD"; case XMLEvent.CDATA: return "CDATA"; case XMLEvent.SPACE: return "SPACE"; } return "UNKNOWN_EVENT_TYPE , " + eventType; }
运行游标示例
- 要编译和运行游标示例,在终端窗口中,转到INSTALL_DIR
/jaxp-
version/samples/
目录,并输入以下内容:
javac stax/cursor/*.java
- 在
BookCatalogue.xml
文件上运行CursorParse
示例,使用以下命令。CursorParse
将打印出BookCatalogue.xml
文件的每个元素。
java stax/event/CursorParse stax/data/BookCatalogue.xml
游标到事件示例
位于tut-install/javaeetutorial5/examples/stax/cursor2event/
目录中,CursorApproachEventObject.java
演示了如何在使用游标 API 时获取XMLEvent
对象返回的信息。
这里的想法是,游标 API 的XMLStreamReader
返回与特定事件对应的整数常量,而事件迭代器 API 的XMLEventReader
返回不可变且持久的事件对象。 XMLStreamReader
更有效率,但XMLEventReader
更易于使用,因为与特定事件相关的所有信息都封装在返回的XMLEvent
对象中。然而,事件方法的缺点是为每个事件创建对象的额外开销,这既消耗时间又消耗内存。
有了这个想法,即使使用游标 API,也可以使用XMLEventAllocator
来获取事件信息作为XMLEvent
对象。
实例化一个 XMLEventAllocator
第一步是创建一个新的XMLInputFactory
并实例化一个XMLEventAllocator
:
XMLInputFactory xmlif = XMLInputFactory.newInstance(); System.out.println("FACTORY: " + xmlif); xmlif.setEventAllocator(new XMLEventAllocatorImpl()); allocator = xmlif.getEventAllocator(); XMLStreamReader xmlr = xmlif.createXMLStreamReader(filename, new FileInputStream(filename));
创建一个事件迭代器
下一步是创建一个事件迭代器:
int eventType = xmlr.getEventType(); while (xmlr.hasNext()) { eventType = xmlr.next(); // Get all "Book" elements as XMLEvent object if (eventType == XMLStreamConstants.START_ELEMENT && xmlr.getLocalName().equals("Book")) { // get immutable XMLEvent StartElement event = getXMLEvent(xmlr).asStartElement(); System.out.println ("EVENT: " + event.toString()); } }
创建分配器方法
最后一步是创建XMLEventAllocator
方法:
private static XMLEvent getXMLEvent(XMLStreamReader reader) throws XMLStreamException { return allocator.allocate(reader); }
运行游标到事件示例
- 要编译和运行游标到事件示例,在终端窗口中,转到INSTALL_DIR
/jaxp-
version/samples/
目录,并输入以下内容:
javac -classpath ../lib/jaxp-ri.jar stax/cursor2event/*.java
- 在
BookCatalogue.xml
文件上运行CursorApproachEventObject
示例,使用以下命令。
java stax/cursor2event/CursorApproachEventObject stax/data/BookCatalogue.xml
CursorApproachEventObject
将打印出BookCatalogue.xml
文件中定义的事件列表。
事件示例
位于INSTALL_DIR/jaxp-
version/samples/stax/event/
目录中,EventParse.java
演示了如何使用 StAX 事件 API 读取 XML 文档。
创建一个输入工厂
第一步是创建一个新的XMLInputFactory
实例:
XMLInputFactory factory = XMLInputFactory.newInstance(); System.out.println("FACTORY: " + factory);
创建一个事件读取器
下一步是创建一个 XMLEventReader
实例:
XMLEventReader r = factory.createXMLEventReader (filename, new FileInputStream(filename));
Java 中文官方教程 2022 版(四十)(3)https://developer.aliyun.com/article/1488168