【问题标题】:Split XML stream by XML documents按 XML 文档拆分 XML 流
【发布时间】:2020-02-10 08:46:19
【问题描述】:

我有一个这样的无模式 XML 文档流:

<?xml version="1.0" encoding="UTF-8"?>
<message id="1">
    <text>aaaaaaa</text>
</message>
<kuku>
    bbbbb
</kuku>
<?xml version="1.0" encoding="UTF-8"?>
<other_message id="3">
    <text>ccccc</text>
</other_message>

需要解析文档的是流模式。 通过单个根 XML 元素包装流的解决方案不起作用,因为当 StAX 在文档中遇到 &lt;?xml ... ?&gt; 元素时会失败。但如果我能够在输入流中跳过此元素,则可以使用它。

所有文档都可以不同,所以没有共同的end_document XML 元素。

【问题讨论】:

  • 您认为“XML 文档”到底是什么?在 XML 规范定义的意义上,您需要一个包含所有其他元素的根元素。即使您的第一个片段在顶层具有messagekuku,情况似乎也并非如此。
  • 您想使用 SAX 或 StAX 吗?它们本质上是不同的模型;您的问题被标记为两者。
  • 是否确保任何 XML 声明 (&lt;?xml version="1.0" ...?&gt;) 都在自己的一行上? XQuery 3 或 XSLT 3 在 XSLT 中使用 unparsed-text-linesxsl:for-each-group group-starting-with 或在 XQuery 中使用翻滚窗口来识别片段,然后在每个组上使用 parse-xml-fragment string-join()ed 至少应该允许像这样拆分输入。
  • @Ironluca 在遇到第二个根元素或XML解析指令时都失败&lt;?...?&gt;
  • @PavelLeonov,正如您所观察到的,您要么将声明从流中过滤掉,要么遵循 Kay 博士建议的一些变体。不会有其他机制。

标签: java xml sax stax


【解决方案1】:

没有办法做到 100% 可靠。您不能使用 XML 解析器执行此操作,因为它会在看到第二个 XML 声明时报告错误,并且无法从该错误中恢复。因此,您必须使用自己的“预解析”来执行此操作,并且始终存在您的预解析会识别出看起来像 XML 声明但实际上并非如此的风险,因为(例如)它在正文中XML 注释或 CDATA 部分。但这可能是你能做的最好的。这样做的优雅方法可能是编写一个 InputStream 的实现,它提供一个可迭代的 InputStream 序列,然后循环遍历这个可迭代的序列,将每个迭代依次传递给 XML 解析器。或者,您建议过滤掉 XML 声明(并添加外部包装开始标记和结束标记)也可以。

更好的是,鼓励提供此数据的人他们做错了。

【讨论】:

  • 添加外包装开始标签和结束标签不起作用,因为XML解析器会在正文中找到内部文档的XML解析指令(&lt;? ... ?&gt;
  • 我还没有找到在解析器中忽略它们的方法。肮脏的解决方案是过滤自己的InputStream中的元素:
  • 是的,这是脏输入,所以唯一的解决方案是脏的。 (实际上,我认为您的 cmets 所说的正是我在回答中所说的)
【解决方案2】:

这是可能的,但它有点丑陋和骇人听闻。您可以使用 StAX 并计算开始和结束元素标签。增加开始标签上的计数器并减少结束标签上的计数器。当您到达0 时,您就知道您已经完全阅读了根元素。使用XMLStreamReader 上的getLocation() 方法来查看您已经阅读了多远,特别是getCharsetOffset() 方法。使用原始源/流的新位置/偏移量,您可以构建一个新流,其起点位于下一个 XML 声明。作为概念验证,请参见以下代码:

String content = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"+
            "<foobar>\n"+
            "    <bla />\n"+
            "</foobar>\n"+
            "<?xml version=\"1.0\" encoding=\"ASCII\" ?>\n"+
            "<first with=\"attributes\">\n"+
            "   <second>\n"+
            "       <third />\n"+
            "   </second>\n"+
            "</first>";
XMLInputFactory factory = XMLInputFactory.newFactory();
InputStream stream = new ByteArrayInputStream(content.getBytes());
XMLStreamReader xmlReader = factory.createXMLStreamReader(stream);
int nestingCounter = 0;
int characterOffset = 0;
while(xmlReader.hasNext()) {
    int event = xmlReader.next();
    characterOffset = xmlReader.getLocation().getCharacterOffset();
    if (event == XMLStreamConstants.START_ELEMENT) {
        nestingCounter++;
    }
    if (event == XMLStreamConstants.END_ELEMENT) {
        nestingCounter--;
    }
    // work with the event/data here
    System.out.println(event);
    if (nestingCounter == 0) {
        break;
    }
}

System.out.println("Second XML");

// build a new stream
content = content.substring(characterOffset).trim();
xmlReader = factory.createXMLStreamReader(new ByteArrayInputStream(content.getBytes()));

// now, again...
while(xmlReader.hasNext()) {
    int event = xmlReader.next();
    if (event == XMLStreamConstants.START_ELEMENT) {
        nestingCounter++;
    }
    if (event == XMLStreamConstants.END_ELEMENT) {
        nestingCounter--;
    }           
    // work with the event/data here
    System.out.println(event);
    if (nestingCounter == 0) {
        break;
    }
}

这将生成以下输出(并且不会引发异常):

1
4
1
2
4
2
Second XML
1
4
1
4
1
2
4
2
4
2

显然您应该使用适当的循环并关闭流和阅读器,这只是一个概念验证。此外,当您在前一个根元素的结束标记和新的 XML 声明之间有 other stuff 时,您可能会遇到问题,因为您可以在 XML 文档中拥有这些东西 at the end, but not at the beginning

2.1 格式良好的 XML 文档

document     ::=      prolog element Misc*

2.8 Prolog 和文档类型声明

Misc         ::=      Comment | PI | S

【讨论】:

  • 感谢您的回答,如果您有字符串输入,POC 可以正常工作。如果我没有 String 作为输入,而是 InputStream,有没有类似的解决方案?我试图用新的解析器实例重用 InputStream 对象,但它不起作用,因为解析器使用缓冲读取器并且流中的当前光标位置(当第一个文档的解析完成时)已经在下一个 XML 文档中的某个位置。跨度>
  • @PavelLeonov 我遇到了同样的问题。使用reset()skip() 方法以及getCharacterOffset() 提供的值,可以使用BufferedInputStream() 在流中来回切换。
猜你喜欢
  • 2013-08-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-04
  • 1970-01-01
相关资源
最近更新 更多