【问题标题】:Using XSLT, how do I separate nodes based on their value?使用 XSLT,我如何根据节点的值来分隔节点?
【发布时间】:2010-03-25 15:30:04
【问题描述】:

我有一个非常扁平的 XML 结构,我需要将其重新排序为分类的部分,而且,在我的一生中,我无法弄清楚如何在 XSLT 中做到这一点(并不是说我绝对是专家。 )

基本上,原始 XML 看起来有点像:

<things>
  <thing>
    <value>one</value>
    <type>a</type>
  </thing>
  <thing>
    <value>two</value>
    <type>b</type>
  </thing>
  <thing>
    <value>thee</value>
    <type>b</type>
  </thing>
  <thing>
    <value>four</value>
    <type>a</type>
  </thing>
  <thing>
    <value>five</value>
    <type>d</type>
  </thing>
</things>

我需要输出如下内容:

<data>
  <a-things>
    <a>one</a>
    <a>four</a>
  </a-things>
  <b-things>
    <b>two</b>
    <b>three</b>
  </b-things>
  <d-things>
    <d>five</d>
  </d-things>
</data>

请注意,如果没有任何 &lt;c&gt; 元素,我无法输出 &lt;c-things&gt;,但我确实提前知道完整的类型列表是什么,而且它相当短,因此每种类型的手动编码模板是绝对有可能。感觉就像我可以使用&lt;xsl:if&gt;&lt;xsl:for-each&gt; 一起破解一些东西,但也感觉必须有一个更...'模板'的方式来做到这一点。有人可以帮忙吗?

干杯。

【问题讨论】:

    标签: xslt xpath saxon


    【解决方案1】:

    当您使用 Saxon 时,请使用本机 XSLT 2.0 分组。

    <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
        <xsl:output method="xml" indent="yes" />
    
        <xsl:template match="things">
            <data>
                <xsl:for-each-group select="thing" group-by="type">
                    <xsl:element name="{concat(current-grouping-key(),'-things')}">
                        <xsl:for-each select="current-group()">
                            <xsl:element name="{current-grouping-key()}">
                                <xsl:value-of select="value" />
                            </xsl:element>
                        </xsl:for-each>
                    </xsl:element>
                </xsl:for-each-group>
            </data>
        </xsl:template>
    
    </xsl:stylesheet>
    

    在 XSLT 1.0 中,您可以使用键进行分组。这种方法称为Muenchian Grouping

    xsl:key 定义了一个包含thing 元素的索引,按其type 元素的字符串值分组。函数key()从key返回指定值的所有节点。

    外部 xsl:for-each 选择由 key() 第一个返回的 thing 元素作为它们的值。

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
        <xsl:output method="xml" indent="yes" />
    
        <xsl:key name="thing" match="thing" use="type" />
    
        <xsl:template match="things">
            <data>
                <xsl:for-each select="thing[generate-id(.)=generate-id(key('thing',type)[1])]">
                    <xsl:element name="{concat(type,'-things')}">
                        <xsl:for-each select="key('thing',type)">
                            <xsl:element name="{type}">
                                <xsl:value-of select="value" />
                            </xsl:element>
                        </xsl:for-each>
                    </xsl:element>
                </xsl:for-each>
            </data>
        </xsl:template>
    
    </xsl:stylesheet>
    

    【讨论】:

      【解决方案2】:

      一般的解决方案是使用 XSL 键:

      <xsl:key name="kThingByType" match="thing" use="type" />
      
      <xsl:template match="things">
        <xsl:copy>
          <xsl:apply-templates select="thing" mode="group">
            <xsl:sort select="type" />
          </xsl:apply-templates>
        </xsl:copy>
      </xsl:template>
      
      <xsl:template match="thing" mode="group">
        <xsl:variable name="wholeGroup" select="key('kThingByType', type)" />
        <xsl:if test="generate-id() = generate-id($wholeGroup[1])">
          <xsl:element name="{type}-thing">
            <xsl:copy-of select="$wholeGroup/value" />
          </xsl:element>
        </xsl:if>
      </xsl:template>
      

      以上产出:

      <things>
        <a-thing>
          <value>one</value>
          <value>four</value>
        </a-thing>
        <b-thing>
          <value>two</value>
          <value>thee</value>
        </b-thing>
        <d-thing>
          <value>five</value>
        </d-thing>
      </things>
      

      【讨论】:

        【解决方案3】:

        在 XSLT 2 中,您可以非常优雅地做到这一点。假设您有一个模板,用于在将每个事物包装在 元素中之前对其进行格式化:

        <xsl:template match="thing" mode="format-thing">
            <xsl:value-of select="value/text()"/>
        </xsl:template>
        

        然后您可以将其应用于某些 $type 的每个事物,以通过函数构建 元素:

        <xsl:function name="my:things-group" as="element()">
            <xsl:param name="type" as="xs:string"/>
            <xsl:param name="things" as="element(thing)*"/>
        
            <xsl:element name="{ concat($type, '-things') }">
                <xsl:for-each select="$things[type/text() eq $type]">
                    <xsl:element name="{ $type }">
                        <xsl:apply-templates select="." mode="format-thing"/>
                    </xsl:element>
                </xsl:for-each>
            </xsl:element>
        </xsl:function>
        

        然后您可以为每个唯一类型(示例输入中的 a、b、d)调用该函数来构建整个输出,然后您就完成了:

        <xsl:template match="/">
            <data>
                <xsl:sequence select="
                    for $type in distinct-values(things/thing/type/text())
                    return my:things-group($type, /things/thing)
                    "/>
            </data>
        </xsl:template>
        

        【讨论】:

          【解决方案4】:

          当然,问这个问题就很明显了……

          我的解决方案确实使用了&lt;xsl:if&gt;,但现在我想不出它是怎么做到的。我的解决方案基本上是这样的:

          <xsl:if test="/things/thing/type = 'a'">
            <a-things>
              <xsl:apply-templates select="/things/thing[type='a']" mode="a" />
            </a-things>
          </if>
          
          <xsl:template match="/things/thing[type='a']" mode="a">
              <a><xsl:value-of select="value"/>
          </xsl:template>
          

          然后重复其他类型。我已经把它编码了,它似乎工作得很好。

          【讨论】:

            【解决方案5】:
            <a-things>
                <xsl:for-each select="thing[type = 'a']">
                    <a><xsl:value-of select="./value" /></a>
                </xsl:for-each>
            </a-things>
            

            如果你想变得非常时髦,请将&lt;a-things&gt; 和谓词替换为参数并使用属性值模板:

            <xsl:param name="type" />
            <xsl:element name="{$type}-things">
                <xsl:for-each select="thing[type = $type]">
                    <xsl:element name="{$type}"><xsl:value-of select="./value" /></xsl:element>
                </xsl:for-each>
            </xsl:element>
            

            【讨论】:

              【解决方案6】:

              而使用分组,你可以不使用 if:

              <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
                <xsl:template match="things">
                  <data>
                    <xsl:for-each select="thing[not(type=preceding-sibling::thing/type)]">
                      <xsl:variable name="type"><xsl:value-of select="type" /></xsl:variable>
                        <xsl:element name="concat($type, '-things')">
                          <xsl:for-each select="../thing[type=$type]">
                            <xsl:element name="$type">
                              <xsl:value-of select="value" />
                            </xsl:element>
                          </xsl:for-each>
                        </xsl:element>
                    </xsl:for-each>
                  </data>
                </xsl:template>
              </xsl:stylesheet>
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2021-08-24
                • 2015-03-12
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多