【问题标题】:Group child elements by partial value按部分值对子元素进行分组
【发布时间】:2011-04-16 14:20:27
【问题描述】:

我有这个 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,我不知道如何实现结果。

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

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

【问题讨论】:

  • 很好的问题,+1。请参阅我的答案以获得完整的解决方案,该解决方案仅对 name 属性值的格式做出最小假设。
  • 还添加了一个 XSLT 2.0 解决方案,它对name 属性的格式有没有个假设。
  • 完全用不假设任何东西的 XSLT 1.0 解决方案取代了 XSLT 1.0 解决方案——这是最一般情况下的完整解决方案! :)

标签: xslt xpath xslt-2.0


【解决方案1】:

这个 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>

【讨论】:

  • 感谢您的意见,Dimitre。不幸的是,name 中的标记属性由点分隔,并且可以超过一个字符。抱歉,我没有在我的问题中说清楚(已编辑)。
  • @Ramunas:将解决方案替换为最通用的解决方案——假设没有限制。这是您需要的解决方案。 :)
  • Namespace 'http://exslt.org/common' does not contain any functions. 你知道我为什么会得到这个吗?
【解决方案2】:

只是为了好玩,没有扩展的通用 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>

【讨论】:

  • @Ramunas:不客气。以任何方式检查您的 XSLT 处理器的 node-set() 扩展函数实现。组合是声明式范式中最有用的模式之一。
【解决方案3】:

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>

【讨论】:

  • 如果我没有绑定到 XSL 1.0,这对我来说将是一个完美的解决方案。
  • @Ramunas:您还用“xquery”标记了您的问题,xquery 使用 XPath 2.0。 XQuery 解决方案真的对您有用吗?
  • 我想只有 XSL 处理器可以利用它(如果有的话)。 XSL 不是我最强的技能,所以我不知道两个人是否可以一起生活并互相帮助。基本上我有 XML 并且必须创建 XSL 以使用 XSL 1.0 处理器将 XML 转换为所需的格式。
  • @Ramunas:没问题,看看我最新的最通用且完全无假设的 XSLT 1.0 解决方案。
  • Namespace 'http://exslt.org/common' does not contain any functions. 你知道我为什么会得到这个吗?
【解决方案4】:

使用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>

【讨论】:

  • 抱歉,我没有在我的问题中明确表示 substring() 不是我的解决方案。
猜你喜欢
  • 2016-08-28
  • 1970-01-01
  • 1970-01-01
  • 2011-08-13
  • 1970-01-01
  • 1970-01-01
  • 2011-11-26
  • 1970-01-01
相关资源
最近更新 更多