关于java:解析没有根元素的XML流

Parsing an XML stream with no root element

我需要解析一个连续的格式良好的XML元素流,只给我一个已经构造好的java.io.Reader对象。这些元素没有包含在根元素中,也没有用像"这样的XML头作为前缀,但在其他方面是有效的XML。

使用JavaEDCX1的2个类不起作用,因为XML读取器期望解析封闭格式的XML,从封闭的根元素开始。所以,它只读取流中的第一个元素,它将其视为根,并在下一个元素中失败,典型的

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

对于不包含根元素的文件,但如果该元素确实存在或可以定义(并称为myrootelement),则可以执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
        Strint path = <the full path to the file>;

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

        StringBuilder buffer = new StringBuilder();

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

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

        xmlReader.parse(source);

我已经通过将部分java.io.Reader输出保存到一个文件中来测试了上面的内容,它可以工作。但是,这种方法在我的例子中不适用,并且不能插入这样的额外信息(XML头、根元素),因为传递给我的代码的java.io.Reader对象已经被构造出来了。

本质上,我在寻找"零碎的XML解析"。所以,我的问题是,是否可以使用标准的Java API(包括EDCOX1的5)和EDCOX1(6个)包来完成?


Sequenceinputstream来救援:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    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对象的提议类似,但几乎FilterReader中的所有方法都必须被重写,因此使用超类可能不会获得太多好处。

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


您可以编写自己的读卡器实现来封装给定的读卡器实例。这个新的阅读器应该像您在示例代码中所做的那样,提供头和根元素,然后提供来自底层阅读器的数据,最后提供结束根标记。通过这种方式,您可以向XML解析器提供有效的XML流,还可以使用传递给代码的reader对象。


您可以创建自己的读卡器,将其委托给提供的读卡器,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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中读取。

不过,这种方法是可行的。


只需插入虚拟根元素。我能想到的最优雅的解决方案是创建自己的inputstream或reader,当您第一次调用它的read()/readline()时,它包装常规inputsteam/reader并返回虚拟的,然后返回有效负载流的结果。这应该满足SAX解析器。


答案3是可行的,但对我来说,我必须从Sequenceinputstream创建一个inputsource的额外步骤。

1
2
3
4
5
6
7
8
9
10
11
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);