没有根元素的XML流解析

17
我需要解析一个连续的、格式良好的XML元素流,我只能获得一个已经构建好的java.io.Reader对象。这些元素没有包含在根元素中,也没有像<?xml version="1.0"?>这样的XML头,但它们是有效的XML。
使用Java的org.xml.sax.XMLReader类不起作用,因为XML Reader期望解析以封闭根元素开始的格式良好的XML。所以它只读取流中的第一个元素,将其视为根,并在下一个元素中失败,产生典型的异常:

org.xml.sax.SAXParseException: The markup in the document following the root element must be well-formed.

对于不包含根元素但存在或可以定义该元素(称为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个回答

15

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()
    );

9
您可以将给定的Reader包装在您实现的FilterReader子类中,以执行更多或更少与此处所做的相同操作。 编辑: 虽然这与其他答案提供的实现自己的委托给给定Reader对象的Reader类似,但几乎必须重写FilterReader中的所有方法,因此您可能无法从使用超类中获得太多好处。
对其他提议的有趣变化可能是实现一个SequencedReader,它包装了多个Reader对象,并在一个被用尽时转移到序列中的下一个。 然后,您可以传递一个带有要添加的根起始文本的StringReader对象,原始的Reader和另一个带有结束标记的StringReader

5
你可以编写自己的Reader实现来封装你所得到的Reader实例。这个新的Reader应该做和你示例代码中一样的事情,提供头部和根元素,然后提供基础reader中的数据,并在最后提供结束的根标记。通过这种方式,你可以向XML解析器提供一个有效的XML流,同时也可以使用传递给你代码的Reader对象。

+1 伟大的思想总是相似的(尽管我的比你的早了一分钟 :) ) - Bohemian
+1 给你们俩。直接实现一个 Reader 可能比尝试在我的回答中子类化 FilterReader 更好。 - Don Roby

4
你可以创建一个委托给提供的 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读取。这种方法虽然可行。

但是难道真的没有任何可以读取“分段”XML的XML解析类吗? - PNS

3

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


2
这个答案对我有用,但我还需要额外的步骤,从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);

通常答案会被重新排序,所以“第三个答案”是相对的,你指的是哪一个答案? - emecas
我指的是用户656449给出的答案。 - Anita Kulkarni
无法编译 - 注意 'seqStream' 在任何地方都没有定义,并且将其重命名为 'stream' 会为新的 InputSource 生成一个 'no suitable constructor' 错误。 - Paul Hargreaves

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