我需要解析连续的格式良好的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.*
包)来完成?
答案 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);