2009-04-21 7 views
3

(Note: J'ai posté une variation sur my earlier question comme suggéré)XSLT pour dénormaliser/pivoter/aplatir le fichier xml? Partie 2

Compte tenu d'un fichier xml d'entrée avec la structure suivante:

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

Et les informations suivantes:

  1. Chaque widget a une forme, un matériau et une couleur
  2. Chaque combinaison de forme, de matériau et de couleur est unique
  3. Toutes les combinaisons de formes, de matériaux et de couleurs n'existent pas. Il n'y a pas de widget rond en plastique.
  4. Les formes, les matériaux et les couleurs peuvent être illimités
  5. La sortie souhaitée est un tableau où chaque ligne représente une forme et chaque colonne représente un matériau.

Comment puis-je générer la structure suivante à l'aide de 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> 
+0

Il manque encore des informations pertinentes. Dans le résultat recherché quelle est la valeur qui a une matière mais pas de couleur? Devons-nous deviner cela? –

+0

Il n'y a pas de valeur à savoir il n'y a pas de widget en forme de diamant kevlar et pas de widget en plastique de forme ronde. J'ai seulement ajouté ces cellules pour la complétude mais leur contenu sera vide. – eft

Répondre

2

Une variante de ma réponse à 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> 

qui produit:

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

Fondamentalement, tout ce que je disais dans mon autre réponse sont encore valables.

Cette fois, j'ai utilisé trois <xsl:key> s au lieu de deux. Deux d'entre eux sont utilisés pour l'itération, et un pour rechercher <widget> s par @shape et @material.

J'utilise différents modes de modèle en conjonction avec <xsl:apply-templates> au lieu de <xsl:for-each>. Cela rend le code plus long de quelques lignes, mais il est avantageux pour la clarté et la lisibilité.

Le dernier modèle (<xsl:template match="widget">) est là uniquement à des fins de démonstration, vous montrant comment vous pourriez continuer. Il est appelé depuis <xsl:template match="widget" mode="td">, une fois pour chaque <widget> qui existe réellement.

3

Cette transformation:

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

lorsqu'il est appliqué sur le document XML fourni:

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

produit le résultat recherché:

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

comment tout cela fonctionne:

  1. En utilisant la méthode Muenchian pour grouper, nous trouvons toutes les différentes formes, les matériaux et les couleurs - dans les variables $vShapes, $vMaterials et $vColors.

  2. Nous sortie un <tr> pour chaque valeur $vShapes

  3. Pour tous les matériaux possibles contenues dans $vMaterials nous affichons un ou plusieurs <td> éléments avec l'attribut class dont la valeur est déterminée en deux séparés cases:

  4. Le premier cas est lorsqu'il n'y a pas de couleur spécifiée pour cette combinaison de forme et de matière (key('kColorByShapeAndMat', concat($vShape, '+', $vMat) est vide). Dans ce cas, l'attribut class contient uniquement le matériau.

  5. Le deuxième cas est lorsqu'il existe une ou plusieurs couleurs spécifiées pour cette combinaison de forme et de matériau. Ensuite, pour chaque couleur de ce type, un élément <td> distinct est généré et son attribut class est produit en tant que concaténation du matériau et de la couleur, séparés par un espace.

+0

Merci Dimitre - questions: 1) Ai-je raison de dire que kcolorByVal et vColors ne sont pas réellement utilisés dans la transformation? 2) Votre solution est-elle significativement différente de celle de Tomalak (à l'exception de l'approche de correspondance de modèles)? 3) Serait un XSLT 2.0 solution soit plus rapide pour les grands ensembles de données? – eft

+0

@ eft Oui, kcolorByVal et vColors ne sont pas nécessaires, est resté dans le processus d'affinage de la solution. Comme pour une solution XSLT 2.0, cela dépend des capacités d'optimisation du processeur XSLT 2.0 particulier utilisé. Voulez-vous que je fournisse une solution XSLT 2.0 dans une réponse séparée? –

+0

@eft quant à la réponse de Tomalak, je pense que les deux réponses sont similaires. Cependant, j'ai personnellement des difficultés à comprendre le code de Tomalak - par exemple il a une variable nommée $ vShapes qui contient ... des éléments "widget". –

1

Comme demandé dans un coment par TEF, voici une solution 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> 

lorsque cette transformation est appliquée sur le document XML fourni initialement:

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

le résultat requis est produit:

<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> 
+0

Dimitre - Merci beaucoup - Je vais essayer – eft

Questions connexes