解析没有根元素的XML流

时间:2011-07-10 11:20:35

标签: java xml sax xmlreader

我需要解析连续的格式良好的XML元素流,我只给它一个已构造的java.io.Reader对象。这些元素不包含在根元素中,也不是像<?xml version="1.0"?>"这样的XML标题前缀,而是有效的XML。

使用Java org.xml.sax.XMLReader类不起作用,因为XML Reader期望从封闭的根元素开始解析格式良好的XML。因此,它只读取流中的第一个元素,它将其视为根,并在下一个元素中失败,具有典型的

  

org.xml.sax.SAXParseException:根元素后面的文档中的标记必须格式正确。

对于不包含根元素的文件,但是这样的元素确实存在或者可以定义(并且被称为MyRootElement),可以执行以下操作:

        Strint path = <the full path to the file>;

        XMLReader xmlReader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();

        StringBuilder buffer = new StringBuilder();

        buffer.append("<?xml version=\"1.0\"?>\n");
        buffer.append("<!DOCTYPE MyRootElement ");
        buffer.append("[<!ENTITY data SYSTEM \"file:///");
        buffer.append(path);
        buffer.append("\">]>\n");
        buffer.append("<MyRootElement xmlns:...>\n");
        buffer.append("&data;\n");
        buffer.append("</MyRootElement>\n");

        InputSource source = new InputSource(new StringReader(buffer.toString()));

        xmlReader.parse(source);

我已经通过将部分java.io.Reader输出保存到文件来测试上述内容并且它可以正常运行。但是,这种方法在我的情况下不适用,并且无法插入此类额外信息(XML标头,根元素),因为已经构造了传递给我的代码的java.io.Reader对象。

基本上,我正在寻找“碎片式XML解析”。那么,我的问题是,是否可以使用标准Java API(包括org.sax.xml.*java.xml.*包)来完成?

6 个答案:

答案 0 :(得分:13)

SequenceInputStream来救援:

    SAXParserFactory saxFactory = SAXParserFactory.newInstance();
    SAXParser parser = saxFactory.newSAXParser();

    parser.parse(
        new SequenceInputStream(
            Collections.enumeration(Arrays.asList(
            new InputStream[] {
                new ByteArrayInputStream("<dummy>".getBytes()),
                new FileInputStream(file),//bogus xml
                new ByteArrayInputStream("</dummy>".getBytes()),
            }))
        ), 
        new DefaultHandler()
    );

答案 1 :(得分:9)

您可以将给定的Reader包装在您实现的FilterReader子类中,以便在此处执行更多或更少的操作。

修改

虽然这类似于实施您自己的Reader委托给其他答案给出的Reader对象的提案,但FilterReader中的所有方法都必须是被覆盖,因此使用超类可能无法获得太多收益。

其他提案的一个有趣的变体可能是实现一个SequencedReader包装多个Reader对象,并在一个用完时转移到序列中的下一个。然后,您可以传入一个StringReader对象,其中包含您要添加的根的原始文本,原始Reader和另一个带有结束标记的StringReader

答案 2 :(得分:5)

您可以编写自己的Reader-Implementation,封装您给出的Reader实例。这个新的Reader应该只是你在你的示例代码中做的事情,提供标题和根元素,然后是底层读者的数据,最后是结束的根标记。通过这种方式,您可以向XML解析器提供有效的XML流,您也可以使用传递给代码的Reader对象。

答案 3 :(得分:3)

只需插入虚拟根元素即可。我能想到的最优雅的解决方案是创建自己的InputStream或Reader,它包装常规的InputSteam / Reader,并在第一次调用read()/ readLine()时返回虚拟<dummyroot>,然后返回结果有效载荷流。这应该满足SAX解析器。

答案 4 :(得分:3)

您可以创建自己的Reader,委托给提供的Reader,如下所示:

final Reader reader = <whatever you are getting>;

Reader wrappedReader = new Reader()
{
    Reader readerCopy = reader;
    String start = "<?xml version=\"1.0\"?><MyRootElement>";
    String end = "</MyRootElement>";
    int index;

    @Override
    public void close() throws IOException
    {
        readerCopy.close();
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException
    {
        // You'll have to get the logic right here - this is only placeholder code

        if (index < start.length())
        {
            // Copy from start to cbuf
        }
        int result = readerCopy.read(cbuf, off, len);

        if (result == -1) {
            // Copy from end
        }

        index += len; 

        return result;
    }
};

您必须填写逻辑以首先从start读取,然后委托给中间的读者,最后当读者为空时,从end读取。

这种方法可行。

答案 5 :(得分:2)

答案3有效,但对我来说,我必须从SequenceInputStream创建一个输入源的额外步骤。

XMLReader xmlReader = saxParser.getXMLReader();
xmlReader.setContentHandler((ContentHandler) this);
// Trying to add root element
Enumeration<InputStream> streams = Collections.enumeration(
    Arrays.asList(new InputStream[] {
        new ByteArrayInputStream("<TopNode>".getBytes()),
        new FileInputStream(xmlFile),//bogus xml
        new ByteArrayInputStream("</TopNode>".getBytes()),
}));
InputSource is = new InputSource(seqStream);
xmlReader.parse(is);