Моя проблема очень похожа на найденную здесь:
Как вытащить данные из 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><b>ID</b></displayName>
</SimpleField>
<SimpleField type = "string" name = "cname"><displayName><b>cname</b></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»?
Я потратил часы, пытаясь решить проблему. Любая помощь приветствуется.
По какой-то причине у меня возникли проблемы с 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 не может измениться, нет. Но как только элемент выбран с помощью функции lxml xpath (или find() или findall()), вы можете изменить его, используя методы append, remove и т. д. класса _Element. См. методы здесь: lxml.de/api/lxml.etree._Element-class.html В lxml-документация также есть примеры, но иногда их трудно найти. Однако в stackoverflow есть много примеров.
Я забыл упомянуть вас в моем предыдущем комментарии @Marlin. Пожалуйста, дайте мне знать, если у вас остались вопросы.
Я провел некоторое время за чтением учебника по lxml.etree, но, похоже, я все еще не полностью понимаю принцип. XPath довольно прост. Просто используйте путь, чтобы перейти к элементу и получить значение с .text. Затем, если мне нужно изменить существующий kml, мне нужно использовать один из примеров в lxml.etree. Однако у меня это не работает, так как я не могу разобраться с пространствами имен.
Возможно, вы дадите мне подсказку о том, как добавить элемент с именем <name> со значением «test» прямо под <Placement> в моем примере выше?
@Marlin - Не могли бы вы создать новый вопрос? Кроме того, в вашем примере нет элементов <Placement>
; Вы имели в виду метку? Если да, то их 2; как вы хотите определить, после какого из них добавить новый элемент?
Да, @Daniel, я создам новый вопрос. Вчера я провел еще один эксперимент и многому научился, но все же хотел бы услышать ваши комментарии.
Я создал новый пост @Daniel. Если возможно, не могли бы вы также объяснить мне немного подробнее о концепции пространства имен? stackoverflow.com/questions/55670854/…
Спасибо, Даниил, твой ответ идеален. Просто хотелось бы немного уточнить. XPath не позволяет изменять файл KML? Я просмотрел документ и не смог найти никаких ссылок на «добавить», «удалить» и т. д. Я знаю, что могу сделать это с другими модулями, такими как pykml.