XSL:在创建价值递归时添加同级节点

时间:2018-09-11 08:54:37

标签: xml xslt xslt-1.0 xslt-2.0

输入:

<root>
  <name>a,b,c,d,e,f,g,h,i,j,k,</name>
  <value>1,,3,,5,,7,,,,11,<value>
</root>

预期输出

<root>
  <out>a:1|c:3|e:5|g:7|k:11</out>
  <e>5</e>
  <j/>
</root>

我能够通过递归获得期望。 但是我需要分别打印两个值,例如“ e”和“ j”。 循环时,如果名称是'e'或'j',则应创建这些元素。我无法做到这一点。

递归代码

<xsl:template match="//root">
      <xsl:param name="columnName" select="a,b,c,d,e,f,g,h,i,j,k"></xsl:param>

        <root>
           <out>
            <xsl:call-template name="merge">
                <xsl:with-param name="name" select="normalize-space(name)" />
                 <xsl:with-param name="value" select="normalize-space(value)" />
            </xsl:call-template>                    
          </out>
       </root>
     </xsl:template>

    <xsl:template name="merge">
        <xsl:param name="name" />
        <xsl:param name="value" />
        <xsl:param name="separator" select="','" />  
           <xsl:variable name="currentValue"  select="substring-before($value, $separator)"/>
            <xsl:if test="$currentValue!=''">
                <xsl:value-of select="substring-before($name, $separator)" /><xsl:text>:</xsl:text>
                <xsl:value-of select="$currentValue" /><xsl:text>|</xsl:text>
            </xsl:if>
            <xsl:call-template name="merge">
                <xsl:with-param name="value" select="normalize-space(substring-after($value, $separator))" />
                 <xsl:with-param name="name" select="normalize-space(substring-after($name, $separator))" />
            </xsl:call-template>
    </xsl:template> 

如何在循环中添加两个元素以获取“退出”?

3 个答案:

答案 0 :(得分:1)

另一种方法是将“合并”模板更改为<a>1</a>形式的输出元素,可以将其存储在变量中并进行操作以获取所需的输出。

尝试使用此XSLT

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:output method="xml" indent="yes" />

  <xsl:template match="//root">
    <xsl:variable name="nodes">
      <xsl:call-template name="merge">
        <xsl:with-param name="name" select="normalize-space(name)" />
        <xsl:with-param name="value" select="normalize-space(value)" />
      </xsl:call-template>              
    </xsl:variable>
    <root>
      <out>
        <xsl:value-of select="$nodes/*[normalize-space()]/concat(name(), ':', .)" separator="|" />
        <!-- Alternate approach if above does not work
        <xsl:for-each select="$nodes/*[normalize-space()]">
          <xsl:if test="position() > 1">|</xsl:if>
          <xsl:value-of select="concat(name(), ':', .)" />
        </xsl:for-each>
        -->
      </out>
      <xsl:copy-of select="$nodes/e" />
      <xsl:copy-of select="$nodes/j" />
    </root>
  </xsl:template>

  <xsl:template name="merge">
    <xsl:param name="name" />
    <xsl:param name="value" />
    <xsl:param name="separator" select="','" />  
    <xsl:variable name="currentName" select="substring-before($name, $separator)"/>
    <xsl:if test="$currentName!=''">
      <xsl:element name="{$currentName}">
        <xsl:value-of select="substring-before($value, $separator)" />  
      </xsl:element>
      <xsl:call-template name="merge">
        <xsl:with-param name="value" select="normalize-space(substring-after($value, $separator))" />
        <xsl:with-param name="name" select="normalize-space(substring-after($name, $separator))" />
      </xsl:call-template>
    </xsl:if>
  </xsl:template> 
</xsl:stylesheet>

http://xsltfiddle.liberty-development.net/6qVRKwD/1上查看它的运行情况

答案 1 :(得分:0)

当您从过程性思维出发时,很自然地用诸如“当我遍历值时,我会计算出以后需要作为副作用的其他东西”之类的术语来思考。通常,对于函数式编程,最好以不同的方式思考。独立计算输出中需要的每件事,不要尝试在输入的一次传递中计算几件事。

但是,您可以做的是将变量预先计算为输入数据的函数,如果它们将不只一次有用的话。因此,在XSLT 2.0中,您可以做到

<xsl:variable name="names" select="tokenize(name, ',')"/>
<xsl:variable name="values" select="tokenize(value, ',')"/>

然后您可以编写一个函数

<xsl:function name="f:value" as="xs:string">
  <xsl:param name="key" as="xs:string"/>
  <xsl:sequence select="$values[index-of($names, $key)]"/>
</xsl:function>

然后您可以这样做,例如:

<e><xsl:value-of select="f:value('e')"/></e>
<j><xsl:value-of select="f:value('j')"/></j>

如果可以使用XSLT 3.0,那么所有这些的自然解决方案是使用地图。像这样:

<xsl:variable name="names" select="tokenize(name, ',')"/>
<xsl:variable name="map" as="map(*)"
  select="map:merge(
           for-each-pair($names, 
                         tokenize(value, ','),
                         function($k, $v) {map{$k, $v}}))"/>
</xsl:variable>
<out>
  <xsl:value-of select="$names ! (. || ':' || $map(.))" separator="|"/>
</out>
<e>{$map?e}</e>
<j>{$map?j}</j>

答案 2 :(得分:0)

如果至少具有XSLT 2支持,则可以使用tokenize并将两个值序列转换为某种XML,然后可以进一步处理:

<xsl:transform 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="#all" 
  version="2.0">

  <xsl:output method="xml" indent="yes" />

  <xsl:template match="root">
      <xsl:copy>
          <xsl:variable name="pairs">
              <xsl:variable name="values" select="tokenize(value, ',')"/>
              <xsl:for-each select="tokenize(name, ',')[. castable as xs:QName]">
                <xsl:element name="{.}">
                  <xsl:variable name="pos" select="position()"/>
                  <xsl:value-of select="$values[$pos]"/>
                </xsl:element>
              </xsl:for-each>
          </xsl:variable>
          <out>
              <xsl:value-of select="$pairs/(* except (e, j))[normalize-space()]/concat(name(), ':', .)" separator="|"/>
          </out>
          <xsl:copy-of select="$pairs/(e, j)"/>
      </xsl:copy>
  </xsl:template>

</xsl:transform>

http://xsltransform.hikmatu.com/gWcDMez

使用XSLT 3,您甚至可以将从tokenize返回的字符串序列推到模板中以构造中间XML(尽管我不得不承认我喜欢使用迈克尔·凯已经发布了超过XML的使用):

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    expand-text="yes"
    version="3.0">

  <xsl:output method="xml" indent="yes"/>

  <xsl:template match=".[. instance of xs:string]">
      <xsl:param name="values"/>
      <xsl:element name="{.}">{ let $pos := position() return $values[$pos] }</xsl:element>
  </xsl:template>

  <xsl:template match="root">
      <xsl:copy>
          <xsl:variable name="pairs">
              <xsl:apply-templates select="tokenize(name, ',')[. castable as xs:QName]">
                  <xsl:with-param name="values" select="tokenize(value, ',')"/>
              </xsl:apply-templates>
          </xsl:variable>
          <out>
              <xsl:value-of select="$pairs/(* except (e, j))[normalize-space()]!(name() || ':' || .)" separator="|"/>
          </out>
          <xsl:copy-of select="$pairs/(e, j)"/>
      </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/bdxtqz/1