2009-05-14 13 views
9

Mam istniejący dokument XML z opcjonalnymi węzłami i chcę wstawić nowy węzeł, ale w określonej pozycji.Wstaw węzeł XML w określonej pozycji istniejącego dokumentu

Dokument wygląda mniej więcej tak:

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

nowego węzła (<s>...</s>) powinien być wstawiony pomiędzy węzłem <r> i <t>, powodując:

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

Problem polega na tym, że istniejące węzły są opcjonalne. Dlatego nie mogę użyć XPath do znalezienia węzła <r> i wstawić po nim nowego węzła.

Chciałbym uniknąć "metody brutalnej siły": Wyszukaj od <r> do <a>, aby znaleźć istniejący węzeł.

Chcę również zachować kolejność, ponieważ dokument XML musi być zgodny ze schematem XML.

Można używać XSLT, jak również zwykłych bibliotek XML, ale ponieważ używam tylko Saxon-B, przetwarzanie XSLT z uwzględnieniem schematu nie jest opcją.

Czy ktoś ma pomysł, jak wstawić taki węzeł?

thx, MyKey_

Odpowiedz

18

[Zastąpiono moją ostatnią odpowiedź. Teraz lepiej rozumiem, co trzeba]

Oto rozwiązanie 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> 

Trzeba wyraźnie wymienić zarówno elementy, które przychodzą po lub elementy, które przychodzą wcześniej. (Nie musisz wymieniać obu). Miałem tendencję do wybierania krótszej z dwóch list (stąd "t" - "z" w powyższym przykładzie, zamiast "a" - "r").

OPCJA ENHANCEMENT:

To załatwia sprawę, ale teraz trzeba utrzymać listę nazw elementów w dwóch różnych miejscach (w XSLT i schematu). Jeśli zmieni się bardzo, mogą się zsynchronizować. Jeśli dodasz nowy element do schematu, ale zapomnisz dodać go do XSLT, to nie zostanie on skopiowany. Jeśli się o to martwisz, możesz wdrożyć własną świadomość schematu.Załóżmy, że schemat wygląda tak:

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

Teraz wszystko co musisz zrobić, to zmienić definicję $ elementach-po zmiennej:

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

To jest oczywiście bardziej skomplikowana, ale teraz don W tekście musi znajdować się lista elementów (innych niż "s"). Zachowanie skryptu zostanie automatycznie zaktualizowane po każdej zmianie schematu (w szczególności w przypadku dodawania nowych elementów). To, czy jest to przesada, czy nie, zależy od projektu. Oferuję go po prostu jako opcjonalny dodatek. :-) rozwiązanie

+0

To nie działa, gdy nie ma węzła "r" (jak na oryginalne pytanie: Wszystkie węzły są opcjonalne). Jak wyglądałby szablon, gdy nie można polegać na żadnym węźle? –

+0

Ups, masz rację. Nie odczytałem oryginalnego wpisu. Teraz całkowicie zastąpiłem odpowiedź. Dzięki. –

+0

To naprawdę fajne. Niewielkie wyrafinowanie: w wyprowadzaniu $ elment-after, użyj zmiennej zamiast "s", dzięki czemu możesz automatycznie obsługiwać wstawianie po dowolnym dziecku o wartości . – 13ren

0

Musisz użyć brute force wyszukiwania ponieważ nie masz stałą ścieżkę, aby znaleźć miejsce wstawiania. Moje podejście polegałoby na użyciu parsera SAX i przeczytaniu dokumentu. Wszystkie węzły są kopiowane na wyjście niezmodyfikowane.

Będziesz potrzebować flagi sWasWritten, dlatego nie możesz używać zwykłego narzędzia XSLT; potrzebujesz takiej, w której możesz modyfikować zmienne.

Jak tylko widzę węzeł>r (t, u ..., z) lub końcowy tag od węzła głównego, chcę napisać węzeł s chyba sWasWritten był true i ustawić flagę sWasWritten .

+0

Przetwarzanie SAX będzie działać zgodnie z Twoimi sugestiami. Ale XSLT jest całkiem zdolny do tego zadania (zobacz moją odpowiedź). –

0

XPath:

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

Musisz wyraźnie obejmują wszystkie węzły do ​​tej, którą chcesz, tak że trzeba innego wyrażenia XPath dla każdego węzła chcesz wstawić po . Na przykład, aby go umieścić bezpośrednio po <t> (jeśli istnieje):

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

Uwaga szczególnym przypadku, gdy żaden z poprzednich węzłów występują: zwraca <root> (dalej „”). Musisz to sprawdzić i wstawić nowy węzeł jako pierwsze dziecko root zamiast po nim (zwykły przypadek). To nie jest takie złe: musiałbyś w jakiś sposób poradzić sobie z tym specjalnym przypadkiem. Inny sposób obsługi tego specjalnego przypadku jest następujący, który zwraca 0 węzłów, jeśli nie ma poprzedzających węzłów.

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

Wyzwanie: czy możesz znaleźć lepszy sposób na obsługę tego specjalnego przypadku?

Powiązane problemy