2013-08-30 7 views
2

Étant donné un arbre XML profondément imbriqué, je veux trouver un certain élément. À ce stade, je veux envelopper X dans un nouvel élément qui est au même niveau qu'un élément supérieur. Je veux ensuite continuer avec le reste de l'arbre d'origine à partir du point après l'élément «certain».Comment diviser une structure imbriquée avec XSLT?

Par exemple, étant donné cette entrée:

<root> 
    <branch att="yo"> 
     <div stuff="no"> 
      <ul> 
       <li>Item 1</li> 
       <li>Item 2</li> 
       <li>Item 3</li> 
      </ul> 
     </div>   
    </branch> 
</root> 

Je veux trouver l'article 2 dans la < ul> (peasy facile). Je veux insérer un nouvel élément au niveau de la branche avant le point 2. Et puis je veux continuer avec l'élément 2 (en continuant ainsi les nœuds ancêtres). C'est, je veux que cette sortie:

<root> 
    <branch att="yo"> 
     <div stuff="no"> 
      <ul> 
       <li>Item 1</li> 
      </ul> 
     </div> 
    </branch> 
    <branch> 
     <div> 
      <p>New branch here</p> 
     </div> 
    </branch> 
    <branch att="yo"> 
     <div stuff="no"> 
      <ul> 
       <li>Item 2</li> 
       <li>Item 3</li> 
      </ul> 
     </div>   
    </branch> 
</root> 

J'ai un problème mal à démarrer avec cela dans le but de faire une solution généralisée . Je pense qu'il impliquera soit des modes multiples ou des clés avec le traitement des noeuds ancêtres pour trouver les noms et les attributs des noeuds. Toute aide est appréciée.

D'accord, c'est ce que j'ai jusqu'ici. Il fonctionne partiellement (par exemple, je copie des nœuds mais pas d'attributs, je copie aussi trop de nœuds mais c'est un début je pense). Ma pensée ici est que j'ai besoin d'une fonction récursive. Je commence par l'ancêtre le plus éloigné qui me concerne (branche) et pour chaque nœud enfant dont le descendant ultime est l'élément certain (li "item 2"), je copie chaque nœud et ses attributs, en l'ajoutant au nœud précédent (c'est qu'est-ce que newNewTree est pour). Et je recurai jusqu'à ce que j'atteigne l'élément certain (point 2 de li), auquel point je renvoie la variable NewNewTree, qui est mon intention pour que ce soit un arbre des éléments ancêtres droits (sans texte). Pour que cela fonctionne, je pense que ce que je dois faire est de traiter chaque nœud de la fonction à travers un remplacement de modèle d'identité qui copie le nœud et ses attributs. Mais trop fatigué pour tenter ça ce soir.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
       xmlns:xs="http://www.w3.org/2001/XMLSchema" 
       xmlns:my="http://www.example.com" 
       exclude-result-prefixes="xs my" 
       version="2.0"> 
    <xsl:template match="@*|node()"> 
    <xsl:copy> 
     <xsl:apply-templates select="@*|node()"/> 
    </xsl:copy> 
    </xsl:template> 
    <xsl:template match="/"> 
    <xsl:sequence select="$origNodesNoText" /> 
    </xsl:template> 
    <xsl:variable name="ancestorElemName" select="'div'" /> 
    <xsl:variable name="origNodesNoText"> 
    <xsl:apply-templates select="/" mode="origNodesNoText"/> 
    </xsl:variable> 
    <xsl:template match="text()" mode="origNodesNoText"/> 
    <xsl:template match="li[.='Item 2']" mode="origNodesNoText"> 
    <xsl:variable name="newTree"> 
     <xsl:element name="{local-name(ancestor::*[local-name()=$ancestorElemName][1])}"> 
     <xsl:for-each select="ancestor::*[local-name()=$ancestorElemName][1]/attribute::*"> 
      <xsl:attribute name="{local-name()}" select="."/> 
     </xsl:for-each> 
     </xsl:element> 
    </xsl:variable> 
    <xsl:sequence select="my:split(ancestor::*[local-name()=$ancestorElemName][1],.,$newTree)" /> 
    </xsl:template> 

    <xsl:function name="my:split"> 
    <xsl:param name="ancestorElemNode" /> 
    <xsl:param name="callingNode" /> 
    <xsl:param name="newTree" /> 
    <xsl:message>Calling my split</xsl:message> 
    <xsl:choose> 
     <xsl:when test="$ancestorElemNode ne $callingNode"> 
      <xsl:message>Found a node to copy its name and attributes</xsl:message> 
      <xsl:variable name="newNewTree"> 
       <xsl:for-each select="$newTree/node()"> 
        <xsl:copy /> <!-- doesn't copy attribute nodes so not what we want --> 
       </xsl:for-each> 
       <xsl:element name="{local-name($ancestorElemNode)}"> 
        <xsl:for-each select="$ancestorElemNode/attribute::*"> 
         <xsl:attribute name="{local-name()}" select="."/> 
        </xsl:for-each> 
       </xsl:element>  
      </xsl:variable> 
      <xsl:sequence select="my:split($ancestorElemNode/child::*[descendant::*[. eq $callingNode]][1],$callingNode,$newNewTree)" /> 
     </xsl:when> 
     <xsl:otherwise> 
      <xsl:message>Found the end point</xsl:message> 
      <xsl:sequence select="$newTree" /> 
     </xsl:otherwise> 
    </xsl:choose> 
    </xsl:function> 

</xsl:stylesheet> 

Répondre

0

Vous souhaitez traiter les nœuds de l'entrée de la manière suivante. Tout d'abord, définissons deux termes pour les nœuds auxquels nous devrons souvent faire référence:

  • Le deuxième élément de la liste est notre élément cible; nous l'appellerons le Bullseye.
  • L'élément de branchement entourant le Bullseye devra être séparé; nous l'appellerons banane.

Maintenant, nous pouvons définir plusieurs classes de nœuds dans l'arbre d'entrée, en termes de leurs correspondances avec l'arbre de sortie. Tout ce qui est à l'extérieur de la banane est copié comme dans une transformation d'identité; il correspond à un noeud du même type dans la sortie, avec le même nom, et à une exception près il a une séquence similaire d'attributs et d'enfants. L'exception est le parent du Banana: sa séquence d'enfants contient deux éléments correspondant au Banana, et un troisième élément (ici nommé branch) entre eux.

  • Chaque élément qui est un ancêtre du Bullseye et un descendant du Banana (ou le Banana lui-même) correspond à deux éléments dans l'arbre de sortie, avec des noms et des attributs similaires. Chaque descendant du Banana qui précède le Bullseye dans l'ordre du document et qui n'est pas un ancêtre du Bullseye et qui n'est pas un attribut d'un ancêtre du Bullseye est copié une fois dans la sortie comme dans une transformation d'identité, dans la première copie de la banane.Chaque descendant de la banane qui suit le Bullseye dans l'ordre des documents et qui n'est pas un ancêtre du Bullseye et qui n'est pas un attribut sur un ancêtre du Bullseye est copié une fois dans la sortie comme dans une transformation d'identité, dans la seconde copie de la banane. Ainsi est le Bullseye lui-même. Je ferais ceci avec une transformée d'identité proche. Nous aurons besoin d'un traitement spécial pour la banane et ses descendants. Certains d'entre eux devront être copiés deux fois, et d'autres seulement une fois, mais la chose la plus simple à faire sera de traiter le sous-arbre entier deux fois.

    La feuille de style dans son ensemble pourrait ressembler à ceci:

    <?xml version="1.0" encoding="UTF-8"?> 
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
        version="2.0"> 
    
        <xsl:output method="xml" indent="yes"/> 
    
        <xsl:variable name="Bullseye" select="//li[.='Item 2']"/> 
    
        <!--* Default identity transform *--> 
        <xsl:template match="comment()"> 
        <xsl:comment><xsl:value-of select="."/></xsl:comment> 
        </xsl:template> 
    
        <xsl:template match="processing-instruction()"> 
        <xsl:variable name="pitarget" select="name()"/> 
        <xsl:processing-instruction name="{$pitarget}"> 
         <xsl:value-of select="."/> 
        </xsl:processing-instruction> 
        </xsl:template> 
    
        <xsl:template match="@*|*"> 
        <xsl:copy> 
         <xsl:apply-templates select="@*|node()"/> 
        </xsl:copy> 
        </xsl:template> 
    
        <!--* Banana *--> 
        <xsl:template match="branch[descendant::* intersect $Bullseye]">  
        <xsl:copy> 
         <xsl:apply-templates select="@*|node()" mode="pre-bullseye"/> 
        </xsl:copy> 
        <xsl:call-template name="insertion"/> 
        <xsl:copy> 
         <xsl:apply-templates select="@*|node()" mode="post-bullseye"/> 
        </xsl:copy> 
        </xsl:template> 
    
        <!--* first pass over subtree rooted at Banana *--> 
        <xsl:template match="comment()" mode="pre-bullseye"> 
        <xsl:apply-templates select="."/> 
        </xsl:template> 
        <xsl:template match="processing-instruction()" mode="pre-bullseye"> 
        <xsl:apply-templates select="."/> 
        </xsl:template> 
        <xsl:template match="@*|*|text()" mode="pre-bullseye"> 
        <xsl:choose> 
         <xsl:when test="descendant::* intersect $Bullseye"> 
         <xsl:copy> 
          <xsl:apply-templates select="@*"/> 
          <xsl:apply-templates select="node()" mode="pre-bullseye"/> 
         </xsl:copy> 
         </xsl:when> 
         <xsl:when test=". &lt;&lt; $Bullseye"> 
         <xsl:copy> 
          <xsl:apply-templates select="@*|node()"/> 
         </xsl:copy> 
         </xsl:when> 
         <xsl:when test=". is $Bullseye"/>  
         <xsl:when test=". >> $Bullseye"/> 
         <xsl:otherwise> 
         <xsl:message terminate="yes" 
          >Unexpected case in mode pre-bullseye, dying now.</xsl:message> 
         </xsl:otherwise> 
        </xsl:choose> 
    
        </xsl:template> 
    
        <!--* Second pass over subtree rooted at Banana *--> 
        <xsl:template match="comment()" mode="post-bullseye"> 
        <xsl:apply-templates select="."/> 
        </xsl:template> 
        <xsl:template match="processing-instruction()" mode="post-bullseye"> 
        <xsl:apply-templates select="."/> 
        </xsl:template> 
        <xsl:template match="@*|*|text()" mode="post-bullseye"> 
        <xsl:choose> 
         <xsl:when test="descendant::* intersect $Bullseye"> 
         <xsl:copy> 
          <xsl:apply-templates select="@*"/> 
          <xsl:apply-templates select="node()" mode="post-bullseye"/> 
         </xsl:copy> 
         </xsl:when> 
         <xsl:when test=". &lt;&lt; $Bullseye"/> 
    
         <xsl:when test=". is $Bullseye"> 
         <xsl:copy-of select="."/> 
         </xsl:when> 
         <xsl:when test=". >> $Bullseye"> 
         <xsl:copy> 
          <xsl:apply-templates select="@*|node()"/> 
         </xsl:copy> 
         </xsl:when> 
         <xsl:otherwise> 
         <xsl:message terminate="yes" 
         >Unexpected case in post-bullseye, dying now.</xsl:message> 
         </xsl:otherwise> 
        </xsl:choose> 
    
        </xsl:template> 
    
        <xsl:template name="insertion"> 
        <branch> 
         <div> 
         <p>New branch here.</p> 
         </div> 
        </branch> 
        </xsl:template> 
    
    </xsl:stylesheet> 
    

    Lorsqu'il est exécuté sur l'entrée que vous fournissez, cette feuille de style produit la sortie suivante.

    <?xml version="1.0" encoding="UTF-8"?> 
    <root> 
        <branch att="yo"> 
         <div stuff="no"> 
          <ul> 
           <li>Item 1</li> 
           </ul> 
         </div> 
        </branch> 
        <branch> 
         <div> 
         <p>New branch here.</p> 
         </div> 
        </branch> 
        <branch> 
         <div stuff="no"> 
         <ul> 
          <li>Item 2</li> 
           <li>Item 3</li> 
          </ul> 
         </div>   
        </branch> 
    </root> 
    
  • +0

    W ow Je vous remercie. C'est beau. Un seul problème, le @att sur la branche n'est pas copié post-banane. J'ai fait un changement et ça a marché, je ne suis pas sûr des conséquences de ce changement. (Je ne suis pas familier avec la construction <<.) Voici ce que je devais faire: – user5923

    +0

    Dans ce modèle: dans le choix, je devais faire le vide quand dans ceci: user5923

    +0

    Je n'ai pas trouvé un effet secondaire négatif à la modification de l'élément xsl: when J'ai mentionné. Merci beaucoup monsieur. – user5923

    0

    Voici une autre option. Fondamentalement, vous:

    • Définissez une valeur pour l'élément cible (le paramètre target).
    • Identifiez l'élément cible (la clé branch) et appliquez les modèles pour cet élément. (S'il y a plus d'un élément avec cette valeur, il se branche sur la première occurrence.)
    • Appliquez des modèles pour les frères et sœurs précédents en commençant par le haut. Note: Le "top" est /*/*. Si l'élément racine a plus d'un enfant, cette feuille de style devra être peaufinée.
    • Appliquez les modèles à l'élément cible. Nous passons un param (branch) donc nous savons que c'est là que la nouvelle branche est insérée.
    • Appliquez à nouveau les modèles à l'élément cible (sans le paramètre branch) avec les frères et sœurs suivants.

    XML entrée

    <root> 
        <branch att="yo"> 
         <div stuff="no"> 
          <ul> 
           <li>Item 1</li> 
           <li>Item 2</li> 
           <li>Item 3</li> 
          </ul> 
         </div>   
        </branch> 
    </root> 
    

    XSLT 2,0

    <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
        <xsl:output indent="yes"/> 
        <xsl:strip-space elements="*"/> 
    
        <xsl:param name="target" select="'Item 2'"/> 
        <xsl:key name="branch" match="*[.=$target]" use="."/> 
    
        <xsl:template match="/*"> 
         <xsl:copy> 
          <xsl:apply-templates select="@*|(//*[key('branch',.)])[1]"/> 
         </xsl:copy> 
        </xsl:template> 
    
        <xsl:template match="*"> 
         <xsl:variable name="precID" select="for $id in preceding-sibling::* return generate-id($id)"/> 
         <xsl:variable name="follID" select="for $id in (self::*,following-sibling::*) return generate-id($id)"/> 
         <xsl:apply-templates select="/*/*" mode="ident"> 
          <xsl:with-param name="restricted2IDs" select="$precID" tunnel="yes"/>   
         </xsl:apply-templates> 
         <xsl:apply-templates select="/*/*" mode="ident"> 
          <xsl:with-param name="restricted2IDs" select="generate-id(current())" tunnel="yes"/> 
          <xsl:with-param name="branch" select="';-)'" tunnel="yes"/> 
         </xsl:apply-templates> 
         <xsl:apply-templates select="/*/*" mode="ident"> 
          <xsl:with-param name="restricted2IDs" select="$follID" tunnel="yes"/>   
         </xsl:apply-templates>  
        </xsl:template> 
    
        <xsl:template match="@*|node()" mode="ident"> 
         <xsl:param name="restricted2IDs" tunnel="yes"/> 
         <xsl:param name="branch" tunnel="yes"/> 
          <xsl:choose> 
           <xsl:when test="*[generate-id()=$restricted2IDs]"> 
            <xsl:choose> 
             <xsl:when test="$branch"> 
              <p>New branch here</p> 
             </xsl:when> 
             <xsl:otherwise> 
              <xsl:copy> 
               <xsl:apply-templates select="@*|node()[generate-id()=$restricted2IDs]" mode="ident"/>              
              </xsl:copy>       
             </xsl:otherwise> 
            </xsl:choose> 
           </xsl:when> 
           <xsl:when test="descendant::*[generate-id()=$restricted2IDs]"> 
            <xsl:copy> 
             <!--If you want the attributes in the new branch, remove the "if" and just use "@*|node".--> 
             <xsl:apply-templates select="if ($branch) then node() else @*|node()" mode="ident"/>       
            </xsl:copy> 
           </xsl:when> 
           <xsl:otherwise> 
            <xsl:copy> 
             <xsl:apply-templates select="@*|node()" mode="ident"/> 
            </xsl:copy> 
           </xsl:otherwise> 
          </xsl:choose> 
        </xsl:template> 
    
    </xsl:stylesheet> 
    

    XML de sortie

    <root> 
        <branch att="yo"> 
         <div stuff="no"> 
         <ul> 
          <li>Item 1</li> 
         </ul> 
         </div> 
        </branch> 
        <branch> 
         <div> 
         <p>New branch here</p> 
         </div> 
        </branch> 
        <branch att="yo"> 
         <div stuff="no"> 
         <ul> 
          <li>Item 2</li> 
          <li>Item 3</li> 
         </ul> 
         </div> 
        </branch> 
    </root> 
    
    +0

    Merci, Daniel. Votre réponse est informative. – user5923

    Questions connexes