如何从XML文件中消除空的叶元素,而不将整个XML保存在内存中?

How to eliminate empty leaf elements from an XML file without holding the whole XML in memory?

提问人:NicuMarasoiu 提问时间:6/14/2019 更新时间:6/14/2019 访问量:194

问:

我们需要从XML文件中删除满足以下条件之一的元素: C1.它们是叶元素(没有其他元素作为子元素),其修剪的文本(由非元素子节点连接)为空(只有空格)。 -或- C2.他们只有尊重 C1 或 C2 的孩子。换言之, C2.它们没有任何不遵循 C1 或 C2 的子元素。

所以它是一种递归清理算法。DOM 方法的问题在于,将树存储在内存中需要 XML 大小的倍数。我们正在寻找恒定内存方法的替代方案,即使我们需要对磁盘进行多个读写周期,例如写入多个 XML 文件,直到生成所需的 XML。

我们有一个 dom4j 实现,但它占用了大约 5 倍的内存作为 XML 大小(它显然将所有树都保留在内存中,尽管在特定测试中实际上没有操作任何更改 - 在特定测试用例中没有删除任何元素)。

我们正在考虑在一次迭代中只对完整的 XML 进行 C1(如果可以以消耗更少内存的方式完成,例如,将空格叶子与 XPath 匹配并将它们取出而不将整个结构加载到内存中 - 有没有办法唯一标识这些元素?XPath 是否总是唯一标识节点?),输出到一个文件,并迭代执行,直到没有叶子匹配,然后清理 XML。

一个步骤或多个步骤中的转换,涉及使用 Java 或 XSLT 或其他任何内容进行 JVM 处理,它采用随机 XML(涉及多个 XML 模式),并输出清理 XML(作为文件或输出/输入流)。

xml 解析 sax jdom dom4j

评论


答:

1赞 Michael Kay 6/14/2019 #1

这很棘手,因为它涉及前瞻。考虑

<a>
  <b/>
  <c/>
  <d/>
  <z>23</z>
</a>

在你看到元素之前,你不知道是否要消除这个元素。因此,这当然不是一个纯粹的可流式转换。<a><z/>

你可以在一次传递中建立一个要消除的所有元素的列表。

了解您是希望消除非常多还是非常少的元素会很有用;在第一种情况下,第一次传递应收集对要保留的元素的引用,在第二种情况下,它应该收集对要删除的元素的引用。

我认为表达您的要求的另一种方式是:消除任何不是至少一个非空格文本节点的祖先的元素。

在可流式处理的 XSLT 3.0 转换中,收集非空格文本节点的所有祖先的路径非常容易:

//text()[normalize-space()] ! ancestor::* ! path(.)

唯一的问题是,在没有任何体积的情况下,我不知道这个列表是否大得不可思议。您可以通过将其放入地图表达式中来消除重复项:

map:merge(//text()[normalize-space()] ! ancestor::* ! path(.) ! map{.:1},
            map{'duplicates':'use-first'})

构建此列表后,可以很容易地进行流式转换,以消除不在列表中的元素:

<xsl:mode streamable="yes" on-no-match="shallow-copy"/>
<xsl:template match="*[not(map:contains($retained-path, path(.))]"/>

正如我所说,问题在于保留节点的列表可能会变得非常大。

另一种方法是尝试构建要删除的元素路径列表。这方面的算法可能是:当您遇到元素开始标记时,将该元素添加到要消除的候选列表中;当您遇到非空格文本节点时,请从列表中删除其所有祖先节点。问题在于,正如这里所表达的,它需要列表的可变数据结构。这使它成为 XSLT 3.0 累加器的候选者:

<xsl:accumulator name="dropped-elements" as="map(xs:string, xs:integer)">
 <xsl:accumulator-rule match="*" select="map:merge($value, map{path(.), 1}"/>
 <xsl:accumulator-rule match="text()[normalize-space()]
    select="map:remove($value, ancestor::*!path(.))"/>
</xsl:accumulator>

然后在处理结束时为您提供要删除的元素的路径。map:keys(accumulator-after('dropped-elements'))

全部未经测试:我希望这能给你一些想法。