本教程针对JDK 8编写。本页面中描述的示例和实践不利用后续版本中引入的改进,并且可能使用不再可用的技术。
有关Java SE 9及后续版本中更新的语言特性的摘要,请参阅Java语言变更。
有关所有JDK版本的新功能、增强功能以及已删除或弃用选项的信息,请参阅JDK发行说明。
本节介绍了JAXP参考实现包中包含的示例StAX代码。本节中使用的所有示例目录都位于INSTALL_DIR/jaxp-version/samples/stax目录中。
本节涵盖的主题如下:
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。
大多数StAX示例类使用的示例XML文档BookCatalog.xml是一个基于通用BookCatalogue命名空间的简单图书目录。 BookCatalog.xml的内容如下:
<?xml version="1.0" encoding="UTF-8"?> <BookCatalogue xmlns="http://www.publishing.org"> <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++) { // 传递文件名...所有相对实体引用将相对于此进行解析 // 作为基本URI。 XMLStreamReader xmlr = xmlif.createXMLStreamReader(filename, new FileInputStream(filename)); // 当创建XMLStreamReader时, // 它位于START_DOCUMENT事件处。 int eventType = xmlr.getEventType(); printEventType(eventType); printStartDocument(xmlr); // 检查输入流中是否还有更多事件 while(xmlr.hasNext()) { eventType = xmlr.next(); printEventType(eventType); // 这些函数通过调用相关函数来打印有关特定事件的信息 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; }
javac stax/cursor/*.java
CursorParse将打印出BookCatalogue.xml文件的每个元素。
java stax/event/CursorParse stax/data/BookCatalogue.xml
CursorApproachEventObject.java位于tut-install/javaeetutorial5/examples/stax/cursor2event/目录中,演示了如何在使用游标API时获取XMLEvent对象返回的信息。
这里的思路是,游标API的XMLStreamReader返回与特定事件对应的整数常量,而事件迭代器API的XMLEventReader返回不可变和持久的事件对象。XMLStreamReader更高效,但XMLEventReader更易于使用,因为与特定事件相关的所有信息都封装在返回的XMLEvent对象中。但是,事件方法的缺点是为每个事件创建对象的额外开销,这会消耗时间和内存。
鉴于此,可以使用XMLEventAllocator将事件信息作为XMLEvent对象获取,即使在使用游标API时也可以。
第一步是创建一个新的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(); // 将所有"Book"元素作为XMLEvent对象获取 if (eventType == XMLStreamConstants.START_ELEMENT && xmlr.getLocalName().equals("Book")) { // 获取不可变的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); }
javac -classpath ../lib/jaxp-ri.jar stax/cursor2event/*.java
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));
第三步是创建一个事件迭代器:
XMLEventReader r = factory.createXMLEventReader (filename, new FileInputStream(filename)); while (r.hasNext()) { XMLEvent e = r.nextEvent(); System.out.println(e.toString()); }
最后一步是获取底层事件流:
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; }
当运行事件示例时,EventParse类被编译,XML流被解析为事件并返回到STDOUT。例如,Author元素的实例被返回为:
<[’http://www.publishing.org’]::Author> Dhirendra Brahmachari </[’http://www.publishing.org’]::Author>
注意在这个例子中,事件包含一个包含命名空间的开始和结束标签。元素的内容作为字符串在标签内返回。
类似地,Cost元素的实例被返回为:
<[’http://www.publishing.org’]::Cost currency=’INR’> 11.50 </[’http://www.publishing.org’]::Cost
在这种情况下,currency属性和值在事件的开始标签中返回。
javac -classpath ../lib/jaxp-ri.jar stax/event/*.java
java stax/event/EventParse stax/data/BookCatalogue.xml
EventParse将打印出由BookCatalogue.xml文件定义的所有元素的数据。
位于INSTALL_DIR/jaxp-version/samples/stax/filter/目录中,MyStreamFilter.java演示了如何使用StAX流过滤器API来过滤应用程序不需要的事件。在此示例中,解析器会过滤掉除StartElement和EndElement之外的所有事件。
MyStreamFilter类实现了javax.xml.stream.StreamFilter:
public class MyStreamFilter implements javax.xml.stream.StreamFilter { // ... }
下一步是创建XMLInputFactory的实例。在这种情况下,还在工厂上设置了各种属性:
XMLInputFactory xmlif = null ; try { xmlif = XMLInputFactory.newInstance(); xmlif.setProperty( XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.TRUE); xmlif.setProperty( XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE); xmlif.setProperty( XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.TRUE); xmlif.setProperty( XMLInputFactory.IS_COALESCING, Boolean.TRUE); } catch (Exception ex) { ex.printStackTrace(); } System.out.println("FACTORY: " + xmlif); System.out.println("filename = "+ filename);
下一步是实例化一个文件输入流并创建流过滤器:
FileInputStream fis = new FileInputStream(filename); XMLStreamReader xmlr = xmlif.createFilteredReader( xmlif.createXMLStreamReader(fis), new MyStreamFilter()); int eventType = xmlr.getEventType(); printEventType(eventType); while (xmlr.hasNext()) { eventType = xmlr.next(); printEventType(eventType); printName(xmlr,eventType); printText(xmlr); if (xmlr.isStartElement()) { printAttributes(xmlr); } printPIData(xmlr); System.out.println("-----------------------"); }
下一步是捕获事件流。这与事件示例中的方式基本相同。
最后一步是过滤流:
public boolean accept(XMLStreamReader reader) { if (!reader.isStartElement() && !reader.isEndElement()) return false; else return true; }
当你运行过滤器示例时,MyStreamFilter类将被编译,XML流将被解析为事件并返回给STDOUT。例如,一个Author事件将被返回如下:
EVENT TYPE(1):START_ELEMENT HAS NAME: Author HAS NO TEXT HAS NO ATTRIBUTES ----------------------------- EVENT TYPE(2):END_ELEMENT HAS NAME: Author HAS NO TEXT -----------------------------
类似地,一个Cost事件将被返回如下:
EVENT TYPE(1):START_ELEMENT HAS NAME: Cost HAS NO TEXT HAS ATTRIBUTES: ATTRIBUTE-PREFIX: ATTRIBUTE-NAMESP: null ATTRIBUTE-NAME: currency ATTRIBUTE-VALUE: USD ATTRIBUTE-TYPE: CDATA ----------------------------- EVENT TYPE(2):END_ELEMENT HAS NAME: Cost HAS NO TEXT -----------------------------
有关StAX事件解析的更详细讨论,请参阅迭代器API和读取XML流。
javac -classpath ../lib/jaxp-ri.jar stax/filter/*.java
java -Djava.endorsed.dirs=../lib stax/filter/MyStreamFilter -f stax/data/BookCatalogue.xml
MyStreamFilter将按照BookCatalogue.xml文件定义的事件,将其作为XML流输出。
位于INSTALL_DIR/jaxp-version/samples/stax/readnwrite/目录中的EventProducerConsumer.java演示了如何同时将StAX解析器作为生产者和消费者使用。
StAX XMLEventWriter API扩展自XMLEventConsumer接口,被称为事件消费者。相比之下,XMLEventReader是一个事件生产者。StAX支持同时读取和写入,因此可以从一个XML流中顺序读取并同时写入到另一个流中。
读取和写入示例展示了如何使用StAX生产者/消费者机制同时读取和写入。此示例还展示了如何修改流以及如何动态添加新事件,然后将其写入到另一个流中。
第一步是实例化一个事件工厂,然后创建一个事件生产者/消费者的实例:
XMLEventFactory m_eventFactory = XMLEventFactory.newInstance(); public EventProducerConsumer() { // ... try { EventProducerConsumer ms = new EventProducerConsumer(); XMLEventReader reader = XMLInputFactory.newInstance(). createXMLEventReader(new java.io.FileInputStream(args[0])); XMLEventWriter writer = XMLOutputFactory.newInstance().createXMLEventWriter(System.out); } // ... }
下一步是创建一个迭代器来解析流:
while (reader.hasNext()) { XMLEvent event = (XMLEvent)reader.next(); if (event.getEventType() == event.CHARACTERS) { writer.add(ms.getNewCharactersEvent(event.asCharacters())); } else { writer.add(event); } } writer.flush();
最后一步是创建一个流写入器,以新的Character事件的形式:
Characters getNewCharactersEvent(Characters event) { if (event.getData().equalsIgnoreCase("Name1")) { return m_eventFactory.createCharacters( Calendar.getInstance().getTime().toString()); } // else return the same event else { return event; } }
当运行读取和写入示例时,EventProducerConsumer类将被编译,并将XML流解析为事件并写回到STDOUT。输出结果是示例XML文档中描述的BookCatalog.xml文件的内容。
javac -classpath ../lib/jaxp-ri.jar stax/readnwrite/*.java
java stax/readnwrite/EventProducerConsumer stax/data/BookCatalogue.xml
EventProducerConsumer将打印出BookCatalogue.xml文件的内容。
CursorWriter.java位于INSTALL_DIR/jaxp-version/samples/stax/writer/目录中,演示了如何使用StAX游标API编写XML流。
第一步是创建XMLOutputFactory的实例:
XMLOutputFactory xof = XMLOutputFactory.newInstance();
下一步是创建XMLStreamWriter的实例:
XMLStreamWriter xtw = null;
最后一步是写入XML流。注意,在写入最后一个EndDocument之后,流会被刷新和关闭:
xtw = xof.createXMLStreamWriter(new FileWriter(fileName)); xtw.writeComment("这里的所有元素都明确地位于HTML命名空间中"); xtw.writeStartDocument("utf-8","1.0"); xtw.setPrefix("html", "http://www.w3.org/TR/REC-html40"); xtw.writeStartElement("http://www.w3.org/TR/REC-html40","html"); xtw.writeNamespace("html", "http://www.w3.org/TR/REC-html40"); xtw.writeStartElement("http://www.w3.org/TR/REC-html40", "head"); xtw.writeStartElement("http://www.w3.org/TR/REC-html40", "title"); xtw.writeCharacters("Frobnostication"); xtw.writeEndElement(); xtw.writeEndElement(); xtw.writeStartElement("http://www.w3.org/TR/REC-html40", "body"); xtw.writeStartElement("http://www.w3.org/TR/REC-html40", "p"); xtw.writeCharacters("Moved to"); xtw.writeStartElement("http://www.w3.org/TR/REC-html40", "a"); xtw.writeAttribute("href","http://frob.com"); xtw.writeCharacters("here"); xtw.writeEndElement(); xtw.writeEndElement(); xtw.writeEndElement(); xtw.writeEndElement(); xtw.writeEndDocument(); xtw.flush(); xtw.close();
运行写入器示例时,CursorWriter类被编译,并且XML流被解析为事件并写入名为dist/CursorWriter-Output的文件中:
<!--这里的所有元素都明确在HTML命名空间中--> <?xml version="1.0" encoding="utf-8"?> <html:html xmlns:html="http://www.w3.org/TR/REC-html40"> <html:head> <html:title>Frobnostication</html:title></html:head> <html:body> <html:p>转移到<html:a href="http://frob.com">这里</html:a> </html:p> </html:body> </html:html>
在实际的dist/CursorWriter-Output文件中,该流没有任何换行符;这里添加换行是为了更容易阅读清单。在这个例子中,与事件示例中的对象流一样,命名空间前缀添加到了开放和关闭的HTML标签。在StAX规范中并不要求添加此前缀,但在输出流的最终范围不确定时,这是一种好的做法。
javac -classpath \ ../lib/jaxp-ri.jar stax/writer/*.java
java stax/writer/CursorWriter -f output_file
CursorWriter将创建一个相应名称的输出文件,其中包含返回的输出中显示的数据。