使用 dom4j 从流中读取单个 XML 文档
题
我尝试使用 dom4j 一次从流中读取一个 XML 文档,对其进行处理,然后继续处理流中的下一个文档。不幸的是,dom4j 的 SAXReader(在幕后使用 JAXP)持续读取并阻塞了以下文档元素。
有没有办法让 SAXReader 在找到文档元素的末尾后停止读取流?有更好的方法来实现这一点吗?
解决方案
我可以使用一些内部 JAXP 类将其与一些体操一起使用:
- 创建自定义扫描仪,XMLNSDocumentScannerImpl 的子类
- 在自定义扫描器内创建一个自定义驱动程序,即 XMLNSDocumentScannerImpl.Driver 的实现,当它看到声明或元素时返回 END_DOCUMENT。从 fElementScanner.getCurrentEntity() 获取 ScannedEntity。如果实体有 PushbackReader,则将实体缓冲区中剩余的未读字符推回到阅读器上。
- 在构造函数中,将 fTrailingMiscDriver 替换为此自定义驱动程序的实例。
- 创建一个自定义配置类,它是 XIncludeAwareParserConfiguration 的子类,在其构造函数中用此自定义扫描仪的实例替换库存 DOCUMENT_SCANNER。
- 安装此自定义配置类的实例作为“com.sun.org.apache.xerces.internal.xni.parser.XMLParserConfiguration”属性,以便在 dom4j 的 SAXReader 类尝试创建 JAXP XMLReader 时将其实例化。
- 将 Reader 传递给 dom4j 的 SAXReader.read() 方法时,提供一个缓冲区大小比默认的单字符大得多的 PushbackReader。至少 8192 应该足以支持 JAXP 的 Apache2 副本内 XMLEntityManager 的默认缓冲区大小。
这不是最干净的解决方案,因为它涉及内部 JAXP 类的子类化,但它确实有效。
其他提示
最有可能的是,您不希望同时在同一个流中包含多个文档。我不认为SAXReader足够智能,当它到达第一个文档的末尾时就停止了。为什么需要在同一个流中包含多个文档?
我认为你必须添加一个适配器,用来包装流并让它在看到下一个文档的开头时返回文件末尾。据我所知,编写的解析器将一直运行到文件末尾或错误...并且看到另一个<?xml version="1.0"?>
肯定会出错。
假设您负责首先将文档放入流中,应该很容易以某种方式划分文档。例如:
// Any value that is invalid for an XML character will do. static final char DOC_TERMINATOR=4; BOOL addDocumentToStream(BufferedWriter streamOut, char xmlData[]) { streamOut.write(xmlData); streamOut.write(DOC_TERMINATOR); }
然后从流中读取读入数组直到遇到DOC_TERMINATOR。
char *getNextDocuument(BufferedReader streamIn) { StringBuffer buffer = new StringBuffer(); int character; while (true) { character = streamIn.read(); if (character == DOC_TERMINATOR) break; buffer.append(character); } return buffer.toString().toCharArray(); }
由于4是无效的字符值,除非您明确添加它,否则不会遇到。因此允许您拆分文档。现在只需将结果char数组包装起来输入SAX即可。
... XMLReader xmlReader = XMLReaderFactory.createXMLReader(); ... while (true) { char xmlDoc = getNextDocument(streamIn); if (xmlDoc.length == 0) break; InputSource saxInputSource = new InputSource(new CharArrayReader(xmlDoc)); xmlReader.parse(saxInputSource); } ...
请注意,循环在获取长度为0的文档时终止。这意味着您应该在最后一个文档之后添加第二个DOC_TERMINATOR,以便在getNextDocument()中添加一些内容来检测流的结尾。 / p>
我之前已经通过使用我自己创建的另一个具有非常简单的解析功能的读者包装基本读取器来完成此操作。假设您知道文档的结束标记,则包装器只需解析匹配项,例如for <!> quot; <!> lt; / MyDocument <!> gt; <!> quot;。当它检测到它返回EOF时。通过解析出第一个开始标记并在匹配的结束标记上返回EOF,可以使包装器自适应。我发现没有必要实际检测结束标记的级别,因为没有文档我自己使用了文档标记,所以保证了第一次出现的结束标记结束了文档。
我记得,其中一个技巧是让封装器块close(),因为DOM读取器会关闭输入源。
因此,给定Reader输入,您的代码可能看起来像:
SubdocReader sdr=new SubdocReader(input);
while(!sdr.eof()) {
sdr.next();
// read doc here using DOM
// then process document
}
input.close();
如果遇到EOF,则eof()方法返回true。 next()方法标记读取器停止为read()返回-1。
希望这能为您指明一个有用的方向。
- 猕猴桃。
我会将输入流读入内部缓冲区。根据预期的总流大小,我会读取整个流,然后解析它或检测一个xml和下一个xml之间的边界(查找
处理带有一个xml的流和带有多个xmls的流之间唯一真正的区别是缓冲区和拆分逻辑。