2009-05-14 10 views
9

J'ai un document XML existant avec des nœuds optionnels et je veux insérer un nouveau nœud, mais à un certain endroit.Insérer un nœud XML à une position spécifique d'un document existant

Le document ressemble à ceci:

<root> 
    <a>...</a> 
    ... 
    <r>...</r> 
    <t>...</t> 
    ... 
    <z>...</z> 
</root> 

Le nouveau nœud (<s>...</s>) doit être inséré entre le noeud <r> et <t>, entraînant:

<root> 
    <a>...</a> 
    ... 
    <r>...</r> 
    <s>new node</s> 
    <t>...</t> 
    ... 
    <z>...</z> 
</root> 

Le problème est que l'actuel les noeuds sont optionnels. Par conséquent, je ne peux pas utiliser XPath pour trouver le nœud <r> et insérer le nouveau nœud après celui-ci.

Je voudrais éviter la "méthode de la force brute": Recherche de <r> jusqu'à <a> pour trouver un nœud qui existe. Je souhaite également préserver l'ordre, car le document XML doit être conforme à un schéma XML.

XSLT ainsi que les bibliothèques XML normales peuvent être utilisées, mais comme j'utilise uniquement Saxon-B, le traitement XSLT basé sur le schéma n'est pas une option.

Est-ce que quelqu'un a une idée sur la façon d'insérer un tel noeud?

thx, MyKey_

Répondre

18

[a remplacé ma dernière réponse. Maintenant, je comprends mieux ce que vous avez besoin]

est ici une solution XSLT 2.0.

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

    <xsl:template match="/root"> 
    <xsl:variable name="elements-after" select="t|u|v|w|x|y|z"/> 
    <xsl:copy> 
     <xsl:copy-of select="* except $elements-after"/> 
     <s>new node</s> 
     <xsl:copy-of select="$elements-after"/> 
    </xsl:copy> 
    </xsl:template> 

</xsl:stylesheet> 

Vous devez lister explicitement soit les éléments qui viennent après ou les éléments qui viennent avant. (Vous n'avez pas à lister les deux.) J'aurais tendance à choisir la plus courte des deux listes (d'où "t" - "z" dans l'exemple ci-dessus, au lieu de "a" - "r").

MISE EN VALEUR EN OPTION:

Cela fait le travail, mais maintenant vous avez besoin de maintenir la liste des noms d'éléments en deux endroits différents (dans le XSLT et dans le schéma). Si cela change beaucoup, ils pourraient se désynchroniser. Si vous ajoutez un nouvel élément au schéma mais oubliez de l'ajouter au fichier XSLT, il ne sera pas copié. Si cela vous inquiète, vous pouvez implémenter votre propre type de connaissance du schéma.Disons que votre schéma ressemble à ceci:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> 

    <xs:element name="root"> 
    <xs:complexType> 
     <xs:sequence> 
     <xs:element name="a" type="xs:string"/> 
     <xs:element name="r" type="xs:string"/> 
     <xs:element name="s" type="xs:string"/> 
     <xs:element name="t" type="xs:string"/> 
     <xs:element name="z" type="xs:string"/> 
     </xs:sequence> 
    </xs:complexType> 
    </xs:element> 

</xs:schema> 

Maintenant, tout ce que vous devez faire est de changer votre définition des éléments-après variable $:

<xsl:variable name="elements-after" as="element()*"> 
    <xsl:variable name="root-decl" select="document('root.xsd')/*/xs:element[@name eq 'root']"/> 
    <xsl:variable name="child-decls" select="$root-decl/xs:complexType/xs:sequence/xs:element"/> 
    <xsl:variable name="decls-after" select="$child-decls[preceding-sibling::xs:element[@name eq 's']]"/> 
    <xsl:sequence select="*[local-name() = $decls-after/@name]"/> 
    </xsl:variable> 

Ceci est évidemment plus compliqué, mais maintenant vous n Ne pas avoir à lister les éléments (autres que "s") dans votre code. Le comportement du script sera automatiquement mis à jour chaque fois que vous changerez le schéma (en particulier, si vous deviez ajouter de nouveaux éléments). Que ce soit exagéré ou non dépend de votre projet. Je l'offre simplement comme un ajout optionnel. :-)

+0

Cela ne fonctionne pas quand il n'y a pas de nœud 'r' (selon la question initiale: tous les nœuds sont facultatifs). À quoi ressemblerait le modèle lorsque vous ne pouvez pas compter sur un noeud pour exister? –

+0

Oops, vous avez raison. J'avais mal lu le message original. Maintenant, j'ai complètement remplacé la réponse. Merci. –

+0

C'est vraiment cool. Léger raffinement: en dérivant $ elments-after, utilisez une variable au lieu de 's', donc vous pouvez gérer automatiquement l'insertion après un enfant de . – 13ren

0

Vous devez utiliser une recherche de force brute puisque vous avez pas de chemin statique pour trouver l'emplacement d'insertion. Mon approche serait d'utiliser un analyseur SAX et de lire le document. Tous les nœuds sont copiés dans la sortie non modifiée.

Vous aurez besoin d'un indicateur sWasWritten, c'est pourquoi vous ne pouvez pas utiliser un outil XSLT normal; vous en avez besoin d'un où vous pouvez modifier les variables.

Dès que je vois un nœud>r (t, u, ..., z) ou la balise de fin du noeud racine, j'écrire le nœud s à moins sWasWritten était true et mis le drapeau sWasWritten .

+0

Le traitement SAX fonctionnera comme vous le suggérez. Mais XSLT est tout à fait capable de la tâche (voir ma réponse). –

0

Une solution XPath:

/root/(.|a|r)[position()=last()] 

Vous devez inclure explicitement tous les noeuds jusqu'à celui que vous voulez, de sorte que vous aurez besoin d'une autre expression XPath pour chaque nœud que vous souhaitez insérer après . Par exemple, pour le placer immédiatement après <t> (si elle existe):

/root/(.|a|r|t)[position()=last()] 

Notez le cas particulier du moment où aucun des nœuds précédents sont présents: il retourne <root> (le « »). Vous devrez vérifier cela, et insérez le nouveau noeud en tant que premier enfant de la racine, au lieu de l'après (le cas habituel). Ce n'est pas si grave: de toute façon, il faudrait gérer ce cas particulier. Une autre façon de gérer ce cas particulier est la suivante, qui retourne 0 nœuds s'il n'y a pas de nœuds précédents. Défi: pouvez-vous trouver une meilleure façon de gérer ce cas particulier?