繁体   English   中英

解析没有根元素的 XML stream

[英]Parsing an XML stream with no root element

I need to parse a continuous stream of well-formed XML elements, to which I am only given an already constructed java.io.Reader object. These elements are not enclosed in a root element, nor are they prepended with an XML header like <?xml version="1.0"?>" , but are otherwise valid XML.

Using the Java org.xml.sax.XMLReader class does not work, because the XML Reader expects to parse well-formed XML, starting with an enclosing root element. 因此,它只读取 stream 中的第一个元素,它认为它是根,并在下一个元素中失败,典型的

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 output 的一部分保存到文件来测试上述内容,并且它可以工作。 However, this approach is not applicable in my case and such extra information (XML header, root element) cannot be inserted, since the java.io.Reader object passed to my code is already constructed.

本质上,我正在寻找“碎片 XML 解析”。 So, my question is, can it be done, using standard Java APIs (including the org.sax.xml.* and java.xml.* packages)?

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

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

编辑:

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

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

您可以编写自己的 Reader-Implementation 来封装您获得的 Reader-instance。 这个新的阅读器应该做您在示例代码中所做的事情,提供 header 和根元素,然后是来自底层阅读器的数据,最后是结束根标签。 By going this way you can provide a valid XML stream to the XML parser and you can as well use the Reader object passed to your code.

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

您可以创建自己的 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读取,然后委托给中间的 reader,最后当 reader 为空时,从end读取。

不过,这种方法会奏效。

这个答案对我有用,但我必须执行从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);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM