按部分值对子元素进行分组

时间:2011-04-16 14:20:27

标签: xslt xpath xslt-2.0

我有这个XML文件:

<Elements>
  <Element name="A.B.C.x">
    <Child>...</Child>
    <Child>...</Child>
    <Child>...</Child>
  </Element>
  <Element name="A.B.C.y">
    <Child>...</Child>
    <Child>...</Child>
  </Element>
  <Element name="A.D.E.y">
    <Child>...</Child>
  </Element>
  <Element name="A.D.E.z">
    <Child>...</Child>
    <Child>...</Child>
    <Child>...</Child>
  </Element>
</Elements>

我需要创建XSL来获得这个结果:

<Elements>
  <Element name="A.B.C">
    <LastToken name="x" childCount="3" />
    <LastToken name="y" childCount="2" />
  </Element>
  <Element name="A.D.E">
    <LastToken name="y" childCount="1" />
    <LastToken name="z" childCount="3" />
  </Element>
</Elements>

我仅限于没有扩展的XSL 1.0,我无法弄清楚如何实现结果。

任何帮助表示赞赏。
提前致谢。

编辑:当我看到一些答案时,我必须澄清我的问题/任务:
name节点的Element属性中的标记不限于一个字符。 “name”属性的示例值可以是This.Is.Grouping.Target.AndThisIsGroupChild

4 个答案:

答案 0 :(得分:2)

此XSLT 1.0转换(绝对没有限制):

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common"
 exclude-result-prefixes="ext">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kElemByName" match="Element"
          use="@name"/>

 <xsl:key name="klastTokenByName" match="@lastToken"
  use="../@name"/>

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

 <xsl:template match="Element/@name">
  <xsl:attribute name="name">
   <xsl:call-template name="init"/>
  </xsl:attribute>
  <xsl:attribute name="lastToken">
   <xsl:call-template name="lastToken"/>
  </xsl:attribute>
 </xsl:template>

 <xsl:template match="/">
  <xsl:variable name="vrtfPass1">
   <xsl:apply-templates/>
  </xsl:variable>
  <xsl:apply-templates mode="pass2"
      select="ext:node-set($vrtfPass1)/*"/>
 </xsl:template>

 <xsl:template mode="pass2" match="Element"/>

 <xsl:template mode="pass2" match=
  "Element[generate-id()
          =
           generate-id(key('kElemByName',@name)[1])
          ]
  ">
  <Element name="{@name}">
    <xsl:for-each select=
    "key('klastTokenByName',@name)">

     <lastToken name="{.}"
       childCount="{count(key('kElemByName',../@name)
                               [@lastToken=current()]
                                 /Child
                          )
                    }"
     />
    </xsl:for-each>
  </Element>
 </xsl:template>

 <xsl:template name="lastToken">
  <xsl:param name="pText" select="."/>
  <xsl:param name="pDelim" select="'.'"/>

  <xsl:variable name="vrtfTokens">
   <xsl:call-template name="tokenize">
    <xsl:with-param name="pText" select="$pText"/>
    <xsl:with-param name="pDelim" select="$pDelim"/>
   </xsl:call-template>
  </xsl:variable>

  <xsl:value-of select=
   "ext:node-set($vrtfTokens)/*[last()]"/>
 </xsl:template>

 <xsl:template name="init">
  <xsl:param name="pText" select="."/>
  <xsl:param name="pDelim" select="'.'"/>

  <xsl:variable name="vLastToken">
    <xsl:call-template name="lastToken">
     <xsl:with-param name="pText" select="$pText"/>
     <xsl:with-param name="pDelim" select="$pDelim"/>
    </xsl:call-template>
  </xsl:variable>

  <xsl:value-of select=
   "substring($pText,
              1,
               string-length($pText)
              - string-length($vLastToken)
              - string-length($pDelim)
              )
   "/>
 </xsl:template>

 <xsl:template name="tokenize">
  <xsl:param name="pText"/>
  <xsl:param name="pDelim" select="'.'"/>

  <xsl:if test="string-length($pText)">
    <token>
     <xsl:value-of select=
      "substring-before(concat($pText,$pDelim),
                        $pDelim)"/>
    </token>
    <xsl:call-template name="tokenize">
     <xsl:with-param name="pText" select=
     "substring-after($pText,$pDelim)"/>
    </xsl:call-template>
  </xsl:if>
 </xsl:template>
</xsl:stylesheet>

应用于提供的XML文档

<Elements>
    <Element name="A.B.C.x">
        <Child>...</Child>
        <Child>...</Child>
        <Child>...</Child>
    </Element>
    <Element name="A.B.C.y">
        <Child>...</Child>
        <Child>...</Child>
    </Element>
    <Element name="A.D.E.y">
        <Child>...</Child>
    </Element>
    <Element name="A.D.E.z">
        <Child>...</Child>
        <Child>...</Child>
        <Child>...</Child>
    </Element>
</Elements>

产生了想要的正确结果:

<Elements>
   <Element name="A.B.C">
      <lastToken name="x" childCount="3"/>
      <lastToken name="y" childCount="2"/>
   </Element>
   <Element name="A.D.E">
      <lastToken name="y" childCount="1"/>
      <lastToken name="z" childCount="3"/>
   </Element>
</Elements>

答案 1 :(得分:1)

XSLT 2.0解决方案(绝对没有任何限制):

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:my="my:my" exclude-result-prefixes="xs my"
    >
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/">
     <xsl:for-each-group select="/*/*"
        group-by="substring(@name, 1,
                             string-length(@name)
                            - string-length(my:LastToken(@name)) -1)">
      <xsl:variable name="vLastToken"
           select="my:LastToken(@name)"/>
      <Element name="{substring(@name,1,
                                  string-length(@name)
                                 -
                                   string-length($vLastToken)-1)}">

       <xsl:for-each-group select="current-group()"
        group-by="my:LastToken(@name)">

        <xsl:variable name="vLastToken" select="my:LastToken(@name)"/>

        <LastToken name="{$vLastToken}"
                   childCount="{count(current-group()/Child)}"/>
       </xsl:for-each-group>

      </Element>
     </xsl:for-each-group>
 </xsl:template>

 <xsl:function name="my:LastToken" as="xs:string">
  <xsl:param name="pText" as="xs:string"/>

  <xsl:sequence select="tokenize($pText, '\.')[last()]"/>
 </xsl:function>
</xsl:stylesheet>

应用于提供的XML文档时:

<Elements>
    <Element name="A.B.C.x">
        <Child>...</Child>
        <Child>...</Child>
        <Child>...</Child>
    </Element>
    <Element name="A.B.C.y">
        <Child>...</Child>
        <Child>...</Child>
    </Element>
    <Element name="A.D.E.y">
        <Child>...</Child>
    </Element>
    <Element name="A.D.E.z">
        <Child>...</Child>
        <Child>...</Child>
        <Child>...</Child>
    </Element>
</Elements>

产生了想要的正确结果:

<Element name="A.B.C">
   <LastToken name="x" childCount="3"/>
   <LastToken name="y" childCount="2"/>
</Element>
<Element name="A.D.E">
   <LastToken name="y" childCount="1"/>
   <LastToken name="z" childCount="3"/>
</Element>

答案 2 :(得分:1)

只是为了好玩,一个没有扩展的通用XSLT 1.0解决方案:

<!DOCTYPE xsl:stylesheet [
  <!ENTITY key "
substring(
   @name,
   1,
   string-length(
      @name
   )
 - count(
      document('')//node()[
         not(
            contains(
               substring(
                  current()/@name,
                  string-length(
                     current()/@name
                  )
                - position()
                + 1
               ),
               '.'
            )
         )
      ]
   )
 - 1
)">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="kElementByNamePrefix" match="Element" use="&key;"/>
    <xsl:key name="kElementByName" match="Element" use="@name"/>
    <xsl:template match="Element">
        <xsl:variable name="vNamePrefix" select="&key;"/>
        <xsl:variable name="vCurrentGroup"
         select="key('kElementByNamePrefix',$vNamePrefix)"/>
        <xsl:if test="generate-id() = generate-id($vCurrentGroup[1])">
            <Element name="{$vNamePrefix}">
                <xsl:apply-templates
                 select="$vCurrentGroup[
                            generate-id()
                          = generate-id(
                               key('kElementByName',@name)[1]
                            )
                         ]"
                 mode="prefix">
                    <xsl:with-param name="pNamePrefix" select="$vNamePrefix"/>
                </xsl:apply-templates>
            </Element>
        </xsl:if>
    </xsl:template>
    <xsl:template match="Element" mode="prefix">
        <xsl:param name="pNamePrefix"/>
        <LastToken name="{substring(substring-after(@name,$pNamePrefix),2)}"
                   childCount="{count(key('kElementByName',@name)/Child)}"/>
    </xsl:template>
</xsl:stylesheet>

输出:

<Element name="A.B.C">
    <LastToken name="x" childCount="3" />
    <LastToken name="y" childCount="2" />
</Element>
<Element name="A.D.E">
    <LastToken name="y" childCount="1" />
    <LastToken name="z" childCount="3" />
</Element>

答案 3 :(得分:0)

使用Muenchian grouping

<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">

<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:key name="k1" match="Element" use="substring(@name, 1, 5)"/>

<xsl:key name="k2" match="Element" use="@name"/>

<xsl:template match="Elements">
  <xsl:copy>
    <xsl:apply-templates select="Element[generate-id() = generate-id(key('k1', substring(@name, 1, 5))[1])]"/>
  </xsl:copy>
</xsl:template>

<xsl:template match="Element">
  <Element name="{substring(@name, 1, 5)}">
    <xsl:apply-templates select="key('k1', substring(@name, 1, 5))[generate-id() = generate-id(key('k2', @name)[1])]" mode="token">
      <xsl:sort select="substring(@name, 7)"/>
    </xsl:apply-templates>
  </Element>
</xsl:template>

<xsl:template match="Element" mode="token">
  <LastToken name="{substring(@name, 7)}" childCount="{count(key('k2', @name)/Child)}"/>
</xsl:template>

</xsl:stylesheet>