Löschen Sie einen XML-Knoten, der ein bestimmtes Element enthält

Löschen Sie einen XML-Knoten, der ein bestimmtes Element enthält

Ich möchte alle Ortsmarken aus einer KML-Datei entfernen, die das Element enthalten <tessellate>. Der folgende Block sollteganzENTFERNT:

<Placemark>
    <styleUrl>#m_ylw-pushpin330</styleUrl>
    <LineString>
        <tessellate>1</tessellate>
        <coordinates>
            0.0000000000000,0.0000000000000,0 0.0000000000000,0.0000000000000,0
        </coordinates>
    </LineString>
</Placemark>

Ich habe einige nicht gierige Perl-Regex-Ausdrücke ohne Erfolg ausprobiert (eine Menge Zeug wird zusammen mit dem ersten entfernt <Placemark>):

sed -r ':a; N; $!ba; s/\n\t*//g' myplaces.kml |
perl -pe 's|<Placemark>.*?<tessellate>.*?</Placemark>||g'

Ich glaube, ein XML-Parser ist die Lösung, aber ich habe die Dokumentation für xmlstarlet gelesen und bin nicht weitergekommen. Daher sind auch alle Lösungen in xmlstarlet, Python usw. willkommen!

Antwort1

Mit xmlstarlet:

xmlstarlet ed -d '//Placemark[.//tessellate]' < myplaces.kml

Und da kmlNamespaces verwendet werden, müssen Sie diese zuerst definieren (siehe die XMLStarlet-Dokumentation)

xmlstarlet ed -N 'ns=http://www.opengis.net/kml/2.2' -d '//ns:Placemark[.//ns:tessellate]'

Bei perlmüssten Sie die Datei als Ganzes verarbeiten (nicht Zeile für Zeile) und das sFlag zu hinzufügen s///. Und selbst dann, selbst bei nicht gieriger Übereinstimmung, würde es immer noch vom ersten <Placemark>bis zum nächsten übereinstimmen </Placemark>, der nach dem nächsten auftritt <tessellate>. Sie müssten es also etwa so schreiben:

perl -0777 -pe 's|(<Placemark>.*?</Placemark>)|
   $1 =~ /<tessellate>/?"":$1|gse'

Antwort2

Angesichts dieser Testdatei:

start
<Placemark>
        <tessellate>1</tessellate>
</Placemark>
middle1
<Placemark>
</Placemark>
middle2
<Placemark>
        <tessellate>1</tessellate>
</Placemark>
end

Wenn Sie es perl -0 -pe 's|<Placemark>.*?<tessellate>.*?</Placemark>||gs'wie vorgeschlagen machen, wird zu viel entfernt:

start

middle1

end

Das liegt daran, dass der reguläre Ausdruck nur nach vorne schaut. Er findet ein Start-Tag, nimmt alles bis zum ersten Tessellate-Tag und bis zum nächsten End-Tag. Leider ist es ihm egal, ob er dabei weitere Start-Tags verbraucht...

Wenn Sie dies mit regulären Ausdrücken tun möchten, müssen Sie jeden Block einzeln verarbeiten: perl -0 -pe 's|<Placemark>.*?</Placemark>|$&=~/<tessellate>/?"":$&|gse'

Dies sollte das gewünschte Ergebnis liefern.

Antwort3

Verwenden von Python (2.7) mit Standardmodulen:

Datei test.xml:

<Container>
<Placemark>
  <KeepMe/>
</Placemark>
<Placemark>
    <styleUrl>#m_ylw-pushpin330</styleUrl>
    <LineString>
        <tessellate>1</tessellate>
        <coordinates>
            0.0000000000000,0.0000000000000,0 0.0000000000000,0.0000000000000,0
        </coordinates>
    </LineString>
</Placemark>
</Container>

Und das Programm:

#! /usr/bin/env python

from __future__ import print_function # works on 2.x and 3.x
from lxml import etree

file_name = 'test.xml'
root = etree.parse(file_name)
for element in root.iterfind('.//Placemark'):
    if(element.find('.//tessellate')) is not None:
        element.getparent().remove(element)

print(etree.tostring(root))

gibt als Ausgabe:

<Container>
<Placemark>
  <KeepMe/>
</Placemark>
</Container>

verwandte Informationen