XSLT преобразовывает плоскую структуру в массив - PullRequest
3 голосов
/ 12 июля 2011

Вот исходный XML:

<customers>
    <firstname1>Sean</firstname1>
    <lastname1>Killer</lastname1>
    <sex1>M</sex1>
    <firstname2>Frank</firstname2>
    <lastname2>Woods</lastname2>
    <sex2>M</sex2>
    <firstname3>Jennifer</firstname3>
    <lastname3>Lee</lastname3>
    <sex3>F</sex3>
</customers>

Как я могу преобразовать это в это?

<MyCustomers>
    <Customer>
        <Name> Sean Killer</Name>
        <Sex>M</Sex>
    </Customer>
    <Customer>
        <Name> Frank Woods</Name>
        <Sex>M</Sex>
    </Customer>
    <Customer>
        <Name>Jennifer Lee</Name>
        <Sex>F</Sex>
    </Customer>
</MyCustomers>

Ответы [ 3 ]

4 голосов
/ 12 июля 2011

По комментариям:

что, если элементы не были в последовательных заказах?

В этом случае (при условии XSLT 1.0) вы можете использовать translate() для получения идентификатора элементов, а затем искать соответствующие элементы по правильному имени, построенному с использованием concat(). Я бы изменил ось following-sibling:: на ../ (сокращение от parent::), чтобы в конечном итоге ловить также элементы, предшествующие текущему firstname.

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

    <xsl:template match="customers">
        <MyCustomers>
            <xsl:apply-templates select="*[starts-with(name(),'firstname')]"/>
        </MyCustomers>
    </xsl:template>

    <xsl:template match="*[starts-with(name(),'firstname')]">
        <xsl:variable name="id" select="translate(name(),'firstname','')"/>

        <Customer>
            <Name><xsl:value-of select="concat(.,' ',
                    ../*[name()=concat('lastname',$id)])"/></Name>
            <Sex><xsl:value-of select="../*[name()=concat('sex',$id)]"/></Sex>
        </Customer>
    </xsl:template>

</xsl:stylesheet>

Устаревший ответ

При условии фиксированной структуры входного документа, как показано в вопросе, отлично работающее преобразование XSLT 1.0:

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

    <xsl:template match="customers">
        <MyCustomers>
            <xsl:apply-templates select="*[starts-with(name(),'firstname')]"/>
        </MyCustomers>
    </xsl:template>

    <xsl:template match="*[starts-with(name(),'firstname')]">
        <Customer>
            <Name><xsl:value-of select="concat(.,' ',
                    following-sibling::*[1]
                    [starts-with(name(),'lastname')])"/></Name>
            <Sex><xsl:value-of select="following-sibling::*[2]
                    [starts-with(name(),'sex')]"/></Sex>
        </Customer>
    </xsl:template>

</xsl:stylesheet>

Маленькое объяснение

Вам нужна функция XPath 1.0 starts-with() из-за печального названия тегов в вашем XML-вводе. Вы можете использовать ось following-sibling::, чтобы получить следующие обязательные теги родственного элемента любого элемента, имя которого начинается с firstname.

0 голосов
/ 12 июля 2011

Это преобразование дает желаемый результат, даже когда дочерние элементы верхнего элемента перетасовываются произвольным образом :

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

 <xsl:variable name="vNumCustomers"
      select="count(/*/*) div 3"/>

 <xsl:template match="/*">
     <MyCustomers>
       <xsl:for-each select=
           "*[not(position() > $vNumCustomers)]">
         <xsl:variable name="vNum" select="position()"/>

         <Customer>
          <Name>
            <xsl:value-of select=
             "concat(/*/*[name()=concat('firstname',$vNum)],
                     ' ',
                     /*/*[name()=concat('lastname',$vNum)]
                     )
             "/>
          </Name>
          <Sex>
            <xsl:value-of select=
             "/*/*[name()=concat('sex',$vNum)]
             "/>
          </Sex>
         </Customer>
       </xsl:for-each>
     </MyCustomers>
 </xsl:template>
</xsl:stylesheet>

при применении к этому документу XML (произвольная перестановка предоставленного документа):

<customers>
    <sex1>M</sex1>
    <lastname2>Woods</lastname2>
    <lastname1>Killer</lastname1>
    <sex2>M</sex2>
    <firstname3>Jennifer</firstname3>
    <firstname2>Frank</firstname2>
    <lastname3>Lee</lastname3>
    <firstname1>Sean</firstname1>
    <sex3>F</sex3>
</customers>

желаемый, правильный результат получается :

<MyCustomers>
   <Customer>
      <Name>Sean Killer</Name>
      <Sex>M</Sex>
   </Customer>
   <Customer>
      <Name>Frank Woods</Name>
      <Sex>M</Sex>
   </Customer>
   <Customer>
      <Name>Jennifer Lee</Name>
      <Sex>F</Sex>
   </Customer>
</MyCustomers>

Объяснение

  1. Мы рассчитываем количество клиентов, чьи данные представлены. Переменная $vNumCustomers содержит эти данные.

  2. Для каждого клиента {i} (от i = 1 до $vNumCustomers) мы создаем соответствующий элемент <Customer{i}>. Чтобы избежать использования рекурсии, мы используем метод Пиза здесь.

0 голосов
/ 12 июля 2011

Вот таблица стилей XSLT 2.0, которая получит искомый вывод, даже если они не в порядке. Также сортирует по именам элементов "firstname".

Пример ввода XML (смешан для отображения другого порядка):

<customers>
  <lastname1>Killer</lastname1>
  <sex3>F</sex3>
  <firstname2>Frank</firstname2>
  <firstname1>Sean</firstname1>
  <lastname2>Woods</lastname2>
  <sex2>M</sex2>
  <firstname3>Jennifer</firstname3>
  <sex1>M</sex1>
  <lastname3>Lee</lastname3>
</customers>

Таблица стилей XSLT 2.0 (протестирована с Saxon-HE 9.3):

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:template match="node()|@*">
    <xsl:choose>
      <xsl:when test="name()[starts-with(.,'firstname')]">
        <xsl:variable name="suffix" select="substring(name(),10)"></xsl:variable>
        <xsl:message><xsl:value-of select="$suffix"/></xsl:message>
        <customer>
          <Name>
            <xsl:value-of select="concat(.,' ',/customers/*[starts-with(name(),'lastname')][ends-with(name(),$suffix)])"/>  
          </Name>
          <Sex>
            <xsl:value-of select="/customers/*[starts-with(name(),'sex')][ends-with(name(),$suffix)]"/>
          </Sex>
        </customer>
      </xsl:when>
      <xsl:when test="name()='customers'">
        <MyCustomers>
          <xsl:apply-templates>
            <xsl:sort select="name()[starts-with(.,'firstname')]"></xsl:sort>
          </xsl:apply-templates>
        </MyCustomers>
      </xsl:when>
      <xsl:otherwise>
        <xsl:apply-templates select="node()|@*"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

</xsl:stylesheet>

Выход:

<MyCustomers>
   <customer>
      <Name>Sean Killer</Name>
      <Sex>M</Sex>
   </customer>
   <customer>
      <Name>Frank Woods</Name>
      <Sex>M</Sex>
   </customer>
   <customer>
      <Name>Jennifer Lee</Name>
      <Sex>F</Sex>
   </customer>
</MyCustomers>
...