错误的属性值转换?

Wrong attribute value transformation?

输入xml文件:

<a key="777">
  <desc key="1" attr1="# {../@key}" attr2="{@key} #"  attr3="555" title="{@attr2}">
    <b attr3="444">
      <slot key="2" attr3="333" attr4="{../../@attr3}" title="{../../@attr3}+{../@attr3}-{@attr3}" >
        <val key="3">
          <fix key="4" title11="{../../@key} {../@key}" title="{@key}+{@title11}" >
            <c></c>
          </fix>
        </val>
        <numb key="5" title12="z{@key}z in {@key}" title="{@title12}+{@key}" titlew="{@title}+{@title12}">
        </numb>
      </slot>
    </b>
  </desc>
</a>

大括号指示属性的 XPath,您必须沿此 XPath 替换属性的值,例如,代替 {@key} 和 {../@key},值当前节点的关键属性被替换。 XSLT1.0 转换后的文件应该是这样的:

<a key="777">
  <desc key="1" attr1="# 777" attr2="1 #" attr3="555" title="1 #">
    <b attr3="444">
      <slot key="2" attr3="333" attr4="555" title="555+444-333">
        <val key="3">
          <fix key="4" title11="2 3" title="4+2 3">
            <c />
          </fix>
        </val>
        <numb key="5" title12="z5z in 5" title="z5z in 5+5" titlew="z5z in 5+5+z5z in 5" />
      </slot>
    </b>
  </desc>
</a>

现在这是 XSLT1.0 转换文件:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
  xmlns:msxsl="urn:schemas-microsoft-com:xslt">
  <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
  <xsl:strip-space  elements="*"/>

  <xsl:template match="node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="@*">
    <xsl:attribute name="{name()}">
      <xsl:call-template name="expand">
        <xsl:with-param name="text" select="."/>
      </xsl:call-template>
    </xsl:attribute>
  </xsl:template>

  <xsl:template name="expand">
    <xsl:param name="text"/>
    <xsl:choose>

      <xsl:when test="contains($text, '{@')">
        <xsl:value-of select="substring-before($text, '{')"/>
        <xsl:variable name="name" select="substring-before(substring-after($text, '{@'), '}')" />
        <xsl:call-template name="expand">
          <xsl:with-param name="text" select="../@*[name()=$name]"/>
        </xsl:call-template>
        <xsl:call-template name="expand">
          <xsl:with-param name="text" select="substring-after($text, '}')"/>
        </xsl:call-template>
      </xsl:when>

      <xsl:when test="contains($text, '{../@') ">
        <xsl:value-of select="substring-before($text, '{')"/>
        <xsl:variable name="name" select="substring-before(substring-after($text, '{../@'), '}')" />
        <xsl:call-template name="expand">
          <xsl:with-param name="text" select="../../@*[name()=$name]"/>
        </xsl:call-template>
        <xsl:call-template name="expand">
          <xsl:with-param name="text" select="substring-after($text, '}')"/>
        </xsl:call-template>
      </xsl:when>

      <xsl:when test="contains($text, '{../../@') ">
        <xsl:value-of select="substring-before($text, '{')"/>
        <xsl:variable name="name" select="substring-before(substring-after($text, '{../../@'), '}')" />
        <xsl:call-template name="expand">
          <xsl:with-param name="text" select="../../../@*[name()=$name]"/>
        </xsl:call-template>
        <xsl:call-template name="expand">
          <xsl:with-param name="text" select="substring-after($text, '}')"/>
        </xsl:call-template>
      </xsl:when>

      <xsl:otherwise>
        <xsl:value-of select="$text"/>
      </xsl:otherwise>

    </xsl:choose>
  </xsl:template>

</xsl:stylesheet>

Xml 转换后的文件:

<a key="777">
  <desc key="1" attr1="# 777" attr2="1 #" attr3="555" title="1 #">
    <b attr3="444">
      <slot key="2" attr3="333" attr4="555" title="333+333-333">
        <val key="3">
          <fix key="4" title11="3 3" title="4+3 3">
            <c />
          </fix>
        </val>
        <numb key="5" title12="z5z in 5" title="z5z in 5+5" titlew="z5z in 5+5+z5z in 5" />
      </slot>
    </b>
  </desc>
</a>

错误是如果在输入文件节点的属性值中指定了相同的属性名,比如在title属性的Slot标签中花括号中三次@attr3,那么就是相同的值替换花括号:

<slot key="2" ... title="333+333-333">

应该是这样的:

<slot key="2" ... title="555+444-333">

哪里错了?

在纯 XSLT 1.0 中没有真正好的解决方案。

如果输入 XML 中的路径表达式仅限于一些预定义模式(如您的示例中所示),您可以通过以下方式获得预期结果:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:template match="node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="@*">
    <xsl:attribute name="{name()}">
        <xsl:call-template name="expand">
            <xsl:with-param name="text" select="."/>
        </xsl:call-template>
    </xsl:attribute>
</xsl:template>

<xsl:template name="expand">
    <xsl:param name="text"/>
    <xsl:choose>
        <xsl:when test="contains($text, '{') ">
            <!-- text before reference -->
            <xsl:value-of select="substring-before($text, '{')"/>
            <!-- reference -->
            <xsl:call-template name="evaluate">
                <xsl:with-param name="path" select="substring-before(substring-after($text, '{'), '}')"/>
            </xsl:call-template>            
            <!-- recursive call with text after reference -->
            <xsl:call-template name="expand">
                <xsl:with-param name="text" select="substring-after($text, '}')"/>
            </xsl:call-template>            
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$text"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template name="evaluate">
    <xsl:param name="path"/>
    <xsl:variable name="name" select="substring-after($path, '@')"/>
    <xsl:call-template name="expand">
        <xsl:with-param name="text">
            <xsl:choose>
                <xsl:when test="starts-with($path, '../../@')">
                    <xsl:value-of select="../../../@*[name()=$name]"/>
                </xsl:when>
                <xsl:when test="starts-with($path, '../@')">
                    <xsl:value-of select="../../@*[name()=$name]"/>
                </xsl:when>
               <xsl:when test="starts-with($path, '@')">
                    <xsl:value-of select="../@*[name()=$name]"/>
                </xsl:when>
            </xsl:choose>
        </xsl:with-param>
    </xsl:call-template>            
</xsl:template>

</xsl:stylesheet>

更好的解决方案是升级到支持 XSLT 3.0 或其他动态评估机制的处理器 - 如 中所建议。