首页 > 解决方案 > 转换未完成时访问临时树

问题描述

我有一个以 xml 作为输出的两阶段 XSLT 转换。当我在第一阶段在一个模板中设置断点并使用 XML Spy Professional 2020 在调试模式下开始我的 xslt 转换时,我可以在XSL Output.xml应用带有断点的模板之前看到一个 xml 结构作为处理结果。

我的问题是,同阶段的一个模板中是否有办法访问这个结构,这是转换的临时结果,尚未完成?

对于开发,我使用 XML Spy Professional 2020,对于应用程序的转换,我使用 Saxon Professional Edition SaxonPE9-9-1-3J

我的问题如下:

输入是纯文本https://gist.github.com/jia2/35143e79213864153b57ad0323a440a8#file-input-txt

基于此格式规则https://gist.github.com/jia2/76d676b90935cb7f33f5028180557af3

预期的 XML 输出如下: https ://gist.github.com/jia2/daaa4b2de5d1dadcb834f9f91c65d45b

这是我的模板:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:csb="http://www.dbcargo.org/csb" exclude-result-prefixes="#all" version="3.0">
    <!-- <xsl:param name="msg" as="xs:string">H0 EVU_DBSRD PVG     Z24 ABF-RF  IR    ExternalPartnerID_uuuuuuuuuuuuuuuuu0202017-03-16-07.27.40.864320NJNJ   M1           80281261300008                        M2 16.03.201707:27:00Z1 H62430  16.03.2017                    16.03.201707:00:00+0027R1 00131800820664780201703154023641201703151159043706346965                                   000    JJ                                                R1 02031800819657480201703154045545201703151159306557346965                                   000    NN                                                </xsl:param> -->
    <xsl:param name="msg" as="xs:string">H0 EVU_DBSRD PVG     Z24 ABF-RF  IR    ExternalPartnerID_uuuuuuuuuuuuuuuuu0202017-03-16-07.27.40.864320NJNJJJ M1           80281261300008                        M2 16.03.201707:27:00Z1 H62430  16.03.2017                    16.03.201707:00:00+0027R1 00131800820664780201703154023641201703151159043706346965                                   000    JJ                                                R1 02031800819657480201703154045545201703151159306557346965                                   000    NN                                                </xsl:param>
    <xsl:param name="relatviePath2MFL" as="xs:string" select="'./format.xml'"/>
    <xsl:variable name="MFL" select="document($relatviePath2MFL)"/>
    <xsl:output method="xml" indent="yes"/>
    <xsl:mode name="unroll" on-no-match="shallow-copy"/>
    <xsl:strip-space elements="*"/>
        <xsl:template match="StructFormat[@repeat]" mode="unroll">
        <xsl:variable name="this" select="."/>
        <xsl:choose>
            <xsl:when test="$this/@repeat != '*' ">
                <xsl:for-each select="1 to @repeat">
                    <xsl:choose>
                        <xsl:when test="$this/@delimOptional = 'n' and $this/TagField and contains($msg, $this/TagField)">
                            <xsl:copy select="$this">
                                <xsl:apply-templates select="@* except @repeat, node()" mode="#current"/>
                            </xsl:copy>
                        </xsl:when>
                        <xsl:otherwise/>
                    </xsl:choose>
                </xsl:for-each>
            </xsl:when>
            <xsl:otherwise>
                <xsl:variable name="repeat" select="count(tokenize($msg, $this/TagField/@value)) - 1"/>
                <xsl:for-each select="1 to $repeat">
                    <xsl:copy select="$this">
                        <xsl:apply-templates select="@* except @repeat, node()" mode="#current"/>
                    </xsl:copy>
                </xsl:for-each>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    <xsl:template match="StructFormat[not(@repeat)]" mode="unroll">
        <xsl:variable name="this" select="."/>
        <xsl:choose>
            <xsl:when test="$this/TagField and not(contains($msg, $this/TagField/@value)) ">
            </xsl:when>
            <xsl:otherwise>
                <xsl:copy select="$this">
                    <xsl:apply-templates select="@* except @repeat, node()" mode="#current"/>
                </xsl:copy>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    <xsl:template match="FieldFormat[@repeat]" mode="unroll">
        <xsl:variable name="this" select="."/>
        <xsl:for-each select="1 to @repeat">
            <xsl:copy select="$this">
                <xsl:apply-templates select="@* except @repeat, node()" mode="#current"/>
            </xsl:copy>
        </xsl:for-each>
    </xsl:template>
    <xsl:variable name="complete-struct">
        <xsl:apply-templates select="$MFL/*" mode="unroll"/>
    </xsl:variable>
    <xsl:template match="/">
        <xsl:element name="{$MFL/MessageFormat/@name}">
            <xsl:apply-templates select="$complete-struct/*"/>
        </xsl:element>  
    </xsl:template>
    <xsl:template match="StructFormat">
        <xsl:element name="{@name}">
            <xsl:apply-templates/>
        </xsl:element>
    </xsl:template>
    <xsl:template match="FieldFormat">
        <xsl:variable name="precedingFieldFormatsLength" select="sum(preceding::FieldFormat/@length)"/>
        <xsl:variable name="offset">
            <xsl:value-of select="string-length(string-join(./preceding::TagField/@value, ''))"/>
        </xsl:variable>
        <xsl:element name="{@name}">
            <xsl:variable name="value" select="substring($msg, 1 + $precedingFieldFormatsLength + $offset, @length)"/>
            <xsl:value-of select="csb:formatField(.,$value)"/>
        </xsl:element>
    </xsl:template>
    <!-- format output -->
    <xsl:function name="csb:formatField" as="xs:string">
        <xsl:param name="field" as="element()"/>
        <xsl:param name="value" as="xs:string"/>
        <xsl:choose>
                    <xsl:when test="$field/@length = '1' and $value = ' '">
                <xsl:value-of select="''"/>
            </xsl:when>
            <!-- remove leading and trailing space -->
            <xsl:when test="$field/@trimLeading = ' ' and $field/@trimTrailing = ' '">
                <xsl:value-of select="fn:replace($value, '^\s+|\s+$', '')"/>
            </xsl:when>
            <!-- remove ONLY leading space -->
            <xsl:when test="$field/@trimLeading = ' '  and fn:not(fn:exists($field//@trimTrailing))">
                <xsl:value-of select="fn:replace($value, '^\s+', '')"/>
            </xsl:when>
            <!-- remove ONLY trailing space -->
            <xsl:when test="$field/@trimTrailing = ' '  and fn:not(fn:exists($field//@trimLeading))">
                <xsl:value-of select="fn:replace($value, '\s+$', '')"/>
            </xsl:when>
            <!-- remove leading 0 -->
            <xsl:when test="$field/@type = 'Numeric'  and $field/@trimLeading = '0'  and fn:not(fn:exists($field//@trimTrailing))">
                <!-- <xsl:value-of select="fn:replace($value, '^0+', '')"/> -->

                <xsl:if test="number($value) != number($value)">
                <xsl:message terminate="yes" ><xsl:value-of select="concat('Transformation failed. The field', $field, ' has invalid value')" /></xsl:message>              
                </xsl:if>
                <xsl:value-of select="number($value)"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$value"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:function>
</xsl:stylesheet>

我的 xslt 模板https://gist.github.com/jia2/5f7387e549e6f83601dbfac23ceb3acf正在读取这个 format.xml 作为输入,纯文本作为参数传递。它适用于某些输入。但是,当用于标记“StructFormat”开头的值存在于其他位置时,它将失败。

例如:

    <StructFormat name='HandoverTakeover' delimOptional='n' optional='y'>
            <TagField type='String' value='U1 '/>

这意味着,当输入在某个位置有“U1”时,应该生成 StructFormat。现在我只是检查输入文本是否包含“U1”(<xsl:when test="$this/TagField and not(contains($msg, $this/TagField/@value)) ">),但这还不够,我需要检查“U1”是否在“正确”位置范围内,而不是在整个输入中。

我虽然如果我可以访问当前构建的结果树,我可以计算到现在的长度以剪切我正在检查的这个位置之前的文本。

谢谢定军

标签: xslt-3.0

解决方案


XSLT 是一种函数式语言;因此,它不允许结果取决于执行顺序的操作。特定处理器以特定方式组织处理(即使两个不同的处理器选择相同的策略)这一事实并不意味着它是可以依赖的。例如,几年后,并行执行策略可能会更加普遍。

更具体地说,转换的两个阶段“同时”执行(一个在另一个完成之前开始)这一事实是您无法利用或依赖的内部优化,这是设计使然。

毫无疑问,您试图实现的转换可以在声明性函数语言的范式中以某种完全不同的方式实现。我没有研究过具体问题;像许多在 StackOverflow 上回答问题的人一样,我不准备关注场外代码的链接。


推荐阅读