使用dom4j从流中读取单个XML文档

时间:2008-10-22 14:56:01

标签: java xml stream sax dom4j

我正在尝试使用dom4j一次从流中读取单个XML文档,处理它,然后继续到流上的下一个文档。不幸的是,dom4j的SAXReader(使用JAXP)会对以下文档元素进行读取和阻塞。

有没有办法让SAXReader在找到文档元素的结尾后停止读取流?有没有更好的方法来实现这一目标?

6 个答案:

答案 0 :(得分:1)

我能够使用一些内部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时对其进行实例化。 / LI>
  • 将Reader传递给dom4j的SAXReader.read()方法时,提供一个缓冲区大小远远大于单字符默认值的PushbackReader。至少8192应足以支持JAXP的Apache2副本中XMLEntityManager的默认缓冲区大小。

这不是最干净的解决方案,因为它涉及对内部JAXP类进行子类化,但它确实有效。

答案 1 :(得分:0)

最有可能的是,您不希望同时在同一个流中包含多个文档。我不认为SAXReader足够智能,当它到达第一个文档的末尾时就停止了。为什么需要在同一个流中包含多个文档?

答案 2 :(得分:0)

我认为你必须添加一个适配器,用来包装流并让它在看到下一个文档的开头时返回文件末尾。据我所知,编写的解析器将一直运行到文件末尾或错误...并且看到另一个<?xml version="1.0"?>肯定会出错。

答案 3 :(得分: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>

答案 4 :(得分:0)

我之前已经通过将基础阅读器与我自己创建的另一个具有非常简单的解析功能的阅读器包装在一起。假设您知道文档的结束标记,则包装器只需解析匹配项,例如为“&lt; / MyDocument&gt;”。当它检测到它返回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。

希望这能为您指明一个有用的方向。

- 猕猴桃。

答案 5 :(得分:0)

我会将输入流读入内部缓冲区。根据预期的总流大小,我会读取整个流,然后解析它或检测一个xml和下一个xml之间的边界(查找

处理带有一个xml的流和带有多个xmls的流之间唯一真正的区别是缓冲区和拆分逻辑。