2009-05-14 11 views
9

Ich habe ein vorhandenes XML-Dokument mit einigen optionalen Knoten, und ich möchte einen neuen Knoten einfügen, aber an einer bestimmten Position.XML-Knoten an einer bestimmten Position eines vorhandenen Dokuments einfügen

Das Dokument sieht in etwa wie folgt aus:

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

Der neue Knoten (<s>...</s>) sollte <r> und <t> zwischen Knoten eingefügt werden, was zu:

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

Das Problem ist, dass die bestehende Knoten sind optional. Daher kann ich mit XPath den Knoten <r> nicht finden und den neuen Knoten danach einfügen.

Ich möchte die "Brute-Force-Methode" vermeiden: Suche von <r> bis zu <a>, um einen Knoten zu finden, der existiert.

Ich möchte auch die Reihenfolge beibehalten, da das XML-Dokument einem XML-Schema entsprechen muss.

Sowohl XSLT als auch normale XML-Bibliotheken können verwendet werden, aber da ich nur Saxon-B verwende, ist die schemafähige XSLT-Verarbeitung keine Option.

Hat jemand eine Idee, wie man einen solchen Knoten einfügt?

thx, MyKey_

Antwort

18

[Meine letzte Antwort wurde ersetzt. Jetzt verstehe ich besser, was Sie brauchen]

Hier ist ein XSLT-2.0-Lösung.

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

Sie müssen explizit entweder die Elemente aufzulisten, die nach oder die Elemente kommen, die vor kommen. (Sie müssen nicht beide auflisten.) Ich würde eher die kürzere der beiden Listen wählen (daher "t" - "z" im obigen Beispiel anstelle von "a" - "r").

optionale Erweiterung:

Dies wird die Arbeit getan, aber jetzt müssen Sie die Liste der Elementnamen in zwei verschiedenen Orten (im XSLT und im Schema) zu halten. Wenn es sich stark ändert, können sie nicht mehr synchron sein. Wenn Sie dem Schema ein neues Element hinzufügen, aber vergessen, es dem XSLT hinzuzufügen, wird es nicht kopiert. Wenn Sie sich darüber Gedanken machen, können Sie Ihre eigene Art von Schema-Awareness implementieren.Angenommen, Ihr Schema wie folgt aussieht:

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

Alles was Sie jetzt tun müssen, ist Ihre Definition der $ ändern Elemente-nach 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> 

Das ist natürlich komplizierter, aber jetzt Sie don Sie müssen keine Elemente (außer "s") in Ihrem Code aufführen. Das Verhalten des Skripts wird automatisch aktualisiert, wenn Sie das Schema ändern (insbesondere wenn Sie neue Elemente hinzufügen). Ob das Overkill ist oder nicht, hängt von Ihrem Projekt ab. Ich biete es einfach als optionales Add-On an. :-)

+0

Dies funktioniert nicht, wenn kein 'r'-Knoten vorhanden ist (wie bei der ursprünglichen Frage: Alle Knoten sind optional). Wie würde die Vorlage aussehen, wenn Sie sich nicht darauf verlassen können, dass ein Knoten existiert? –

+0

Hoppla, du hast Recht. Ich hatte den ursprünglichen Beitrag falsch gelesen. Jetzt habe ich die Antwort komplett ersetzt. Vielen Dank. –

+0

Das ist wirklich cool. Geringfügige Verfeinerung: Wenn Sie $ elements-after ableiten, verwenden Sie eine Variable anstelle von 's', sodass Sie das Einfügen nach jedem untergeordneten Element automatisch durchführen können. – 13ren

0

Sie müssen eine Brute-Force-Methode verwenden, da Sie keine statischen Pfad haben die Insert-Position zu finden. Mein Ansatz wäre, einen SAX-Parser zu verwenden und das Dokument zu lesen. Alle Knoten werden unmodifiziert in die Ausgabe kopiert.

Sie benötigen eine Flagge sWasWritten weshalb Sie kein normales XSLT-Tool verwenden können; Sie brauchen eine, wo Sie Variablen ändern können.

Sobald ich einen Knoten sehen>r (t, u, ..., z) oder das End-Tag des Root-Knotens, würde ich den s Knoten schreiben, es sei denn sWasWrittentrue und das Flag gesetzt war sWasWritten .

+0

Die SAX-Verarbeitung funktioniert wie von Ihnen vorgeschlagen. Aber XSLT ist auch für die Aufgabe sehr geeignet (siehe meine Antwort). –

0

Ein XPath-Lösung:

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

Sie müssen alle Knoten explizit enthalten, um einen bis Sie wollen, so dass Sie für jeden Knoten einen anderen XPath-Ausdruck benötigen Sie einfügen möchten nach . Zum Beispiel kann es unmittelbar nach <t> (falls vorhanden):

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

Hinweis des Sonderfall, wenn keine der vorangehenden Knoten vorhanden ist: es gibt <root> (die „“). Sie müssen dies überprüfen und den neuen Knoten als erstes Kind von root einfügen, anstatt danach (der übliche Fall). Das ist nicht so schlimm: Sie müssten sowieso mit diesem Sonderfall irgendwie fertig werden. Eine andere Möglichkeit, diesen speziellen Fall zu behandeln, ist der folgende, der 0 Knoten zurückgibt, wenn keine vorhergehenden Knoten vorhanden sind.

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

Herausforderung: Können Sie einen besseren Weg finden, diesen speziellen Fall zu behandeln?

Verwandte Themen