我正在尝试使用 dom4j 一次从流中读取单个 XML 文档,对其进行处理,然后继续处理流中的下一个文档。不幸的是,dom4j 的 SAXReader(在幕后使用 JAXP)一直在读取并阻塞在下面的文档元素上。
有没有办法让 SAXReader 在找到文档元素的结尾后停止读取流?有没有更好的方法来实现这一点?
我能够使用一些内部 JAXP 类使其与一些体操一起工作:
这不是最干净的解决方案,因为它涉及子类化内部 JAXP 类,但它确实有效。
最有可能的是,您不希望在同一流中同时拥有多个文档。我不认为 SAXReader 足够聪明,不会在它到达第一个文档的末尾时停止。为什么有必要像这样在同一个流中有多个文档?
我认为您必须添加一个适配器,用于包装流并让该东西在看到下一个文档的开头时返回文件结尾。据我所知,所写的解析器会一直运行到文件末尾或出现错误……看到另一个<?xml version="1.0"?>
肯定是错误的。
假设您首先负责将文档放入流中,应该很容易以某种方式分隔文档。例如:
// 任何对 XML 字符无效的值都可以。 静态最终字符 DOC_TERMINATOR=4; BOOL addDocumentToStream(BufferedWriter streamOut, char xmlData[]) { streamOut.write(xmlData); streamOut.write(DOC_TERMINATOR); }
然后在从流中读取时读入一个数组,直到遇到 DOC_TERMINATOR。
char *getNextDocuument(BufferedReader streamIn) { StringBuffer 缓冲区 = new StringBuffer(); 整数字符; 而(真) { 字符 = streamIn.read(); 如果(字符 == DOC_TERMINATOR) 休息; buffer.append(字符); } 返回缓冲区.toString().toCharArray(); }
由于 4 是一个无效的字符值,除非您明确添加它,否则您不会遇到。从而允许您拆分文档。现在只需将生成的 char 数组包装为 SAX 的输入就可以了。
... XMLReader xmlReader = XMLReaderFactory.createXMLReader(); ... 而(真) { char xmlDoc = getNextDocument(streamIn); 如果(xmlDoc.length == 0) 休息; InputSource saxInputSource = new InputSource(new CharArrayReader(xmlDoc)); xmlReader.parse(saxInputSource); } ...
请注意,循环在获得长度为 0 的文档时终止。这意味着您应该在最后一个文档之后添加第二个 DOC_TERMINATOR,您需要在 getNextDocument() 中添加一些内容以检测流的结尾。
我之前通过将基本阅读器与我自己创建的具有非常简单的解析能力的另一个阅读器包装在一起来做到这一点。假设您知道文档的结束标记,包装器会简单地解析匹配项,例如“</MyDocument>”。当它检测到它返回 EOF 时。通过解析第一个开始标记并在匹配的结束标记上返回 EOF,可以使包装器具有自适应性。我发现实际上没有必要检测结束标签的级别,因为我没有在其自身中使用过文档标签,因此可以保证第一次出现的结束标签结束了文档。
我记得,其中一个技巧是让包装块 close(),因为 DOM 阅读器关闭了输入源。
因此,给定读者输入,您的代码可能如下所示:
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 的流和具有多个 xml 的流之间唯一真正的区别是缓冲区和拆分逻辑。