概述
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
}
对于“正在构建”的开始元素,您需要确定您实际上正在解析地址,然后将 XML 事件路由到其工作是解释 Address 的方法。
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 中,对于大多数事件调用,您始终需要保留某种状态变量 + 根据该状态路由流。
我推荐Statax用于除最简单的文档之外的所有文档。宁愿稍后移动到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用于速度。