Удалить XML-узел, содержащий определенный элемент

Удалить XML-узел, содержащий определенный элемент

Я хочу удалить все метки из файла KML, которые содержат элемент <tessellate>. Следующий блок должен бытьполностьюудаленный:

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

Я попробовал некоторые нежадные регулярные выражения Perl, но безуспешно (много всего удаляется вместе с первым <Placemark>):

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

Я считаю, что XML-парсер — это выход, но я прочитал документацию по xmlstarlet и ничего не нашел. Так что любые решения на xmlstarlet, python и т. д. также приветствуются!

решение1

С xmlstarlet:

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

А поскольку kmlиспользует пространства имен, вам сначала нужно определить его (см. документация xmlstarlet)

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

С perl, вам нужно будет обработать файл целиком (не построчно) и добавить флаг sк s///. И даже тогда, даже с нежадным сопоставлением, он все равно будет соответствовать от первого <Placemark>до следующего </Placemark>, который следует за следующим <tessellate>. Поэтому вам нужно будет написать это примерно так:

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

решение2

Учитывая этот тестовый файл:

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

Если вы сделаете perl -0 -pe 's|<Placemark>.*?<tessellate>.*?</Placemark>||gs'так, как предложили, то это удалит слишком много:

start

middle1

end

Это потому, что регулярное выражение смотрит только вперед. Оно находит начальный тег, берет все до первого тега tessellate и до следующего конечного тега. К сожалению, его не волнует, потребляет ли он больше начальных тегов по пути...

Если вы хотите сделать это с помощью регулярных выражений, вам придется обрабатывать каждый блок отдельно: perl -0 -pe 's|<Placemark>.*?</Placemark>|$&=~/<tessellate>/?"":$&|gse'

Это должно дать желаемый результат.

решение3

Использование Python (2.7) со стандартными модулями:

файл 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>

И программа:

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

дает в качестве вывода:

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

Связанный контент