解析没有根元素的 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() 时返回虚拟 <dummyroot>
然后返回有效负载流的结果.这应该满足 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 流的主要内容,如果未能解决你的问题,请参考以下文章