Как получить значения элементов из KML с помощью lmxl

Моя проблема очень похожа на найденную здесь:

Как вытащить данные из KML/XML?

Ответ на поставленный выше вопрос заключается в использовании Nokogiri для исправления формата.

Интересно, есть ли способ решить подобную проблему без предварительного исправления формата.

Как я могу получить значения dict, чтобы я мог получить «FM2» и «FM3» из Element SimpleData ниже?

Вот мой кмл:

<?xml version = "1.0" encoding = "UTF-8"?>
<kml xmlns = "http://www.opengis.net/kml/2.2" xmlns:gx = "http://www.google.com/kml/ext/2.2" xmlns:kml = "http://www.opengis.net/kml/2.2" xmlns:atom = "http://www.w3.org/2005/Atom">
<Document>
    <name>Test.kml</name>
    <open>1</open>
    <Schema name = "test" id = "S_test_SSSSSIIIDSDDDDDISSSDSSSDD">
        <SimpleField type = "string" name = "ID"> <displayName>&lt;b&gt;ID&lt;/b&gt;</displayName>
        </SimpleField>
        <SimpleField type = "string" name = "cname"><displayName>&lt;b&gt;cname&lt;/b&gt;</displayName>
        </SimpleField>
    </Schema>
    <Style id = "falseColor01">
        <BalloonStyle>
            <text><![CDATA[<table border = "0"><tr> 
            <td>b>ID</b>/td>td>$[test/ID]</td></tr>
            <tr><td><b>cname</b></td><td>$[test/cname]</td></tr>
            </table>]]></text>
        </BalloonStyle>
        <LineStyle>
            <color>ffffff00</color>
            <width>3</width>
        </LineStyle>
        <PolyStyle>
            <color>ffffff00</color>
            <colorMode>random</colorMode>
            <fill>0</fill>
        </PolyStyle>
    </Style>
    <StyleMap id = "falseColor0">
        <Pair>
            <key>normal</key>
            <styleUrl>#falseColor00</styleUrl>
        </Pair>
        <Pair>
            <key>highlight</key>
            <styleUrl>#falseColor01</styleUrl>
        </Pair>
    </StyleMap>
    <Style id = "falseColor00">
      <BalloonStyle>   
      </BalloonStyle>
        <LineStyle>
            <color>ffffff00</color>
            <width>3</width>
        </LineStyle>
        <PolyStyle>
            <color>ffffff00</color>
            <colorMode>random</colorMode>
            <fill>0</fill>
        </PolyStyle>
    </Style>
    <Folder id = "layer 0">
        <name>Test_1</name>
        <open>1</open>
        <Placemark>
            <styleUrl>#falseColor0</styleUrl>
            <ExtendedData>
                <SchemaData schemaUrl = "#S_test_SSSSSIIIDSDDDDDISSSDSSSDD">
                    <SimpleData name = "ID">FM2</SimpleData>
                    <SimpleData name = "cname">FM2</SimpleData>
                </SchemaData>
            </ExtendedData>
            <Polygon>
                <outerBoundaryIs>
                    <LinearRing>
                        <coordinates>150.889999,-32.17281600000001,0 
                        </coordinates>
                    </LinearRing>
                </outerBoundaryIs>
            </Polygon>
        </Placemark>
        <Placemark>
            <styleUrl>#falseColor0</styleUrl>
            <ExtendedData>
                <SchemaData schemaUrl = "#S_test_SSSSSIIIDSDDDDDISSSDSSSDD">
                    <SimpleData name = "ID">FM3</SimpleData>
                    <SimpleData name = "cname">FM3</SimpleData>
                </SchemaData>
            </ExtendedData>
            <Polygon>
                <outerBoundaryIs>
                    <LinearRing>
                        <coordinates>150.90104,-32.15662800000001,0
                        </coordinates>
                    </LinearRing>
                </outerBoundaryIs>
            </Polygon>
        </Placemark>
    </Folder>
</Document>
</kml>

Моя цель - получить значения элемента, т. е. «FM2», из «ID» элемента.

Я пытаюсь использовать lxml etree. Мой код:

tree  = ET.parse(kml_file)
root = tree.getroot()

for Document in root:
    for Folder in Document:
        for Placemark in Folder:
            for ExtendedData in Placemark:
                for SchemaData in ExtendedData:
                    for SimpleData in SchemaData:
                        print(SimpleData.attrib)

и вывод: {'имя': 'идентификатор'} {'имя': 'имя'}

Как я могу получить значения dict, чтобы получить «FM2» и «FM3»?

Я потратил часы, пытаясь решить проблему. Любая помощь приветствуется.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
0
801
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

По какой-то причине у меня возникли проблемы с xml-валидностью вашего kml_file, поэтому я сделал так:

import lxml.html
tree  = lxml.html.fromstring(kml_file)
results = tree.xpath("//*[@name = 'ID']")

for i in results:
    if i.text:
        print(i.text)

Я не уверен, что это то, что вы ищете, но вывод:

FM2
FM3
Ответ принят как подходящий

Одна из проблем, с которыми вы сталкиваетесь, заключается в том, что когда вы делаете for x in y, вы повторяете все дочерние элементы текущего элемента.

Итак, когда вы делаете это:

for Folder in Document:
    ...

вы не просто перебираете Folder элементы; вы также повторяете name, open, Schema, Style и StyleMap (на данный момент исключая пространство имен).

Вы мог по-прежнему получаете то, что хотите, проверяя значение атрибута name, а затем возвращая текст элементов...

for Document in root:
    for Folder in Document:
        for Placemark in Folder:
            for ExtendedData in Placemark:
                for SchemaData in ExtendedData:
                    for SimpleData in SchemaData:
                        if SimpleData.get("name") == "ID":
                            print(SimpleData.text)

но я бы не рекомендовал.

Вместо этого рассмотрите возможность использования XPath 1.0 с функцией lxml xpath().

Это позволит вам напрямую ориентироваться на интересующие вас элементы.

В этом примере я буду использовать полный путь вместо //сокращенный синтаксис. Я также буду использовать предикат для проверки значения атрибута.

На первый взгляд вы могли бы подумать, что XPath для всех элементов SimpleData со значением атрибута name «ID» будет:

/kml/Document/Folder/Placemark/ExtendedData/SchemaData/SimpleData[@name='ID']

Но это не так. Если вы заметили, что в корневом (xmlns = "http://www.opengis.net/kml/2.2") элементе есть kml. Это означает, что этот элемент и все его дочерние элементы находятся в пространстве имен по умолчанию http://www.opengis.net/kml/2.2 (если для этих элементов не указано иное).

Для иллюстрации, если вы добавите print(f"In Folder element \"{Folder.tag}\"...") в свой цикл for Folder in Document, вы увидите:

In Folder element "{http://www.opengis.net/kml/2.2}name"...
In Folder element "{http://www.opengis.net/kml/2.2}open"...
In Folder element "{http://www.opengis.net/kml/2.2}Schema"...
In Folder element "{http://www.opengis.net/kml/2.2}Style"...
In Folder element "{http://www.opengis.net/kml/2.2}StyleMap"...
In Folder element "{http://www.opengis.net/kml/2.2}Style"...
In Folder element "{http://www.opengis.net/kml/2.2}Folder"...

Есть несколько способов обработки пространства имен в lxml, но я предпочитаю объявлять их в словаре и передавать с аргументом namespaces.

Вот полный пример...

from lxml import etree

ns = {"kml": "http://www.opengis.net/kml/2.2"}

tree = etree.parse("test.kml")

for simple_data in tree.xpath("/kml:kml/kml:Document/kml:Folder/kml:Placemark/kml:ExtendedData/kml:SchemaData/kml:SimpleData[@name='ID']", namespaces=ns):
    print(simple_data.text)

Вывод на печать...

FM2
FM3

Спасибо, Даниил, твой ответ идеален. Просто хотелось бы немного уточнить. XPath не позволяет изменять файл KML? Я просмотрел документ и не смог найти никаких ссылок на «добавить», «удалить» и т. д. Я знаю, что могу сделать это с другими модулями, такими как pykml.

Marlin 10.04.2019 09:11

Сам XPath не может измениться, нет. Но как только элемент выбран с помощью функции lxml xpath (или find() или findall()), вы можете изменить его, используя методы append, remove и т. д. класса _Element. См. методы здесь: lxml.de/api/lxml.etree._Element-class.html В lxml-документация также есть примеры, но иногда их трудно найти. Однако в stackoverflow есть много примеров.

Daniel Haley 10.04.2019 14:22

Я забыл упомянуть вас в моем предыдущем комментарии @Marlin. Пожалуйста, дайте мне знать, если у вас остались вопросы.

Daniel Haley 10.04.2019 16:30

Я провел некоторое время за чтением учебника по lxml.etree, но, похоже, я все еще не полностью понимаю принцип. XPath довольно прост. Просто используйте путь, чтобы перейти к элементу и получить значение с .text. Затем, если мне нужно изменить существующий kml, мне нужно использовать один из примеров в lxml.etree. Однако у меня это не работает, так как я не могу разобраться с пространствами имен.

Marlin 12.04.2019 02:29

Возможно, вы дадите мне подсказку о том, как добавить элемент с именем <name> со значением «test» прямо под <Placement> в моем примере выше?

Marlin 12.04.2019 02:36

@Marlin - Не могли бы вы создать новый вопрос? Кроме того, в вашем примере нет элементов <Placement>; Вы имели в виду метку? Если да, то их 2; как вы хотите определить, после какого из них добавить новый элемент?

Daniel Haley 12.04.2019 03:07

Да, @Daniel, я создам новый вопрос. Вчера я провел еще один эксперимент и многому научился, но все же хотел бы услышать ваши комментарии.

Marlin 13.04.2019 01:00

Я создал новый пост @Daniel. Если возможно, не могли бы вы также объяснить мне немного подробнее о концепции пространства имен? stackoverflow.com/questions/55670854/…

Marlin 14.04.2019 01:53

Другие вопросы по теме