XSLT到denormalize / pivot / flatten xml文件?第2部分

时间:2009-04-21 18:55:58

标签: xml xslt pivot

(注意:我已根据建议在my earlier question发布了变体)

给定一个具有以下结构的输入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. 所需的输出是一个表格,其中每行代表一个形状,每列代表一种材料。
  6. 如何使用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 个答案:

答案 0 :(得分:3)

此转化:

<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. 使用Muenchian方法进行分组,我们在变量$vShapes$vMaterials$vColors中找到所有不同的形状,材质和颜色

  2. 我们为<tr>

  3. 中的每个值输出$vShapes
  4. 对于$vMaterials中包含的所有可能材料,我们输出一个或多个<td>元素,其属性为class,其值由两个独立的案例:

  5. 第一种情况是没有为此形状和材质组合指定颜色key('kColorByShapeAndMat', concat($vShape, '+', $vMat)为空)。在这种情况下,tha类属性仅包含材料。

  6. 第二种情况是指为此形状和材质组合指定了一种或多种颜色。然后,对于每种这样的颜色,输出​​单独的<td>元素,并将其class属性生成为材质和颜色的串联,用空格分隔。

答案 1 :(得分:2)

我对your part 1 of the question does it的回答的变体:

<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>@shape查找@material

我使用不同的模板模式与<xsl:apply-templates>而不是<xsl:for-each>一起使用。这使代码延长了几行,但它有利于清晰度和可读性。

最后一个模板(<xsl:template match="widget">)仅用于演示目的,向您展示如何继续。它是在<xsl:template match="widget" mode="td">内调用的,每次<widget>实际存在一次。

答案 2 :(得分:1)

正如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>