Eliminar el nodo XML que contiene cierto elemento

Eliminar el nodo XML que contiene cierto elemento

Quiero eliminar todas las marcas de posición de un archivo KML que contenga el elemento <tessellate>. El siguiente bloque debe sertotalmenteremoto:

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

Probé algunas expresiones regulares de Perl no codiciosas sin suerte (se eliminan muchas cosas junto con la primera <Placemark>):

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

Creo que un analizador XML es el camino a seguir, pero leí la documentación de xmlstarlet y no llegué a ninguna parte. Por lo tanto, cualquier solución en xmlstarlet, python, etc. también es bienvenida.

Respuesta1

Con xmlstarlet:

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

Y como kmlutiliza espacios de nombres, primero debe definirlos (consulte la documentación xmlstarlet)

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

Con perl, necesitarías procesar el archivo como un todo (no línea por línea) y agregar la sbandera a s///. E incluso entonces, incluso con una coincidencia no codiciosa, aún coincidiría desde el primero <Placemark>hasta el siguiente </Placemark>que ocurre después del siguiente <tessellate>. Entonces necesitarías escribirlo algo como:

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

Respuesta2

Dado este archivo de prueba:

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

Si hace perl -0 -pe 's|<Placemark>.*?<tessellate>.*?</Placemark>||gs'lo que sugirió, eliminará demasiado:

start

middle1

end

Esto se debe a que la expresión regular solo mira hacia adelante. Encuentra una etiqueta inicial, toma todo hasta la primera etiqueta teselada y hasta la siguiente etiqueta final. Desafortunadamente, no le importa si consume más etiquetas de inicio en el camino...

Si quieres hacerlo con expresiones regulares, debes procesar cada bloque por sí solo: perl -0 -pe 's|<Placemark>.*?</Placemark>|$&=~/<tessellate>/?"":$&|gse'

Esto debería dar el resultado deseado.

Respuesta3

Usando Python (2.7) con módulos estándar:

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

Y el programa:

#! /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))

da como salida:

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

información relacionada