使用SAX解析常用XML元素
我目前使用SAX(Java)来解析少数不同的XML文档,每个文档都代表不同的数据,并且结构略有不同。由于这个原因,每个XML文档都由不同的SAX类处理(子类DefaultHandler
)。使用SAX解析常用XML元素
但是,有一些XML结构可以出现在所有这些不同的文档中。理想情况下,我想告诉解析器“嗨,当你到达一个complex_node
元素时,只需使用ComplexNodeHandler
来读取它,并将结果返回给我。如果达到some_other_node
,请使用OtherNodeHandler
来读取它并让我知道结果”。
但是,我看不到一个明显的方法来做到这一点。
我应该只是做一个单片处理程序类,它可以读取我拥有的所有不同文档(并根除代码重复),还是有更智能的方法来处理?
下面是我对类似问题的一个答案(Skipping nodes with sax)。它演示了如何交换XMLReader上的内容处理程序。
在这个例子中,ContentHandler中的交换只是忽略了所有的事件,直到它放弃控制,但是你可以很容易地修改这个概念。
你可以做类似如下:
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.XMLReader;
public class Demo {
public static void main(String[] args) throws Exception {
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
XMLReader xr = sp.getXMLReader();
xr.setContentHandler(new MyContentHandler(xr));
xr.parse("input.xml");
}
}
MyContentHandler
这个类是负责处理XML文档。当你点击一个你想忽略的节点时,你可以交换IgnoringContentHandler,它将吞下该节点的所有事件。
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
public class MyContentHandler implements ContentHandler {
private XMLReader xmlReader;
public MyContentHandler(XMLReader xmlReader) {
this.xmlReader = xmlReader;
}
public void setDocumentLocator(Locator locator) {
}
public void startDocument() throws SAXException {
}
public void endDocument() throws SAXException {
}
public void startPrefixMapping(String prefix, String uri)
throws SAXException {
}
public void endPrefixMapping(String prefix) throws SAXException {
}
public void startElement(String uri, String localName, String qName,
Attributes atts) throws SAXException {
if("sodium".equals(qName)) {
xmlReader.setContentHandler(new IgnoringContentHandler(xmlReader, this));
} else {
System.out.println("START " + qName);
}
}
public void endElement(String uri, String localName, String qName)
throws SAXException {
System.out.println("END " + qName);
}
public void characters(char[] ch, int start, int length)
throws SAXException {
System.out.println(new String(ch, start, length));
}
public void ignorableWhitespace(char[] ch, int start, int length)
throws SAXException {
}
public void processingInstruction(String target, String data)
throws SAXException {
}
public void skippedEntity(String name) throws SAXException {
}
}
IgnoringContentHandler
当IgnoringContentHandler做吞咽控制向后传递到主ContentHandler事件。
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
public class IgnoringContentHandler implements ContentHandler {
private int depth = 1;
private XMLReader xmlReader;
private ContentHandler contentHandler;
public IgnoringContentHandler(XMLReader xmlReader, ContentHandler contentHandler) {
this.contentHandler = contentHandler;
this.xmlReader = xmlReader;
}
public void setDocumentLocator(Locator locator) {
}
public void startDocument() throws SAXException {
}
public void endDocument() throws SAXException {
}
public void startPrefixMapping(String prefix, String uri)
throws SAXException {
}
public void endPrefixMapping(String prefix) throws SAXException {
}
public void startElement(String uri, String localName, String qName,
Attributes atts) throws SAXException {
depth++;
}
public void endElement(String uri, String localName, String qName)
throws SAXException {
depth--;
if(0 == depth) {
xmlReader.setContentHandler(contentHandler);
}
}
public void characters(char[] ch, int start, int length)
throws SAXException {
}
public void ignorableWhitespace(char[] ch, int start, int length)
throws SAXException {
}
public void processingInstruction(String target, String data)
throws SAXException {
}
public void skippedEntity(String name) throws SAXException {
}
}
嗯,没有意识到XMLReader可以通过这种方式进行即时更改。绝对看起来像是最好的方式来处理它。 – Dave 2010-08-05 08:09:40
XMLReader被设计成可以做到这一点,请参考http://download-llnw.oracle.com/javase/6/docs/api/org/xml/sax/XMLReader.html#setContentHandler(org.xml.sax.ContentHandler ),在做SAX处理时,我们在我们的JAXB实现MOXy中使用了这个,我们为每个对象构建一个ContentHandler。 – 2010-08-05 13:17:18
@Blaise Doughan首先感谢您的解决方案,这正是我一直在寻找的。我有一个问题。在评估结构深度知道何时返回主内容处理程序的背后是否有任何特殊想法?为此目的使用endDocument()方法有任何问题吗? – 2010-09-20 10:20:06
您可以拥有一个只处理文档某些部分(complex_node)的处理程序(ComplexNodeHandler),并将所有其他部分传递给另一个处理程序。 ComplexNodeHandler的构造函数会将另一个处理程序作为参数。我的意思是这样的:
class ComplexNodeHandler {
private ContentHandler handlerForOtherNodes;
public ComplexNodeHandler(ContentHandler handlerForOtherNodes) {
this.handlerForOtherNodes = handlerForOtherNodes;
}
...
public startElement(String uri, String localName, String qName, Attributes atts) {
if (currently in complex node) {
[handle complex node data]
} else {
// pass the event to the document specific handler
handlerForOtherNodes.startElement(uri, localName, qName, atts);
}
}
...
}
可能有更好的替代品,因为我不熟悉SAX。编写公共部分的基本处理程序并继承它可能会起作用,但我不确定在这里使用继承是个好主意。
我考虑过这个,但很快就确定它会变得相当复杂。我不得不转发来自不仅仅是'startElement',而是'endElement','''字符'和错误处理程序的调用。 – Dave 2010-08-05 08:08:32
我希望/确定我刚刚错过了一些痛苦明显的解决方案! – Dave 2010-08-04 12:58:08
是SAX的要求吗?如何使用xpath与DOM,XOM或vtd-xmL? – 2011-03-01 21:33:47
因为SAX是最快的并且使用最少的内存,这在移动设备上很重要(当我最初问这个问题时,我忽略了这一点)。 – Dave 2011-03-01 21:54:35