XSLT 流式传输复杂文档

XSLT Streaming complex documents

我看到的 XSLT 3.0 流式传输的大多数示例都相当简单,并采用

形式的输入
<rootTag>
 <repeatingThing>
   <CDataTag>text</CDataTag> 
   <CDataTag2>text</CDataTag2>
 </repeatingThing>
 <repeatingThing>...</repeatingThing>
</rootTag>

假设您需要触摸 repeatingThing 内的所有标签。在这种情况下,流式处理效果很好,在您的 repeatingThing 模板中执行 copy-of,并且您已将内存占用减少到其原始内存占用的 1/X(其中 X 是 repeatingThing 标签的数量)。

但是,我处理的是高度嵌套的 XML。此外,由于我的样式表的性质(JSON<->XML 转换),我需要修改 XML 源文档中的所有标签。 copy-of 方法在这里不起作用,因为内容分布在许多子节点上,我将把整个 XML 复制到内存中,只是更明确。

在这种情况下,我不知道如何使用流式处理。此类 "hierarchical" 文档的框架如下:

<n1:ElectionReport xmlns:n1="NIST_V2_election_results_reporting.xsd">
    <n1:Election>
        <n1:BallotCounts>
            <n1:DeviceClass>
                <n1:Manufacturer/>
                <n1:Model/>
                <n1:Type/>
                <n1:OtherType/>
            </n1:DeviceClass>
            <n1:GpUnitId/>
            <n1:IsSuppressedForPrivacy/>
            <n1:Round/>
            <n1:Type/>
            <n1:OtherType/>
            <n1:BallotsCast/>
            <n1:BallotsOutstanding/>
            <n1:BallotsRejected/>
        </n1:BallotCounts>
        <n1:BallotStyle>
            <n1:ExternalIdentifier>
                <n1:Type/>
                <n1:OtherType/>
                <n1:Value/>
            </n1:ExternalIdentifier>
            <n1:GpUnitIds/>
            <n1:ImageUri/>
            <n1:OrderedContent xsi:type="n1:OrderedContest">
                <n1:ContestId/>
                <n1:OrderedContestSelectionIds/>
            </n1:OrderedContent>
            <n1:PartyIds/>
        </n1:BallotStyle>
        <n1:Candidate ObjectId="">
            <n1:BallotName>
                <n1:Text Language=""/>
            </n1:BallotName>
            <n1:CampaignSlogan>
                <n1:Text Language=""/>
            </n1:CampaignSlogan>
            <n1:ContactInformation>
                <n1:AddressLine/>
                <n1:Directions>
                    <n1:Text Language=""/>
                </n1:Directions>
                <n1:Email/>
                <n1:Fax/>
                <n1:LatLng>
                    <n1:Latitude/>
                    <n1:Longitude/>
                    <n1:Source/>
                </n1:LatLng>
                <n1:Name/>
                <n1:Phone/>
                <n1:Schedule>
                    <n1:Hours>
                        <n1:Day/>
                        <n1:StartTime/>
                        <n1:EndTime/>
                    </n1:Hours>
                    <n1:IsOnlyByAppointment/>
                    <n1:IsOrByAppointment/>
                    <n1:IsSubjectToChange/>
                    <n1:StartDate/>
                    <n1:EndDate/>
                </n1:Schedule>
                <n1:Uri/>
            </n1:ContactInformation>
            <n1:ExternalIdentifier>
                <n1:Type/>
                <n1:OtherType/>
                <n1:Value/>
            </n1:ExternalIdentifier>
            <n1:FileDate/>
            <n1:IsIncumbent/>
            <n1:IsTopTicket/>
            <n1:PartyId/>
            <n1:PersonId/>
            <n1:PostElectionStatus/>
            <n1:PreElectionStatus/>
        </n1:Candidate>
    </n1:Election>
    <n1:SequenceStart/>
    <n1:SequenceEnd/>
    <n1:Status/>
    <n1:TestType/>
    <n1:VendorApplicationId/>
</n1:ElectionReport>

使用 Saxon-EE 9.8.0.12

你链接到的那个样本太长了,我无法判断它,但至少有些模板的编写风格看起来过于冗长,即使你不想使用流,例如

<xsl:template name="cdf:LatLng" match="element(*, cdf:LatLng)">
    <xsl:param name="set_type" select="false()"/>
    <xsl:where-populated>
        <string key="Label">
            <xsl:value-of select="@Label"/>
        </string>
    </xsl:where-populated>
    <xsl:where-populated>
        <number key="Latitude">
            <xsl:value-of select="cdf:Latitude"/>
        </number>
    </xsl:where-populated>
    <xsl:where-populated>
        <number key="Longitude">
            <xsl:value-of select="cdf:Longitude"/>
        </number>
    </xsl:where-populated>
    <xsl:where-populated>
        <string key="Source">
            <xsl:value-of select="cdf:Source"/>
        </string>
    </xsl:where-populated>
    <xsl:if test="not($set_type)">
        <string key="@type">ElectionResults.LatLng</string>
    </xsl:if>
</xsl:template>

似乎可行,因为

<xsl:template match="LatLng">
    <xsl:param name="set_type" select="false()"/>
    <xsl:apply-templates select="@*"/>
    <xsl:apply-templates/>
    <xsl:if test="not($set_type)">
        <string key="@type">ElectionResults.LatLng</string>
    </xsl:if>
</xsl:template>

然后对于您知道它们是简单类型的子元素和属性,您只需使用我评论中建议的方法,例如

<xsl:template match="element(*, xs:string)">
    <string key="{local-name()}">{.}</string>
</xsl:template>

<xsl:template match="element(*, xs:double) | element(*, xs:decimal)">
    <number key="{local-name()}">{.}</number>
</xsl:template>

当然,这基本上假设子元素将按照它们出现的顺序进行处理,并且您希望所有这些元素都被处理,但是如果您使用例如流式传输,最后的限制也可以放宽。 <xsl:apply-templates select="*[self::foo or self::bar]"/>.

因此,至少在您只想将已知模式类型映射到 JSON 并为各种元素拼出许多不同模板的地方,我认为使用 apply-templates 而不是拼写出各种子选择可以帮助使代码流式传输。对于可能有 minOccurs=0 和 maxOccurs=unbounded 的类型,我认为你可以接受

<xsl:for-each-group select="*" group-by="node-name()">
  <xsl:variable name="sibling-group" select="copy-of(current-group())"/>
  <xsl:choose>
     <xsl:when test="tail($sibling-group)">
        <array key="{local-name()}">
           <xsl:apply-templates select="$sibling-group"/>
        </array>
     </xsl:when>
     <xsl:otherwise>
        <xsl:apply-templates select="$sibling-group"/>
     </xsl:otherwise>
  </xsl:choose>
</xsl:for-each-group>

而不是 apply-templates,那当然会 "materialize" 相邻的同名元素组,但正如您到目前为止似乎已经在 dedicated 中阐明了数组的显式创建在您需要的地方使用模板,您可以重写这个专用模板,而不会 运行 对任何元素通常使用该方法的风险。

如果您想通过在同一模板中显式选择各种子元素来保持冗长的风格,那么您可以尝试 Saxon 使用 xsl:fork 的效果如何,例如

<xsl:template name="cdf:LatLng" match="element(*, cdf:LatLng)">
    <xsl:param name="set_type" select="false()"/>
    <xsl:fork>
     <xsl:sequence>
      <xsl:where-populated>
        <string key="Label">
            <xsl:value-of select="@Label"/>
        </string>
      </xsl:where-populated>
     </xsl:sequence>
     <xsl:sequence>
      <xsl:where-populated>
        <number key="Latitude">
            <xsl:value-of select="cdf:Latitude"/>
        </number>
      </xsl:where-populated>
     </xsl:sequence>
     <xsl:sequence>
      <xsl:where-populated>
        <number key="Longitude">
            <xsl:value-of select="cdf:Longitude"/>
        </number>
      </xsl:where-populated>
     </xsl:sequence>
     <xsl:sequence>
      <xsl:where-populated>
        <string key="Source">
            <xsl:value-of select="cdf:Source"/>
        </string>
      </xsl:where-populated>
     </xsl:sequence>
    </xsl:fork>
    <xsl:if test="not($set_type)">
        <string key="@type">ElectionResults.LatLng</string>
    </xsl:if>
</xsl:template>

您也有的 call-template 使用一般不会通过流式传输实现。它似乎也用于此样式表以与输入顺序不同的顺序处理 XML 元素,它似乎输出在扩展类型中声明的子元素之后在抽象类型中声明的任何子元素。这当然不适用于仅转发的流式处理方法,逐个节点处理。所以我想你必须决定是否不能在 JSON 中首先输出基本子元素。