解析没有根元素的 XML 流

Posted

技术标签:

【中文标题】解析没有根元素的 XML 流【英文标题】:Parsing an XML stream with no root element 【发布时间】:2011-10-02 05:11:03 【问题描述】:

我需要解析一个连续的格式良好的 XML 元素流,我只得到一个已经构造好的 java.io.Reader 对象。这些元素没有包含在根元素中,也没有以像 <?xml version="1.0"?>" 这样的 XML 标头作为前缀,而是在其他方面是有效的 XML。

使用 Java org.xml.sax.XMLReader 类不起作用,因为 XML 阅读器希望从封闭的根元素开始解析格式良好的 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.* 包)来完成?

【问题讨论】:

可以参考Resolving "The markup in the document following the root element must be well-formed" Exception 【参考方案1】:

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

【讨论】:

【参考方案2】:

您可以将给定的Reader 包装在您实现的FilterReader 子类中,以或多或少地执行您在此处所做的事情。

编辑:

虽然这类似于实现您自己的 Reader 委托给由其他几个答案给出的给定 Reader 对象的提议,但 FilterReader 中的几乎所有方法都必须被覆盖,所以您可能不会从使用超类中获益良多。

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

【讨论】:

【参考方案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读取。

这种方法虽然可行。

【讨论】:

但是真的没有可以读取“碎片化”XML的XML解析类吗?【参考方案4】:

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

【讨论】:

+1 伟大的思想都一样(虽然我的想法比你早 1 分钟 :)) +1 给你们俩。直接实现 Reader 可能比在我的回复中尝试继承 FilterReader 更好。【参考方案5】:

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

【讨论】:

【参考方案6】:

This answer 对我有用,但我必须执行从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);

【讨论】:

通常答案是重新排序的,所以“答案3”是相对的,你的意思是什么答案? 我的意思是user656449给出的答案 不编译 - 注意 'seqStream' 未在任何地方定义,重命名为流会为新 InputSource 生成“没有合适的构造函数”错误。

以上是关于解析没有根元素的 XML 流的主要内容,如果未能解决你的问题,请参考以下文章

Android XML解析错误:只允许一个根元素

如何解析只有根元素的 XML

XML 解析错误:找不到根元素位置

XML 解析错误:找不到根元素

XML 解析错误:找不到根元素

XML解析