概述XML文档是分层的文档,相同的元素名称和命名空间可能出现在多个位置,具有不同的含义,并且深度无限(递归)。通常情况下,解决大问题的方法是将其分解为小问题。在XML解析的上下文中,这意味着使用特定于该XML的方法解析XML的特定部分。例如,一个逻辑块会解析一个地址:
<Address>
<Street>Odins vei</Street>
<Building>4</Building>
<Door>b</Door>
</Address>
即你将拥有一个方法
AddressType parseAddress(...)
或者
void parseAddress(...); // B
在你的逻辑中的某个地方,接受XML输入参数并返回一个对象(B的结果可以后续从字段中获取)。
SAX
SAX“推送”XML事件,让您确定XML事件在程序/数据中的位置。
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
}
如果遇到“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
}
当然,您总是希望在负责解释地址的方法中接收到“Building”事件。
讨论
SAX和StAX之间的区别在于推和拉。在两种情况下,都必须以某种方式处理解析状态。
这意味着对于SAX,方法B是典型的,而对于StAX,则是方法A。此外,SAX必须提供B个单独的XML事件,而StAX可以通过传递XMLStreamReader实例来提供多个事件(A)。
因此,B首先检查解析的先前状态,然后处理每个单独的XML事件,然后存储状态(在字段中)。方法A可以通过多次访问XMLStreamReader来一次性处理所有XML事件,直到满意为止。
结论
StAX使您可以根据XML结构组织解析(数据绑定)代码; 因此,与SAX相比,对于StAX,“状态”从程序流中是隐含的,而对于大多数事件调用,在SAX中,您总是需要保留某种状态变量+根据该状态路由流程。
我建议使用StAX来解析除最简单文档以外的所有文档。稍后再将其优化为SAX(但到那时,您可能想要转到二进制)。
按照以下模式使用StAX进行解析:
public MyDataBindingObject parse(..) {
XMLStreamReader reader = ....;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
break;
}
} while(reader.hasNext());
MyDataBindingObject object = new MyDataBindingObject();
int level = 1;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
level++;
if(reader.getLocalName().equals("Whatever1")) {
WhateverObject child = parseSubTreeForWhatever(reader);
level --;
object.setWhatever(child);
}
if(level == 2) {
parseSubTreeForWhateverAtRelativeLevel2(reader);
level --;
object.setWhatever(child);
}
} else if(event == XMLStreamConstants.END_ELEMENT) {
level--;
}
} while(level > 0);
return object;
}
因此,子方法使用了大致相同的方法,即计算水平:
private MySubTreeObject parseSubTree(XMLStreamReader reader) throws XMLStreamException {
MySubTreeObject object = new MySubTreeObject();
int level = 1;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
level++;
if(reader.getLocalName().equals("Whatever2")) {
MyWhateverObject child = parseMySubelementTree(reader);
level --;
object.setWhatever(child);
}
if(level == 2) {
MyWhateverObject child = parseMySubelementTree(reader);
level --;
object.setWhatever(child);
}
} else if(event == XMLStreamConstants.END_ELEMENT) {
level--;
}
} while(level > 0);
return object;
}
最终,您将达到一定的水平,可以阅读基本类型。
private MySetterGetterObject parseSubTree(XMLStreamReader reader) throws XMLStreamException {
MySetterGetterObject myObject = new MySetterGetterObject();
int level = 1;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
level++;
if(reader.getLocalName().equals("FirstName")) {
String text = reader.getElementText()
if(text.length() > 0) {
myObject.setName(text)
}
level--;
} else if(reader.getLocalName().equals("LastName")) {
}
} else if(event == XMLStreamConstants.END_ELEMENT) {
level--;
}
} while(level > 0);
return myObject;
}
这很简单,没有什么可以误解的地方。只需记得正确递减级别:
A. 当你期望字符但在某个标签中得到了一个 END_ELEMENT 时(在上面的模式中),应该包含字符:
<Name>Thomas</Name>
被替代了
<Name></Name>
同样,缺失的子树也是如此,你明白了吧。
在调用子解析方法之后,它们被称为开始元素,并在相应的结束元素之后返回,即解析器比方法调用前低一级(上述模式)。
请注意,这种方法完全忽略了“可忽略的”空格,以实现更健壮的实现。
解析器
使用
Woodstox获取大多数功能或使用
Aalto-xml提高速度。