XSLT для денормализации / сводить / сглаживать XML-файл? Часть 2 - PullRequest
3 голосов
/ 21 апреля 2009

(Примечание: я разместил вариант на мой предыдущий вопрос в соответствии с предложением)

Имеется входной XML-файл со следующей структурой:

  <widgets>
    <widget shape="square" material="wood" color="red" />
    <widget shape="square" material="metal" color="blue" />
    <widget shape="square" material="plastic" color="green" />
    <widget shape="square" material="kevlar" color="red" />
    <widget shape="round" material="metal" color="orange" />
    <widget shape="round" material="wood" color="green" />
    <widget shape="round" material="kevlar" color="blue" />
    <widget shape="diamond" material="plastic" color="blue" />
    <widget shape="diamond" material="wood" color="brown" />
    <widget shape="diamond" material="metal" color="red" />
  </widgets>

И следующая информация:

  1. Каждый виджет имеет форму, материал и цвет
  2. Каждая форма, материал и цветовая комбинация уникальны
  3. Не каждая комбинация формы, материала и цвета существует, например, нет круглого, пластикового виджета
  4. Может быть неограниченное количество форм, материалов и цветов
  5. Желаемый вывод - это таблица, в которой каждая строка представляет форму, а каждый столбец представляет материал.

Как вывести следующую структуру, используя XSLT?

  <table>
    <tr id="diamond">
      <td class="kevlar"></td>
      <td class="metal red"></td>
      <td class="plastic blue"></td>
      <td class="wood brown"></td>
    </tr>
    <tr id="round">
      <td class="kevlar blue"></td>
      <td class="metal orange"></td>
      <td class="plastic"></td>
      <td class="wood green"></td>
    </tr>
    <tr id="square">
      <td class="kevlar green"></td>
      <td class="metal blue"></td>
      <td class="plastic green"></td>
      <td class="wood red"></td>
    </tr>
  </table>

Ответы [ 3 ]

3 голосов
/ 22 апреля 2009

Это преобразование:

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

 <xsl:key name="kShapeByVal" match="@shape"
  use="."/>

 <xsl:key name="kMaterByVal" match="@material"
  use="."/>

 <xsl:key name="kcolorByVal" match="@color"
  use="."/>

 <xsl:key name="kColorByShapeAndMat" match="@color"
  use="concat(../@shape, '+', ../@material)"/>

  <xsl:variable name="vShapes" select=
  "/*/*/@shape
          [generate-id()
          =
           generate-id(key('kShapeByVal',.)[1])
           ]
  "/>

  <xsl:variable name="vMaterials" select=
  "/*/*/@material
          [generate-id()
          =
           generate-id(key('kMaterByVal',.)[1])
           ]
  "/>

  <xsl:variable name="vColors" select=
  "/*/*/@color
          [generate-id()
          =
           generate-id(key('kcolorByVal',.)[1])
           ]
  "/>

    <xsl:template match="/*">
      <table>
         <xsl:for-each select="$vShapes">
           <xsl:sort select="."/>

           <xsl:variable name="vShape" select="."/>

           <tr id="{.}">
             <xsl:for-each select="$vMaterials">
               <xsl:sort select="."/>

               <xsl:variable name="vMat" select="."/>

               <xsl:variable name="vShapeMatColors" select=
               "key('kColorByShapeAndMat',
                    concat($vShape, '+', $vMat)
                   )
                "/>

                <xsl:if test="not($vShapeMatColors)">
                  <td class="{$vMat}"></td>
                </xsl:if>

                <xsl:for-each select="$vShapeMatColors">
                  <td class="{concat($vMat, ' ', .)}"></td>
                </xsl:for-each>

               </xsl:for-each>
           </tr>
         </xsl:for-each>
      </table>
    </xsl:template>

</xsl:stylesheet>

при применении к предоставленному документу XML:

<widgets>
    <widget shape="square" material="wood" color="red" />
    <widget shape="square" material="metal" color="blue" />
    <widget shape="square" material="plastic" color="green" />
    <widget shape="square" material="kevlar" color="red" />
    <widget shape="round" material="metal" color="orange" />
    <widget shape="round" material="wood" color="green" />
    <widget shape="round" material="kevlar" color="blue" />
    <widget shape="diamond" material="plastic" color="blue" />
    <widget shape="diamond" material="wood" color="brown" />
    <widget shape="diamond" material="metal" color="red" />
</widgets>

дает желаемый результат:

<table>
   <tr id="diamond">
      <td class="kevlar"/>
      <td class="metal red"/>
      <td class="plastic blue"/>
      <td class="wood brown"/>
   </tr>
   <tr id="round">
      <td class="kevlar blue"/>
      <td class="metal orange"/>
      <td class="plastic"/>
      <td class="wood green"/>
   </tr>
   <tr id="square">
      <td class="kevlar red"/>
      <td class="metal blue"/>
      <td class="plastic green"/>
      <td class="wood red"/>
   </tr>
</table>

Как все это работает :

  1. Используя метод группировки Мюнхена, мы находим все различные формы, материалы и цвета - в переменных $vShapes, $vMaterials и $vColors.

  2. Мы выводим <tr> для каждого значения в $vShapes

  3. Для всех возможных материалов, содержащихся в $vMaterials, мы выводим один или несколько <td> элементов с атрибутом class, значение которого определяется в двух отдельных случаях:

  4. Первый случай, когда для этой комбинации формы и материала не указан цвет (key('kColorByShapeAndMat', concat($vShape, '+', $vMat) пусто). В этом случае атрибут класса содержит только материал.

  5. Второй случай, когда для этой комбинации формы и материала указан один или несколько цветов . Затем для каждого такого цвета выводится отдельный элемент <td>, а его атрибут class создается как объединение материала и цвета, разделенных пробелом.

2 голосов
/ 21 апреля 2009

Вариант моего ответа на ваша часть 1 вопроса делает это :

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

  <!-- prepare some keys for later use -->
  <xsl:key name="kWidgetsByShape"       match="widget" use="@shape" />
  <xsl:key name="kWidgetsByMaterial"    match="widget" use="@material" />
  <xsl:key name="kWidgetsByComposition" match="widget" use="concat(@shape, ',', @material)" />

  <!-- select the <widget>s that are the first in their respective @shape -->
  <xsl:variable name="vShapes" select="
    /widgets/widget[
      generate-id()
      =
      generate-id(key('kWidgetsByShape', @shape)[1])
    ]
  " />  

  <!-- select the <widget>s that are the first in their respective @material -->
  <xsl:variable name="vMaterials" select="
    /widgets/widget[
      generate-id()
      =
      generate-id(key('kWidgetsByMaterial', @material)[1])
    ]
  " />

  <!-- output basic table structure -->
  <xsl:template match="/widgets">
    <table title="shapes: {count($vShapes)}, materials: {count($vMaterials)}">
      <xsl:apply-templates select="$vShapes" mode="tr">
        <xsl:sort select="@shape" />
      </xsl:apply-templates>
    </table>
  </xsl:template>

  <!-- output the <tr>s, one for each @shape -->
  <xsl:template match="widget" mode="tr">
    <tr id="{@shape}">
      <xsl:apply-templates select="$vMaterials" mode="td">
        <xsl:sort select="@material" />
        <xsl:with-param name="vCurrentShape" select="@shape" />
      </xsl:apply-templates>
    </tr>
  </xsl:template>

  <!-- output the right number of <td>s in each row, empty or not -->
  <xsl:template match="widget" mode="td">
    <xsl:param name="vCurrentShape" />

    <xsl:variable 
      name="vWidget" 
      select="key('kWidgetsByComposition', concat($vCurrentShape, ',', @material))[1]" 
    />

    <td class="{normalize-space(concat(@material, ' ', $vWidget/@color))}">
      <xsl:apply-templates select="$vWidget" />
    </td>
  </xsl:template>

  <xsl:template match="widget">
    <xsl:value-of select="." />
  </xsl:template>

</xsl:stylesheet>

Который производит:

<table title="shapes: 3, materials: 4">
  <tr id="diamond">
    <td class="kevlar"></td>
    <td class="metal red"></td>
    <td class="plastic blue"></td>
    <td class="wood brown"></td>
  </tr>
  <tr id="round">
    <td class="kevlar blue"></td>
    <td class="metal orange"></td>
    <td class="plastic"></td>
    <td class="wood green"></td>
  </tr>
  <tr id="square">
    <td class="kevlar red"></td>
    <td class="metal blue"></td>
    <td class="plastic green"></td>
    <td class="wood red"></td>
  </tr>
</table>

В основном все, что я сказал в моем другом ответе, все еще применимо.

На этот раз я использовал три <xsl:key> с вместо двух. Два из них используются для итерации, а один - для поиска <widget> s по @shape и @material.

Я использую различные режимы шаблонов вместе с <xsl:apply-templates> вместо <xsl:for-each>. Это делает код на несколько строк длиннее, но это выгодно для ясности и читабельности.

Последний шаблон (<xsl:template match="widget">) предназначен только для демонстрации и показывает, как вы можете продолжать. Он вызывается изнутри <xsl:template match="widget" mode="td">, один раз для каждого <widget>, который действительно существует.

1 голос
/ 22 апреля 2009

Как сказано в комментарии от eft, вот решение 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"
    exclude-result-prefixes="xs"
    >
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

    <xsl:key name="kColorByShapeAndMat" match="@color"
     use="concat(../@shape, '+', ../@material)"/>

    <xsl:template match="/*">
      <xsl:for-each-group select="*/@shape" group-by=".">
        <xsl:sort select="."/>

        <xsl:variable name="vShape" select="current-grouping-key()"/>
        <tr id="{.}">
          <xsl:for-each-group select="/*/*/@material" group-by=".">
            <xsl:sort select="."/>

              <xsl:variable name="vMat" select="."/>

              <xsl:variable name="vColors" 
               select="key('kColorByShapeAndMat',
                            concat($vShape,'+',.)
                         )"/>
            <xsl:for-each select="''[empty($vColors)],$vColors/concat(' ',.)">
             <xsl:sort select="."/>

             <td class="{concat($vMat,.)}"></td>
            </xsl:for-each>
          </xsl:for-each-group>
        </tr>
      </xsl:for-each-group>
    </xsl:template>
</xsl:stylesheet>

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

<widgets>
    <widget shape="square" material="wood" color="red" />
    <widget shape="square" material="metal" color="blue" />
    <widget shape="square" material="plastic" color="green" />
    <widget shape="square" material="kevlar" color="red" />
    <widget shape="round" material="metal" color="orange" />
    <widget shape="round" material="wood" color="green" />
    <widget shape="round" material="kevlar" color="blue" />
    <widget shape="diamond" material="plastic" color="blue" />
    <widget shape="diamond" material="wood" color="brown" />
    <widget shape="diamond" material="metal" color="red" />
</widgets>

необходим требуемый результат :

<tr id="diamond">
   <td class="kevlar"/>
   <td class="metal red"/>
   <td class="plastic blue"/>
   <td class="wood brown"/>
</tr>
<tr id="round">
   <td class="kevlar blue"/>
   <td class="metal orange"/>
   <td class="plastic"/>
   <td class="wood green"/>
</tr>
<tr id="square">
   <td class="kevlar red"/>
   <td class="metal blue"/>
   <td class="plastic green"/>
   <td class="wood red"/>
</tr>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...