代码之家  ›  专栏  ›  技术社区  ›  Alan Krueger

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

  •  1
  • Alan Krueger  · 技术社区  · 16 年前

    我尝试使用dom4j一次从流中读取一个XML文档,处理它,然后继续到流中的下一个文档。不幸的是,dom4j的saxreader(在封面下使用jaxp)一直在阅读并阻塞以下文档元素。

    有没有一种方法可以让SaxReader在找到文档元素结尾时停止读取流?有更好的方法来完成这一点吗?

    6 回复  |  直到 16 年前
        1
  •  1
  •   Alan Krueger    16 年前

    我可以通过一些内部的JAXP课程让它与一些体操一起工作:

    • 创建自定义扫描程序,xmlnsDocumentScannerImpl的子类
      • 在自定义扫描程序中创建一个自定义驱动程序,即xmlnsDocumentScannerImpl.Driver的实现,当它看到声明或元素时,该驱动程序将返回结束文档。从felementscanner.getCurrentEntity()获取scannedEntity。如果实体具有push back reader,请将实体缓冲区中剩余的未读字符推回到该读卡器上。
      • 在构造函数中,将ftRailingMiscDriver替换为此自定义驱动程序的实例。
    • 创建一个自定义配置类(xincludewareparserconfiguration的子类),该类用其构造函数中该自定义扫描程序的实例替换库存文档_scanner。
    • 将此自定义配置类的实例安装为“com.sun.org.apache.xerces.internal.xni.parser.xmlparserconfiguration”属性,以便在dom4j的saxreader类尝试创建jaxp xmlreader时将其实例化。
    • 当将读卡器传递给dom4j的saxreader.read()方法时,请提供一个缓冲区大小远远大于一个字符默认值的pushbackreader。至少8192应该足以支持JAXP的Apache2副本中xmlentityManager的默认缓冲区大小。

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

        2
  •  0
  •   Ian McLaird    16 年前

    很可能,您不希望同时在同一个流中有多个文档。我不认为萨克斯阅读器足够聪明,当它到达第一个文档的结尾时就停止了。为什么需要在同一个流中使用多个文档?

        3
  •  0
  •   Trenton    16 年前

    我认为您需要添加一个适配器,一些东西来包装流,当它看到下一个文档的开始时,就让这个东西返回文件的结尾。据我所知,所写的解析器将一直运行到文件结束或出现错误…看到另一个 <?xml version="1.0"?> 肯定是个错误。

        4
  •  0
  •   Richard    16 年前

    假设您负责将文档放到流中,那么首先应该很容易以某种方式对文档进行定界。例如:

    // 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);
    }
    

    然后,当从流中读取数据时,读取到一个数组中,直到遇到文档终止符为止。

    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是一个无效的字符值,除非您明确添加它,否则您不会遇到它。从而允许您拆分文档。现在,只需将用于输入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的文档时,循环将终止。这意味着您应该在最后一个文档之后添加第二个文档终结符,以便在getNextDocument()中检测流的结尾。

        5
  •  0
  •   L. Cornelius Dol    16 年前

    我以前用我自己创建的具有非常简单解析功能的另一个阅读器包装基阅读器来完成这项工作。假设您知道文档的结束标记,包装器只分析匹配项,例如“</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()方法将读取器标记为停止返回-1 for read()。

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

    —— 几维鸟。

        6
  •  0
  •   Michael Rutherfurd    16 年前

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

    在处理一个XML流和多个XML流之间唯一的区别是缓冲和拆分逻辑。