使用SAX解析常见的XML元素

11

我目前正在使用SAX (Java)解析几个不同的XML文档,每个文档都代表着不同的数据且结构略有不同。因此,每个XML文档都由不同的SAX类来处理 (继承 DefaultHandler)。

然而,有些XML结构可能出现在所有这些不同的文档中。理想情况下,我希望告诉解析器:“嘿,当你遇到complex_node元素时,只需使用ComplexNodeHandler来读取它,并将结果返回给我。如果你遇到some_other_node,使用OtherNodeHandler来读取它,并将结果返回给我。”

然而,我看不出有明显的方法可以实现这一点。

我应该只是制作一个可以解析我所有不同文档的单体处理程序类(并消除代码重复),还是有更聪明的方法来处理这个问题?


我希望/确信我只是错过了一些非常明显的解决方案! - Dave
SAX是必需的吗?使用DOM、XOM或vtd-XML与XPath如何? - vtd-xml-author
因为SAX是最快的,且使用的内存最少,在移动设备上这一点非常重要(当我最初提出这个问题时,我忽略了这一点)。 - Dave
2个回答

16

以下是我对类似问题 (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
XMLReader 就是为此而设计的,参考 http://download-llnw.oracle.com/javase/6/docs/api/org/xml/sax/XMLReader.html#setContentHandler(org.xml.sax.ContentHandler) ,在我们的 JAXB 实现 MOXy 中,当进行 SAX 处理时,我们会为每个正在构建的对象使用一个 ContentHandler。 - bdoughan
@Blaise Doughan 首先感谢您提供的解决方案,正是我一直在寻找的。但我有一个问题。在评估结构深度以确定何时返回到主内容处理程序时,是否有任何特殊的考虑?使用endDocument()方法是否有任何问题? - Octavian Helm
@Blaise Doughan,是的,我现在明白了。那就是我想说的。我的问题不是一个单独的解析操作,而是有多个不同的解析操作,其结构也不同。但这个解决方案对我也有效。我只是不必再切换回来。 :) - Octavian Helm
@OctavianDamiean 这似乎正是我正在寻找的。但是,一旦您将处理程序更改为不忽略而解析为对象,如何获取解析后的对象呢?我无法想象。 - Jeff
显示剩余3条评论

1

你可以有一个处理程序(ComplexNodeHandler),它仅处理文档的某些部分(complex_node),并将所有其他部分传递给另一个处理程序。 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,还需要转发endElementcharacters和错误处理程序的调用。 - Dave

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接