概述
XML文档是层次结构的文档,其中相同的元素名称和名称空间可能出现在多个地方,具有不同的含义,并且具有不确定的深度(递归)。通常,解决大问题的方法是将它们分为小问题。在XML解析的上下文中,这意味着使用特定于XML的方法来解析XML的特定部分。例如,一种逻辑将解析一个地址:
<Address>
<Street>Odins vei</Street>
<Building>4</Building>
<Door>b</Door>
</Address>
即你将有一种方法
AddressType parseAddress(...);
要么
void parseAddress(...);
在您逻辑的某个位置,使用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”开始元素,则需要确定您实际上是在解析一个Address,然后将XML事件路由到用于解释Address的方法。
StAX
StAX“拉” XML事件,由您自己决定在程序/数据中的哪个位置接收XML事件。
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
}
当然,您总是希望通过其方法来解释Address的方法中收到一个“ Building”事件。
讨论
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(..) {
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>
对于丢失的子树也是如此,您就会明白。
B.在调用子解析方法之后,这些方法在开始元素上被调用,并在相应的结束元素之后返回,即解析器比方法调用之前低一级(上述模式)。
请注意,这种方法也是如何完全忽略“可忽略的”空白,以实现更可靠的实现。
解析器
使用Woodstox来实现大多数功能,而使用Aaalto-xml来提高速度。