Helpers - компьютеры, интернет, программирование

Сгруппировать дочерние элементы по частичному значению

У меня есть этот 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 без расширений и не могу понять, как добиться результата.

Любая помощь приветствуется.
Заранее спасибо.

EDIT: Когда пришло несколько ответов, я увидел, что должен уточнить свой вопрос/задачу:
Токены в атрибуте name узла Element не ограничены одним символом. Пример значения атрибута name может быть This.Is.Grouping.Target.AndThisIsGroupChild

16.04.2011

  • Отличный вопрос, +1. См. мой ответ для полного решения, которое делает только минимальное предположение о формате значения атрибута name. 16.04.2011
  • Также добавлено решение XSLT 2.0, в котором нет предположений относительно формата атрибута name. 16.04.2011
  • Полностью заменил решение XSLT 1.0 на решение, которое ничего не предполагает — это полное решение самого общего случая! :) 17.04.2011

Ответы:


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>
17.04.2011
  • @Ramunas: Добро пожаловать. В любом случае проверьте реализацию функции расширения node-set() вашего XSLT-процессора. Композиция — один из самых полезных паттернов в декларативной парадигме. 18.04.2011

  • 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>
    
    16.04.2011
  • Спасибо за ваш вклад, Дмитрий. К сожалению, токены в атрибуте name разделены точками и могут превышать один символ. Извините, я не ясно дал понять в своем вопросе (отредактировано). 16.04.2011
  • @Ramunas: заменил решение на самое общее - никаких ограничений не предполагается. Это решение, которое вам нужно. :) 17.04.2011
  • Namespace 'http://exslt.org/common' does not contain any functions. Ты хоть представляешь, почему я это понял? 18.04.2011

  • 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>
    
    16.04.2011
  • Это было бы идеальным решением для меня, если бы я не был привязан к XSL 1.0. 16.04.2011
  • @Ramunas: вы также пометили свой вопрос с помощью xquery, а xquery использует XPath 2.0. Будет ли решение XQuery действительно полезным для вас? 16.04.2011
  • Я думаю, только настолько, насколько процессор XSL может его использовать (если вообще). XSL не самый сильный мой навык, поэтому я не могу сказать, могут ли двое жить вместе и помогать друг другу. В основном у меня есть XML, и мне нужно создать XSL для преобразования XML в нужный формат с использованием процессора XSL 1.0. 16.04.2011
  • @Ramunas: Нет проблем, посмотрите мое последнее наиболее общее и полностью свободное от предположений решение XSLT 1.0. 17.04.2011
  • Namespace 'http://exslt.org/common' does not contain any functions. Ты хоть представляешь, почему я это понял? 18.04.2011
  • @ Рамунас: Да. Функция расширения xxx:node-set() зависит от поставщика. У каждого поставщика XSLT есть собственное пространство имен (а в случае с Xalan — даже другое имя) для этой функции. В своем решении я использую функцию ext:node-set() в пространстве имен EXSLT, поскольку большинство процессоров XSLT 1.0 реализуют EXSLT. Если ваш XSLT-процессор не поддерживает EXSLT, вам необходимо выяснить, в каком пространстве имен находится эта функция. Для MSXML это пространство имен: urn:schemas-microsoft-com:xslt. 18.04.2011
  • @Ramunas: Вы узнали, что такое ваш XSLT-процессор и какова его функция расширения xxx:node-set()? Это нужно знать не только для решения этой задачи. На практике большинство более или менее сложных задач в XSLT 1.0 решаются с помощью расширения xxx:node-set(). Кроме того, выясните, поддерживает ли ваш XSLT-процессор (любое подмножество) EXSLT. 19.04.2011
  • @Dimitre: я использую утилиту msxsl.exe (не могу найти ссылку с ее описанием). Я предполагаю, что это от Microsoft, и я предполагаю, что он использует MSXML. 19.04.2011
  • @Ramunas: Тогда вы должны определить это пространство имен: xmlns:msxsl="urn:schemas-microsoft-com:xslt" и заменить ext:node-set() на msxsl:node-set(). И используйте более простое решение :) 19.04.2011

  • 4

    Используйте мюнхенскую группировку:

    <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>
    
    16.04.2011
  • Извините, я не ясно дал понять в своем вопросе, что substring() не является решением для меня. 16.04.2011
  • Новые материалы

    Интуитивное понимание тензоров в машинном обучении
    Тензор является важной концепцией во многих научных областях, таких как математика, физика, обработка сигналов и компьютерное зрение, и это лишь некоторые из них. В математике тензор — это..

    Использование машинного обучения для диагностики болезни Альцгеймера, часть 4
    Маркеры семантической согласованности для ранней диагностики болезни Альцгеймера (arXiv) Автор: Давиде Колла , Маттео Дельсанто , Марко Агосто , Бенедетто Витиелло , Даниэле Паоло Радичони..

    Почему объяснимость так важна прямо сейчас?
    По мере того, как системы искусственного интеллекта и инструменты на основе машинного обучения распространяются в нашей повседневной жизни, как практики, так и критики все чаще заявляют о..

    Анимированный математический анализ
    Использование Manim для создания математических анимированных визуализаций Визуализация данных помогает понять скрытые закономерности в данных, которые невозможно визуализировать..

    Создание простого слайдера изображений с помощью JavaScript
    Узнайте, как создать базовый слайдер изображений с помощью HTML, CSS и JavaScript. Введение В этом уроке мы создадим удобный слайдер изображений, используя JavaScript, HTML и CSS. Ползунок..

    Создание базы данных с помощью супергероя «Python»
    В этом посте мы узнаем, как создать «базу данных SQLite с помощью модуля python sqlite3, создав простую функцию входа и регистрации. Готовы ли вы к этому путешествию? Если да , давайте приступим..

    ИИ для чайников: руководство для начинающих по пониманию будущего технологий
    Вы чувствуете, что остались позади в мире ИИ? Не волнуйтесь, вы не одиноки! Со всей этой шумихой вокруг искусственного интеллекта может быть трудно понять, с чего начать. Но не позволяйте сленгу..