概述
XML 文档是分层文档,其中相同的元素名称和名称空间可能出现在多个位置,具有不同的含义,并且具有不定式深度(递归)。通常,解决大问题的方法是将它们分成小问题。在 XML 解析的上下文中,这意味着以特定于 XML 的方法解析 XML 的特定部分。例如,一段逻辑会解析一个地址:
<Address>
<Street>Odins vei</Street>
<Building>4</Building>
<Door>b</Door>
</Address>
即你会有一个方法
AddressType parseAddress(...); // A
或者
void parseAddress(...); // B
在您的逻辑中的某处,获取 XML 输入参数并返回一个对象(稍后可以从字段中获取 B 的结果)。
SAX
SAX '推送' XML事件,让您自己决定 XML 事件在您的程序/数据中的位置。
// method in stock SAX handler
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
// .. your logic here for start element
}
如果是“Building”开始元素,您需要确定您实际上是在解析地址,然后将 XML 事件路由到负责解释地址的方法。
StAX
StAX “拉” XML事件,由您决定在程序/数据中的何处接收 XML 事件。
// method in standard StAX reader
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
// .. your logic here for start element
}
当然,您总是希望在其工作是解释地址的方法中接收“建筑”事件。
讨论
SAX 和 StAX 之间的区别在于推和拉。在这两种情况下,必须以某种方式处理解析状态。
这转换为 SAX 的典型方法 B,StAX 的方法 A。此外,SAX 必须给 B 单独的 XML 事件,而 StAX 可以给 A 多个事件(通过传递 XMLStreamReader 实例)。
因此,B 首先检查解析的先前状态,然后处理每个单独的 XML 事件,然后存储状态(在字段中)。方法 A 可以通过多次访问 XMLStreamReader 直到满意,一次处理所有 XML 事件。
结论
StAX 允许您根据 XML 结构构建解析(数据绑定)代码;所以就 SAX 而言,StAX 的程序流中隐含了“状态”,而在 SAX 中,对于大多数事件调用,您总是需要根据该状态保留某种状态变量 + 路由流。
除了最简单的文档之外,我推荐使用 StAX。而是稍后将其作为优化转移到 SAX(但到那时您可能希望使用二进制)。
使用 StAX 解析时遵循此模式:
public MyDataBindingObject parse(..) { // provide input stream, reader, etc
// set up parser
// read the root tag to get to level 1
XMLStreamReader reader = ....;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
// check if correct root tag
break;
}
// add check for document end if you want to
} while(reader.hasNext());
MyDataBindingObject object = new MyDataBindingObject();
// read root attributes if any
int level = 1; // we are at level 1, since we have read the document header
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
level++;
// do stateful stuff here
// for child logic:
if(reader.getLocalName().equals("Whatever1")) {
WhateverObject child = parseSubTreeForWhatever(reader);
level --; // read from level 1 to 0 in submethod.
// do something with the result of subtree
object.setWhatever(child);
}
// alternatively, faster
if(level == 2) {
parseSubTreeForWhateverAtRelativeLevel2(reader);
level --; // read from level 1 to 0 in submethod.
// do something with the result of subtree
object.setWhatever(child);
}
} else if(event == XMLStreamConstants.END_ELEMENT) {
level--;
// do stateful stuff here, too
}
} while(level > 0);
return object;
}
所以子方法使用了大致相同的方法,即计数级别:
private MySubTreeObject parseSubTree(XMLStreamReader reader) throws XMLStreamException {
MySubTreeObject object = new MySubTreeObject();
// read element attributes if any
int level = 1;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
level++;
// do stateful stuff here
// for child logic:
if(reader.getLocalName().equals("Whatever2")) {
MyWhateverObject child = parseMySubelementTree(reader);
level --; // read from level 1 to 0 in submethod.
// use subtree object somehow
object.setWhatever(child);
}
// alternatively, faster, but less strict
if(level == 2) {
MyWhateverObject child = parseMySubelementTree(reader);
level --; // read from level 1 to 0 in submethod.
// use subtree object somehow
object.setWhatever(child);
}
} else if(event == XMLStreamConstants.END_ELEMENT) {
level--;
// do stateful stuff here, too
}
} while(level > 0);
return object;
}
然后最终你会达到阅读基本类型的水平。
private MySetterGetterObject parseSubTree(XMLStreamReader reader) throws XMLStreamException {
MySetterGetterObject myObject = new MySetterGetterObject();
// read element attributes if any
int level = 1;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
level++;
// assume <FirstName>Thomas</FirstName>:
if(reader.getLocalName().equals("FirstName")) {
// read tag contents
String text = reader.getElementText()
if(text.length() > 0) {
myObject.setName(text)
}
level--;
} else if(reader.getLocalName().equals("LastName")) {
// etc ..
}
} else if(event == XMLStreamConstants.END_ELEMENT) {
level--;
// do stateful stuff here, too
}
} while(level > 0);
// verify that all required fields in myObject are present
return myObject;
}
这很简单,没有误解的余地。请记住正确降低级别:
A. 在您预期字符但在某些应包含字符的标记中获得 END_ELEMENT 之后(在上述模式中):
<Name>Thomas</Name>
而是
<Name></Name>
丢失的子树也是如此,你明白了。
B. 调用子解析方法后,在开始元素上调用,并在相应的结束元素后返回,即解析器比方法调用前低一级(上述模式)。
请注意,这种方法也完全忽略了“可忽略”的空白,以实现更健壮的实现。
解析器
使用Woodstox获得大多数功能或使用 Aaalto-xml获得速度。