2009-10-10 10 views
1

Comme similaire à cette question (il y a des entrées plus liées, mais en tant que nouvel utilisateur, je ne peux poster une URL): Xpath Get elements that are between 2 elementsXPath dans XSLT: sélectionner des éléments qui sont entre 2 autres éléments, partie II

I avoir une question concernant la sélection de l'ensemble des éléments qui se produisent entre les éléments «autres/délimiteurs». Cette situation se produit lorsque vous tentez de transformer une table HTML plate en une structure XML hiérarchique à l'aide de XSLT. J'ai essayé d'utiliser la récursivité dans les gabarits, mais saxon a refusé d'accepter cela car il en résultait un blocage, probablement de ma faute, mais commençons par le début.

D'abord les données source est la table HTML:

<table > 
    <thead> 
     <tr> 
      <th>Column 1</th> 
      <th>Column 2</th> 
      <th>Column 3</th> 
     </tr> 
    </thead> 
    <tbody> 
     <tr> 
      <th colspan="3" >Group 1</th> 
     </tr> 
     <tr> 
      <td>attribute 1.1.1</td> 
      <td>attribute 1.1.3</td> 
      <td>attribute 1.1.2</td> 
     </tr> 
     <tr> 
      <td>attribute 1.2.1</td> 
      <td>attribute 1.2.2</td> 
      <td>attribute 1.2.3</td> 
     </tr> 
     <tr> 
      <td>attribute 1.3.1</td> 
      <td>attribute 1.3.2</td> 
      <td>attribute 1.3.3</td> 
     </tr> 
     <tr> 
      <th colspan="3" >Group 2</th> 
     </tr> 
     <tr> 
      <td>attribute 2.1.1</td> 
      <td>attribute 2.1.3</td> 
      <td>attribute 2.1.2</td> 
     </tr> 
     <tr> 
      <td>attribute 2.2.1</td> 
      <td>attribute 2.2.2</td> 
      <td>attribute 2.2.3</td> 
     </tr> 
     <tr> 
      <td>attribute 2.3.1</td> 
      <td>attribute 2.3.2</td> 
      <td>attribute 2.3.3</td> 
     </tr> 
    </tbody> 
</table> 

La sortie ciblée en XML serait:

<groups> 
    <group name="Group 1"> 
     <item attribute1="attribute 1.1.1" attribute2="attribute 1.1.3" attribute3="attribute 1.1.2"/> 
     <item attribute1="attribute 1.2.1" attribute2="attribute 1.2.2" attribute3="attribute 1.2.3"/> 
     <item attribute1="attribute 1.3.1" attribute2="attribute 1.3.2" attribute3="attribute 1.3.3"/> 
    </group> 
    <group name="Group 2"> 
     <item attribute1="attribute 2.1.1" attribute2="attribute 2.1.3" attribute3="attribute 2.1.2"/> 
     <item attribute1="attribute 2.2.1" attribute2="attribute 2.2.2" attribute3="attribute 2.2.3"/> 
     <item attribute1="attribute 2.3.1" attribute2="attribute 2.3.2" attribute3="attribute 2.3.3"/> 
    </group> 
</groups> 

Je veux avoir toutes les entrées de poste, (éléments TR) et ajoutez-les à un groupe. Cela revient essentiellement à sélectionner tous les éléments TR suivants-frères jusqu'à ce que nous en rencontrions un qui a un élément TH en tant qu'enfant. Si je ne pouvais déterminer la position de ce premier TR qui a un enfant TH, ce qui indique une nouvelle rubrique pour un groupe, cela pourrait se faire avec:

<xsl:for-each select="tbody/tr"> 
    <xsl:if test="th"> 
     <xsl:element name="group"> 
      <xsl:attribute name="name"><xsl:value-of select="th"/></xsl:attribute> 
      <xsl:for-each select="following-sibling::tr[position() < $positionOfNextThElement]">    
       <xsl:call-template name="item"/> 
      </xsl:for-each> 
     </xsl:element> 
    </xsl:if> 
</xsl:for-each> 

Cependant, je ne suis pas en mesure de déterminer la position du première fois rencontré tag TR/TH. Comme je l'ai dit, j'ai essayé de travailler avec la récursivité dans les templates: appelez toujours le template "item" et dans ce template, déterminez si nous voulons l'invoquer sur le prochain item. Je pense que le problème est dans l'invocation du modèle à partir du modèle. L'élément en contexte n'augmente pas? Dois-je remettre un paramètre pour déterminer sur quel article nous travaillons?

Quoi qu'il en soit, ce fut ce que je suis venu avec:

<xsl:for-each select="tbody/tr"> 
    <xsl:if test="th"> 
     <xsl:element name="group"> 
      <xsl:attribute name="name"><xsl:value-of select="th"/></xsl:attribute> 
      <xsl:call-template name="item"/> 
     </xsl:element> 
    </xsl:if> 
</xsl:for-each> 

<xsl:template name="item"> 
    <xsl:element name="item"> 
     <xsl:attribute name="attribute1"><xsl:value-of select="following-sibling::tr[1]/td[1]"/></xsl:attribute> 
     <xsl:attribute name="attribute2"><xsl:value-of select="following-sibling::tr[1]/td[2]"/></xsl:attribute> 
     <xsl:attribute name="attribute2"><xsl:value-of select="following-sibling::tr[1]/td[3]"/></xsl:attribute> 
    </xsl:element> 
    <!-- When the next element has not got a TH tag, continue with invoking this template --> 
    <xsl:if test="count(following-sibling::tr[1]/th) != 1"> 
     <xsl:call-template name="item"/> 
    </xsl:if> 
</xsl:template> 

Toutes les suggestions sur la façon de réaliser ce sont les bienvenus!

Répondre

0

Une solution alternative, adaptée pour les comptes d'attributs variables sans récursion.

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

    <xsl:template match="table"> 
    <groups> 
     <xsl:apply-templates select="tbody/tr[th]"/> 
    </groups> 
    </xsl:template> 

    <xsl:template match="tr[th]"> 
    <group name="{th}"> 
     <xsl:apply-templates select=" 
     following-sibling::tr[not(th)][ 
      generate-id(preceding-sibling::tr[th][1]) = generate-id(current()) 
     ] 
     "/> 
    </group> 
    </xsl:template> 

    <xsl:template match="tr"> 
    <item> 
    <xsl:apply-templates select="td" /> 
    </item> 
    </xsl:template> 

    <xsl:template match="td"> 
    <xsl:attribute name="attribute{position()}"> 
     <xsl:value-of select="." /> 
    </xsl:attribute> 
    </xsl:template> 

</xsl:stylesheet> 
1

La raison pour laquelle le contexte n'augmente pas lorsque vous appelez récursivement le modèle "item" est que xs: call-template passe toujours l'élément de contexte courant en tant que contexte. Donc, comme vous l'avez probablement vu, la transformation entre simplement dans la récurrence infinie.

En supposant que vous ayez toujours besoin de produire exactement trois attributs, vous n'avez même pas besoin de récursivité.

Essayez ceci:

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

    <xsl:template match="table"> 
     <groups> 
      <xsl:apply-templates select="tbody/tr[th]"/> 
     </groups> 
    </xsl:template> 

    <xsl:template match="tr[th]"> 
     <xsl:variable name="id" select="generate-id(.)"/> 
     <group name="{string(th)}"> 
      <xsl:apply-templates 
       select="following-sibling::tr[not(th)][generate-id(preceding-sibling::tr[th][1]) = $id]"/> 
     </group> 
    </xsl:template> 

    <xsl:template match="tr"> 
     <item attribute1="{td[1]}" attribute2="{td[2]}" attribute3="{td[3]}" />      
    </xsl:template> 

</xsl:stylesheet> 

Cela fonctionne en appliquant des modèles à chaque ligne d'en-tête. Chacun de ces modèles utilise xpath compliqué pour appeler "ses" lignes suivantes, qui sont toutes les lignes de même rang suivantes qui ont cette ligne spécifique comme première ligne précédant avec un en-tête.

Bien sûr, si le nombre d'attributs varie, alors vous devrez vous recurder là et augmenter passer un paramètre indiquant la position.

Il existe deux méthodes établies pour le groupement XSLT, dont l'une est récursive, comme vous le faisiez. Une autre méthode s'appelle le groupement Muenchian. Une bonne description est here.

+0

Pouces pour votre réponse! Cela a fait l'affaire, heureusement le nombre d'attributs est statique. Je vais vérifier la documentation référencée pour les futurs problèmes de regroupement. – Holtkamp