如何在不将整个XML保留在内存中的情况下从XML文件中消除空叶元素?

时间:2019-06-14 10:47:53

标签: xml xml-parsing sax jdom dom4j

我们需要从XML文件中消除满足以下条件之一的元素: C1。它们是叶元素(不具有其他元素作为子元素),并且它们的修剪文本(从非元素子节点连接而成)为空(仅空白)。 -要么- C2。他们只有孩子尊重C1或C2。换一种说法, C2。它们没有不符合C1或C2的任何子元素。

因此它是递归清除算法。 DOM方法的问题在于,它需要XML大小的倍数才能将树存储在内存中。即使我们需要对磁盘进行多个读写循环,例如,我们仍在寻找恒定内存方法的替代方案。编写多个XML文件,直到生成所需的XML为止。

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

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

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

1 个答案:

答案 0 :(得分: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'))结束时,为您提供要删除的元素的路径。

所有未经测试:我希望这能给您一些想法。