3

(注意:我已经按照建议发布了我之前问题的变体)

给定一个具有以下结构的输入 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>
4

3 回答 3

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>我们为每个值输出一个$vShapes

  3. 对于其中包含的所有可能的材料,$vMaterials我们输出一个或多个<td>具有属性的元素class,其值在两种不同的情况下确定:

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

  5. 第二种情况是为这种形状和材料的组合指定了一种或多种颜色。然后,对于每一种这样的颜色,都会输出一个单独的<td>元素,并且它的class属性作为材质和颜色的连接生成,由空格分隔。

于 2009-04-21T20:35:12.173 回答
2

我对问题第 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>而不是两个。其中两个用于迭代,一个用于通过and查找<widget>s 。@shape@material

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

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

于 2009-04-21T19:35:28.713 回答
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>
于 2009-04-22T18:51:28.500 回答