Группировать дочерние элементы по частичным значениям - PullRequest
1 голос
/ 16 апреля 2011

У меня есть этот 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 ]

2 голосов
/ 16 апреля 2011

Это преобразование 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 голос
/ 18 апреля 2011

Просто для удовольствия, общее решение 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>
1 голос
/ 16 апреля 2011

Решение 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>
0 голосов
/ 16 апреля 2011

Использование Мюнхенская группировка :

<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>
...