将 xsl:accumulator 与 xsl:try/xsl:catch 一起使用

Using xsl:accumulator with xsl:try/xsl:catch

我有一个非常大的输入文档(数千条记录),其结构类似于这样(数据代表许多子元素):

<Input>
  <Record id="1">
    <Data/>
  </Record>
  <Record id="2">
    <Data/>
  </Record>
  <Record id="3">
    <Data/>
  </Record>
  <Record id="4">
    <Data/>
  </Record>
  <Record id="5">
    <Data/>
  </Record>
  <Record id="6">
    <!-- This is bad data -->
    <BadData/>
  </Record>
  <Record id="7">
    <Data/>
  </Record>
  <Record id="8">
    <Data/>
  </Record>
  <Record id="9">
    <!-- Also bad data -->
    <BadData/>
  </Record>
</Input>

我正在使用样式表处理它,该样式表对每个记录执行复杂的转换,这可能 运行 导致许多动态错误。在此应用程序中,如果一些记录有错误数据,我不想停止转换,但我想知道错误,以便稍后修复它们。我正在使用 xsl:try/xsl:catch 以允许继续处理:

<xsl:stylesheet
  version="3.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:err="http://www.w3.org/2005/xqt-errors"
  exclude-result-prefixes="xs err">

  <xsl:output indent="yes"/>

  <xsl:strip-space elements="*"/>

  <xsl:template match="Input">
    <Output>
      <xsl:apply-templates/>
    </Output>
  </xsl:template>

  <xsl:template match="Record">
    <xsl:variable name="preprocessed" as="element(GoodData)?">
      <xsl:try>
        <xsl:apply-templates mode="preprocess" select="."/>
        <xsl:catch>
          <xsl:message expand-text="yes">Couldn't create good data for {@id} Code: {$err:code} {$err:description}</xsl:message>
        </xsl:catch>
      </xsl:try>
    </xsl:variable>
    <!-- Do some more logic on the preprocessed record -->
    <xsl:if test="$preprocessed">
      <NewRecord id="{@id}">
        <xsl:sequence select="$preprocessed"/>
      </NewRecord>
    </xsl:if>
  </xsl:template>



  <xsl:template mode="preprocess" match="Record">
    <!-- This represents a very complex transform with many potential dynamic errors -->
    <xsl:variable name="source" as="element(Data)" select="*"/>
    <xsl:if test="$source">
      <GoodData/>
    </xsl:if>
  </xsl:template>

</xsl:stylesheet>

这工作正常,但是通过大量输入文档来查找失败的少数记录是一件很痛苦的事情。我想做的是使用 xsl:result-document 将失败的 Record 元素的源写入新的输入文档。我正在尝试添加一个 xsl:accumulator 类似这样的内容:

<xsl:accumulator name="failed-source" initial-value="()" as="element(Record)*">
  <xsl:accumulator-rule match="Record" phase="end">
    <xsl:sequence select="$value, .[false()(:test for failure:)]"/>
  </xsl:accumulator-rule>
</xsl:accumulator>

<xsl:template match="Input">
  <Output>
    <xsl:apply-templates/>
  </Output>
  <xsl:if test="accumulator-after('failed-source')">
    <xsl:result-document href="failed.input.xml">
      <Input>
        <xsl:sequence select="accumulator-after('failed-source')"/>
      </Input>
    </xsl:result-document>
  </xsl:if>
</xsl:template>

但是,我无法弄清楚 xsl:accumulator 规则中的谓词应该是什么,或者是否可以使用此模式。是否可以在不重构样式表的情况下创建单个结果文档?

注意:我知道以下解决方案,但它不是我的首选,因为它似乎可能有更高的内存要求,但也许事实并非如此。我也可以将所有记录写入单独的文件,但我认为这很危险,因为一个源文档可能会产生数千个失败。

<xsl:template match="Input">
  <xsl:variable name="processed" as="document-node()">
    <xsl:document>
      <xsl:apply-templates/>
    </xsl:document>
  </xsl:variable>
  <xsl:if test="$processed/NewRecord">
    <Output>
      <xsl:sequence select="$processed/NewRecord"/>
    </Output>
  </xsl:if>
  <xsl:if test="$processed/Record">
    <xsl:result-document href="failed.input.xml">
      <Input>
        <xsl:sequence select="$processed/Record"/>
      </Input>
    </xsl:result-document>
  </xsl:if>
</xsl:template>

<xsl:template match="Record">
  <xsl:variable name="preprocessed" as="element(GoodData)?">
    <xsl:try>
      <xsl:apply-templates mode="preprocess" select="."/>
      <xsl:catch>
        <xsl:message expand-text="yes">Couldn't create good data for {@id} Code: {$err:code} {$err:description}</xsl:message>
      </xsl:catch>
    </xsl:try>
  </xsl:variable>
  <!-- Do some more logic on the preprocessed record -->
  <xsl:choose>
    <xsl:when test="$preprocessed">
      <NewRecord id="{@id}">
        <xsl:sequence select="$preprocessed"/>
      </NewRecord>
    </xsl:when>
    <xsl:otherwise>
      <xsl:sequence select="."/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

这是一个有趣的方法。

累加器的值必须始终是输入节点的纯函数。无法从其他活动中获取信息,例如节点处理是否失败。我不清楚您是否可以独立于对这些记录执行的处理来检测 "bad records":如果可以,也就是说,如果您本质上是在对输入进行自定义验证,那么这种模式可能会起作用很好。 (但在那种情况下,我不认为你会做 try/catch。相反,你的主要处理函数将首先检查累加器以查看数据是否有效。)

请注意,累加器规范允许一个累加器的计算访问其他累加器,但这目前在 Saxon 中未实现。

我认为解决这个问题更常用的方法可能是将成功处理的结果和处理不成功的报告写入同一结果树,然后在后续的转换过程中将其拆分。不幸的是,XSLT 3.0 流功能在多通道处理领域没有任何可提供的功能。然而,对于拆分通道,xsl:fork 可能很合适。