Excluir nó XML contendo determinado elemento

Excluir nó XML contendo determinado elemento

Quero remover todos os marcadores de um arquivo KML que contém o elemento <tessellate>. O seguinte bloco deve sercompletamenteremovido:

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

Eu tentei alguns regex perl não gananciosos sem sorte (muitas coisas são removidas junto com o primeiro <Placemark>):

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

Acredito que um analisador XML é o caminho a seguir, mas li a documentação do xmlstarlet e não cheguei a lugar nenhum. Portanto, quaisquer soluções em xmlstarlet, python, etc. também são bem-vindas!

Responder1

Com xmlstarlet:

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

E como kmlusa namespaces, você deve defini-lo primeiro (veja a documentação do xmlstarlet)

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

Com perl, você precisaria processar o arquivo como um todo (não linha por linha) e adicionar o ssinalizador a s///. E mesmo assim, mesmo com uma correspondência não gananciosa, ainda corresponderia do primeiro <Placemark>ao próximo </Placemark>que ocorresse após o próximo <tessellate>. Então você precisaria escrever algo como:

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

Responder2

Dado este arquivo de teste:

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

Se você fizer perl -0 -pe 's|<Placemark>.*?<tessellate>.*?</Placemark>||gs'como sugeriu, removerá muito:

start

middle1

end

Isso ocorre porque a regex está apenas olhando para frente. Ele encontra uma tag inicial, leva tudo até a primeira tag tessellate e até a próxima tag final. Infelizmente ele não se importa se consome mais tags iniciais no caminho...

Se você quiser fazer isso com regexes você terá que processar cada bloco por conta própria: perl -0 -pe 's|<Placemark>.*?</Placemark>|$&=~/<tessellate>/?"":$&|gse'

Isso deve dar o resultado desejado.

Responder3

Usando Python (2.7) com módulos padrão:

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

E o 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))

dá como saída:

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

informação relacionada