2010-12-13 51 views
1

Disons que je XML qui ressemble donc (et suppose que je ne peux pas modifier le format de ce XML):Combinant des éléments de 2 listes dans XSLT

<biscuit> 
<name>Hobnobs</name> 
<price>1.49</price> 
<name>Digestives</name> 
<price>89.00</price> 
</biscuit> 


<biscuitInfo name="Hobnobs"> 
    <nutritionalValue> 
    <fat>6 grams</fat> 
    <sugar>lots</sugar> 
    </nutritionalValue>  
    </biscuitInfo> 
    <biscuitInfo name="Digestives"> 
    <nutritionalValue> 
    <fat>3 grams</fat> 
    <sugar>5 grams</sugar> 
    </nutritionalValue> 
</biscuitInfo> 

Et je voudrais utiliser XSLT pour transformer cela en quelque chose qui ressemble à ceci:

<biscuit> 
    <name>Hobnobs</name> 
    <price>1.49</price> 
    <fat>6 grams</fat> 
    <sugar>lots</sugar> 
</biscuit> 

Comment dois-je faire quelque chose comme ça dans XSLT? Puis-je faire défiler la première liste de biscuits (nom & prix) et tirer les éléments de la deuxième liste (valeurs nutritionnelles)? Je ne suis pas trop avertis avec XSL, donc tous les conseils seraient les bienvenus.

Cheers,

JD.

Répondre

1

Remplacement d'une règle d'identité traditionnelle par quelques modèles.Pas de clés, pas traversal grain fin:

<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="node()|@*" name="identity"> 
    <xsl:copy> 
    <xsl:apply-templates select="node()|@*"/> 
    </xsl:copy> 
</xsl:template> 

<xsl:template match="biscuit/name"> 
    <biscuit> 
    <xsl:call-template name="identity"/> 
    <xsl:apply-templates select= 
    "following-sibling::price[1] 
    | 
     ../../biscuitInfo[@name = current()]/*"/> 
    </biscuit> 
</xsl:template> 

<xsl:template match="biscuit"> 
    <xsl:apply-templates select="name"/> 
</xsl:template> 

<xsl:template match="nutritionalValue"> 
    <xsl:apply-templates/> 
</xsl:template> 
<xsl:template match="biscuitInfo"/> 
</xsl:stylesheet> 

Appliqué sur cette source XML (essentiellement celui fourni, mais enveloppé dans un seul élément supérieur):

<product> 
    <biscuit> 
     <name>Hobnobs</name> 
     <price>1.49</price> 
     <name>Digestives</name> 
     <price>89.00</price> 
    </biscuit> 
    <biscuitInfo name="Hobnobs"> 
     <nutritionalValue> 
      <fat>6 grams</fat> 
      <sugar>lots</sugar> 
     </nutritionalValue> 
    </biscuitInfo> 
    <biscuitInfo name="Digestives"> 
     <nutritionalValue> 
      <fat>3 grams</fat> 
      <sugar>5 grams</sugar> 
     </nutritionalValue> 
    </biscuitInfo> 
</product> 

la recherché, résultat correct est produit:

<product> 
    <biscuit> 
     <name>Hobnobs</name> 
     <price>1.49</price> 
     <fat>6 grams</fat> 
     <sugar>lots</sugar> 
    </biscuit> 
    <biscuit> 
     <name>Digestives</name> 
     <price>89.00</price> 
     <fat>3 grams</fat> 
     <sugar>5 grams</sugar> 
    </biscuit> 
</product> 

Prenez note:

  1. En utilisant la règle et en remplaçant l'identité est un modèle fondamental de conception XSLT et son utilisation facilite l'écriture de presque toute transformation.

  2. En utilisant <xsl:apply-templates/> au lieu de <xsl:apply-templates select="node()[1]"/> permet l'exécution parallèle, ce qui peut devenir de plus en plus important dans l'avenir le plus proche.

  3. clés pourrait être utilisé comme une optimisation, mais ce n'est pas nécessaire pour les petits documents XML comme celui fourni et l'utilisation des clés n'est pas liée à l'idée centrale dans la solution de ce problème.

1

Ce que vous pouvez faire est d'utiliser XPath pour la deuxième partie. Faites la boucle sur votre première liste, et dès que vous avez le nom, utilisez XPath pour interroger la chose exacte que vous voulez dans la deuxième liste.

+0

Cela nécessiterait-il la création d'une variable dans xslt qui représente la valeur sur laquelle la boucle est actuellement concentrée? ou puis-je l'utiliser directement dans la requête Xpath? – jdoig

+1

Je pense que les deux options peuvent être valides. Créer une variable vous donnerait des expressions plus courtes, mais je pense que vous pouvez directement utiliser votre valeur dans la requête xpath. –

+0

Très bien, merci. Je ne pense pas que vous ayez de bons liens montrant une telle technique, n'est-ce pas? Juste pour me sauver en train de lutter. À votre santé. – jdoig

1

Ce XSLT:

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
<xsl:output method="xml" indent="yes"/> 

<xsl:key name="bisquit-info" match="/root/biscuitInfo" use="@name"/> 

<xsl:template match="root"> 
    <xsl:copy> 
     <xsl:apply-templates select="biscuit/name"/> 
    </xsl:copy> 
</xsl:template> 

<xsl:template match="name"> 
    <bisquit> 
     <xsl:copy-of select="."/> 
     <xsl:copy-of select="following-sibling::price[1]"/> 
     <xsl:copy-of select="key('bisquit-info', .)/nutritionalValue/*"/> 
    </bisquit> 
</xsl:template> 
</xsl:stylesheet> 

Avec une telle entrée XML (juste ajouté nœud racine pour wellformedness)

<root> 
<biscuit> 
    <name>Hobnobs</name> 
    <price>1.49</price> 
    <name>Digestives</name> 
    <price>89.00</price> 
</biscuit> 

<biscuitInfo name="Hobnobs"> 
    <nutritionalValue> 
    <fat>6 grams</fat> 
    <sugar>lots</sugar> 
    </nutritionalValue>  
</biscuitInfo> 
<biscuitInfo name="Digestives"> 
    <nutritionalValue> 
    <fat>3 grams</fat> 
    <sugar>5 grams</sugar> 
    </nutritionalValue> 
</biscuitInfo> 
</root> 

Fournira ce résultat:

<root> 
<bisquit> 
    <name>Hobnobs</name> 
    <price>1.49</price> 
    <fat>6 grams</fat> 
    <sugar>lots</sugar> 
</bisquit> 
<bisquit> 
    <name>Digestives</name> 
    <price>89.00</price> 
    <fat>3 grams</fat> 
    <sugar>5 grams</sugar> 
</bisquit> 
</root> 

La conception n'est pas idéal , mais cela fonctionnerait. J'ai utilisé des clés, en supposant que l'arbre d'entrée peut être grand.

2

Deux exemples. Cette feuille de style en utilisant la récursivité clasic complète:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:key name="kNutChildByBisName" match="nutritionalValue/*" 
      use="../../@name"/> 
    <xsl:key name="kElemByPrecedingName" match="biscuit/*[not(self::name)]" 
      use="preceding-sibling::name[1]"/> 
    <xsl:template match="node()|@*" name="identity"> 
     <xsl:copy> 
      <xsl:apply-templates select="node()|@*"/> 
     </xsl:copy> 
    </xsl:template> 
    <xsl:template match="name" mode="group"> 
     <bisquit> 
      <xsl:apply-templates select=".|key('kNutChildByBisName',.)| 
             key('kElemByPrecedingName',.)"/> 
     </bisquit> 
    </xsl:template> 
    <xsl:template match="biscuit"> 
     <xsl:apply-templates mode="group"/> 
    </xsl:template> 
    <xsl:template match="biscuitInfo"/> 
    <xsl:template match="node()" mode="group"/> 
</xsl:stylesheet> 

Et cette feuille de style en utilisant traversal à grain fin:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:strip-space elements="*"/> 
    <xsl:key name="kNutByBisName" match="nutritionalValue" 
       use="../@name"/> 
    <xsl:template match="node()|@*" name="identity"> 
     <xsl:copy> 
      <xsl:apply-templates select="node()[1]|@*"/> 
     </xsl:copy> 
     <xsl:apply-templates select="following-sibling::node()[1]"/> 
    </xsl:template> 
    <xsl:template match="biscuitInfo"/> 
    <xsl:template match="biscuit"> 
     <xsl:apply-templates select="node()[1]|following-sibling::node()[1]"/> 
    </xsl:template> 
    <xsl:template match="name[1]" name="group"> 
     <bisquit> 
      <xsl:call-template name="identity"/> 
      <xsl:apply-templates select="key('kNutByBisName',.)/node()[1]"/> 
     </bisquit> 
     <xsl:apply-templates select="following-sibling::name[1]" mode="group"/> 
    </xsl:template> 
    <xsl:template match="name"/> 
    <xsl:template match="name" mode="group"> 
     <xsl:call-template name="group"/> 
    </xsl:template> 
</xsl:stylesheet> 

Avec cette entrée:

<root> 
    <biscuit> 
     <name>Hobnobs</name> 
     <price>1.49</price> 
     <name>Digestives</name> 
     <price>89.00</price> 
    </biscuit> 
    <biscuitInfo name="Hobnobs"> 
     <nutritionalValue> 
      <fat>6 grams</fat> 
      <sugar>lots</sugar> 
     </nutritionalValue> 
    </biscuitInfo> 
    <biscuitInfo name="Digestives"> 
     <nutritionalValue> 
      <fat>3 grams</fat> 
      <sugar>5 grams</sugar> 
     </nutritionalValue> 
    </biscuitInfo> 
</root> 

deux sorties:

<root> 
    <bisquit> 
     <name>Hobnobs</name> 
     <price>1.49</price> 
     <fat>6 grams</fat> 
     <sugar>lots</sugar> 
    </bisquit> 
    <bisquit> 
     <name>Digestives</name> 
     <price>89.00</price> 
     <fat>3 grams</fat> 
     <sugar>5 grams</sugar> 
    </bisquit> 
</root> 

Remarque: Vous effectuez deux tâches: le regroupement et le renvoi croisé.

Modifier: Meilleure traversée à grain fin dans le cas où il n'y a que name dans le groupe.

+0

Pas d'offense ici, mais je suis vraiment intéressé, pourquoi utiliser l'identité transorm dans les cas, où il fait plus de code et est, à mon humble avis, moins lisible? – Flack

+1

@Flack: Réutilisation et signification sémantique: le regroupement est juste un regroupement avec 'apply-templates' au lieu de' copy-of', alors vous pouvez aussi transformer ces éléments. De plus, les seules hypothèses sont: les enfants 'bisquit' sont groupés, le groupe commence par' name'. En plus de cela, le schéma pourrait grandir sans changer la feuille de style. –

+0

@Alejandro +1 Merci. J'ai ton point de vue. Peut-être que c'est juste ma mauvaise habitude due à l'utilisation de transformations xml-html 90% du temps. – Flack