Modułów służących do przetwarzania XML w Pythonie jest bardzo wiele, prezentuję tutaj taki z jakiego sam korzystam. Nawet jeśli zamierzasz korzystać z innego, możesz potraktować ten rozdział jako przykład.
W tym samym katalogu co kod który będę uruchamiać w ramach przykładów umieszczam plik o nazwie "dane.xml", a oto jego zawartość:
<?xml version="1.0" encoding="UTF-8"?>
<dane atrybut="jakaś wartość">
<imie>Andrzej</imie>
<nazwisko param="wartość przykładowa" param2="kolejna wartość przykładowa">Klusiewicz</nazwisko>
<wzrost>176cm</wzrost>
<adres>
<miasto>Warszawa</miasto>
<kod>02-019</kod>
</adres>
<jezyki>polski</jezyki>
<jezyki>angielski</jezyki>
<jezyki>Java</jezyki>
<jezyki>R</jezyki>
<jezyki>Python</jezyki>
<jezyki>PL/SQL</jezyki>
</dane>
Są to te same dane których używałem w rozdziale o przetwarzaniu JSON - tyle że tu w formacie XML.
Zaczniemy od zaimportowania odpowiedniej biblioteki i parsowania pliku XML.
import xml.etree.ElementTree as et
drzewo=et.parse('dane.xml')
Funkcja parse powoduje odczyt wskazanego pliku w całości. Tą samą funkcją można też przeładować dane, gdyby na przykład zostały zmodyfikowane a chcielibyśmy widzieć zmiany. Zmienna drzewo jest obiektem specjalnej klasy opakowującej, tym razem nie bedzie to żadna z omawianych dotychczas struktur danych, zapewne dla tego że w Pythonie nie ma właściwego odpowiednika strukturalnego. Możesz sprawdzić jaki to typ wywołując:
print(type(drzewo))
Na konsoli zostanie wyrzucony typ:
<class 'xml.etree.ElementTree.ElementTree'>
Jest to klasa zdefiniowana w tej samej bibliotece co narzędzia których bedziemy używać. W związku z tym będziemy musieli używać specjalnych funkcji (również wbudowanych w tę samą bibliotekę) służących do przetwarzania obiektu XML.
Format XML wymaga by struktura danych posiadała korzeń - a więc jeden element w którym zawarte są wszystkie pozostałe. Taki element oczywiście w rzeczonym przykładzie występuje, a jest to element który w pliku jest reprezentowany parą tagów:
<dane atrybut="jakaś wartość">
....
</dane>
Aby uzyskać do niego dostęp wykorzystamy funkcję "getroot" która zwraca nam handler do korzenia:
root=drzewo.getroot()
W tej chwili możemy już poruszać się po drzewie XML. Dla przykładu jeśli zechcemy odczytać zawartość pola imię które w pliku znajduje się tu:
<?xml version="1.0" encoding="UTF-8"?>
<dane atrybut="jakaś wartość">
<imie>Andrzej</imie>
zastosujemy konstrukcję jak poniżej:
imie=root.find('imie')
Do zmiennej "imie" nie zostanie jednak przypisana wartość z elementu, czego pewnie można by się spodziewać, a obiekt klasy Element. Jest tak, ponieważ każdy z elementów może mieć jeszcze podelementy do których także będziemy uzyskiwać dostęp. Możemy to sprawdzić drukując samą zmienną "imie", oraz jej typ:
print(imie)
print(type(imie))
Na konsoli zobaczymy:
<Element 'imie' at 0x000002BBEC6CAA98>
<class 'xml.etree.ElementTree.Element'>
W związku z tym, będziemy potrzebowali wykorzystać atrybut "text" tej klasy do pobrania właściwej wartości:
print(imie.text)
print(type(imie.text))
co dopiero zwróci nam oczekiwane przez nas dane. Zrzut z konsoli jak zwykle:
Andrzej
<class 'str'>
Ok, mamy rozebrany proces na części pierwsze. Złóżmy teraz pacjenta do kupy. Całość dotychczasowego kodu, od otwarcia pliku do wydłubania danych z elementu "imie":
import xml.etree.ElementTree as et
drzewo=et.parse('dane.xml')
root=drzewo.getroot()
imie=root.find('imie').text
print(imie)
Korzystając z funkcji "find" wydobywaliśmy obiekt "imie" klasy "Element" z obiektu "root". Tak się składa, że obiekt "root" także jest klasy "Element", a z tego płynie wniosek że wydobycie podelementów wyglądać będzie tak samo dla dowolnego obiektu tej klasy. Sprawdźmy:
import xml.etree.ElementTree as et
tree=et.parse('dane.xml')
root=tree.getroot()
adres=root.find('adres')
miasto=adres.find('miasto')
print(miasto.text)
Zróć uwagę że z obiektu "adres" za pomocą funkcji "find" wydobywam podelement "miasto" w taki sam sposób w jaki wcześniej wydobywałem obiekt "imie" z obiektu "root". Po uruchomieniu tego kodu, na konsoli zobaczymy "Warszawa".
Podobnie jak mogę odnajdywać elementy po nazwie, tak mogę i po pozycji.
import xml.etree.ElementTree as et
drzewko=et.parse('dane.xml')
korzonek=drzewko.getroot()
drugi=korzonek[1].text
print(drugi)
W ten sposób odwołam się do elementu "nazwisko" będącego drugim elementem w pliku (czyli mającemu indeks 1 - liczenie od zera). Co jednak jeśli zechcielibyśmy odwołać się do elementu "kod" zagnieżdżonego w elemencie "adres"? Adres ma indeks 3 (czwarta pozycja), a "kod" ma indeks 1 (druga pozycja) wewnątrz elementu "adres", przypomina więc to listę zagnieżdżoną w liście... Skoro tak, to możemy do "kodu" dobrać się tak:
import xml.etree.ElementTree as et
drzewko=et.parse('dane.xml')
korzonek=drzewko.getroot()
zag=korzonek[3][1].text
print(zag)
W naszym pliku XML mamy jeszcze takie wartości:
...
...
</adres>
<jezyki>polski</jezyki>
<jezyki>angielski</jezyki>
<jezyki>Java</jezyki>
<jezyki>R</jezyki>
<jezyki>Python</jezyki>
<jezyki>PL/SQL</jezyki>
</dane>
Chcielibyśmy odnaleźć wszystkie elementy znajdujące się pomiędzy tagami <jezyki> i </jezyki>. Nic prostszego:
import xml.etree.ElementTree as et
d=et.parse("dane.xml")
root=d.getroot()
for e in root.findall('jezyki'): # tylko po elementach "jezyki"
print(e.text)
Analogicznie do funkcji "find", jest też funkcja "findall" odnajdująca wszystkie elementy o określonym tagu. Funkcja ta zwraca nam zwyczajną listę, po której możemy iterować. Skoro jest to lista, to rozwiązuje nam to również problem typu "a jak się dobrać do 2 wystąpienia elementu xyz?"
import xml.etree.ElementTree as et
d=et.parse("dane.xml")
root=d.getroot()
print (root.findall('jezyki')[1].text)
Atrybuty występują w naszym pliku z danymi w dwóch miejscach. Podświetliłem je na żółto w przykładzie poniżej.
<?xml version="1.0" encoding="UTF-8"?>
<dane atrybut="jakaś wartość">
<imie>Andrzej</imie>
<nazwisko param="wartość przykładowa" param2="kolejna wartość przykładowa">Klusiewicz</nazwisko>
<wzrost>176cm</wzrost>
<adres>
<miasto>Warszawa</miasto>
<kod>02-019</kod>
</adres>
<jezyki>polski</jezyki>
<jezyki>angielski</jezyki>
<jezyki>Java</jezyki>
<jezyki>R</jezyki>
<jezyki>Python</jezyki>
<jezyki>PL/SQL</jezyki>
</dane>
Chciałbym się teraz do nich dobrać. Podobnie jak mogę na elemencie wywołać "text" by dostać jego zawartość, tak mogę wywołać również "attrib" dostając w zamian wszystkie atrybuty w postaci słownika:
import xml.etree.ElementTree as et
tree=et.parse("dane.xml")
root=tree.getroot()
nazwisko=root.find('nazwisko')
print(nazwisko.attrib)
Wynik działania:
{'param': 'wartość przykładowa', 'param2': 'kolejna wartość przykładowa'}
Skoro to słownik, to chcąc wybrać zawartość atrybutu "param", mogę posłużyć się notacją znaną nam już ze słowników i wykorzystać nazwę atrybutu jako klucz słownika (bo tak to jest przechowywane jak widać powyżej):
import xml.etree.ElementTree as et
tree=et.parse("dane.xml")
root=tree.getroot()
nazwisko=root.find('nazwisko')
print(nazwisko.attrib['param'])
Wynik działania:
wartość przykładowa
Ewentualnie to samo ale w krótszym zapisie:
import xml.etree.ElementTree as et
print(et.parse("dane.xml").getroot().find("nazwisko").attrib['param'])
Gdybyśmy zechcieli odczytać zawartość pliku jako zwykły tekst, moglibyśmy oczywiście odczytać plik XML jako zwykły plik tekstowy. Nie zawsze jednak dane XML będą pochodzić z pliku, mogą być np. pobrane z jakiejś usługi sieciowej. Wydrukowanie xml na konsolę w ten sposób:
import xml.etree.ElementTree as et
drzewko= et.parse("dane.xml")
korzen = drzewko.getroot()
print(korzen)
wyświetli nam informacje o obiekcie, a nie zawartość tekstową:
<Element 'dane' at 0x000002A21895A9A8>
Moduł ElementTree posiada metodę tostring która pozwoli nam na odczyt XML jako tekst:
import xml.etree.ElementTree as et
drzewko= et.parse("dane.xml")
korzen = drzewko.getroot()
print(et.tostring(korzen))
Poniżej pokazuję przykład wyświetlenia nazwy, atrybutów i zawartości wszystkich elementów na pierwszym poziomie zagnieżdżenia. Chodzi mi zasadniczo o wywołanie "e.tag" które jest tu nowością, a służy do pobierania nazwy elementu właśnie. Przykład jednak uznałem za użyteczny i dlatego zamieszczam go w takiej formie:
import xml.etree.ElementTree as et
r=et.parse("dane.xml").getroot()
for e in r:
print(e.tag, e.attrib, e.text)
Wynik działania:
imie {} Andrzej
nazwisko {'param': 'wartość przykładowa', 'param2': 'kolejna wartość przykładowa'} Klusiewicz
wzrost {} 176cm
adres {}
jezyki {} polski
jezyki {} angielski
jezyki {} Java
jezyki {} R
jezyki {} Python
jezyki {} PL/SQL
Podobnie jak używaliśmy funkcji find do odnalezienia elementu w celu jego odczytania, tak możemy jej użyć w celu zmiany zawartości. Tym razem zamiast wykorzystywać "text" do odczytu zawartości elementu, używamy go do podstawienia nowej wartości:
import xml.etree.ElementTree as et
r=et.parse("dane.xml").getroot()
for e in r:
print(e.tag,e.text)
r.find('nazwisko').text="Klusiewicz po zmianie"
print("-------------------")
for e in r:
print(e.tag,e.text)
Dodawanie i modyfikowanie atrybutów elementów odbywa się za pomocą "attrib" tak samo jak jego pobieranie. Jeśli atrybut o podanej w nawiasach kwadratowych już istnieje dla podanego elementu, zostanie nadpisany, jeśli nie to zostanie dodany:
import xml.etree.ElementTree as et
r=et.parse("dane.xml").getroot()
for e in r:
print(e.tag,e.text,e.attrib)
r.find('nazwisko').attrib['encoding']="utf-8"
print("-------------------")
for e in r:
print(e.tag,e.text,e.attrib)
Do tworzenia podelementów używamy "SubElement" z modułu ElementTree. Jako jego parametry podajemy element który ma się stać rodzicem nowo dodawanego elementu (w tym przypadku korzeń drzewa - czyli nowy element będzie równoległy do elementów nazwisko, imię etc), oraz nazwę element. Później dodajemy wartość elementu, tak samo jak robiliśmy to w istniejących elementach:
import xml.etree.ElementTree as et
r=et.parse("dane.xml").getroot()
for e in r:
print(e.tag,e.text,e.attrib)
nowy=et.SubElement(r,"masa")
nowy.text=78
print("-------------------")
for e in r:
print(e.tag,e.text,e.attrib)
Taki nowy podelement zostanie dodany na końcu. Gdybyśmy zechcieli dodać go w wybranym przez nas miejscu, użyjemy funkcji "insert":
import xml.etree.ElementTree as et
r=et.parse("dane.xml").getroot()
for e in r:
print(e.tag,e.text,e.attrib)
nowy=et.Element("samochod")
nowy.text="Renault"
r.insert(0,nowy)
print("-------------------")
for e in r:
print(e.tag,e.text,e.attrib)
Funkcja "insert" jest wywoływana na rzecz tego elementu w którym chcemy umieścić nowy element jako podelement. Przyjmuje ona dwa parametry. Pierwszy to pozycja (w tym przypadku pierwsza), drugi to element który ma zostać dodany.
Kasować elementy z drzewa XML możemy po pozycji:
import xml.etree.ElementTree as et
r=et.parse("dane.xml").getroot()
for e in r:
print(e.tag,e.text)
del r[0]
print("-------------------")
for e in r:
print(e.tag,e.text)
lub po nazwie :
import xml.etree.ElementTree as et
r=et.parse("dane.xml").getroot()
for e in r:
print(e.tag,e.text)
naz =r.find('nazwisko')
r.remove(naz)
print("-------------------")
for e in r:
print(e.tag,e.text)
Zapis do pliku odbywa się w podobny sposób jak zapis do pliku tekstowego. Tym razem skorzystamy z funkcji write wbudowanej w element. Zadbamy też o to by zapis wykorzystał właściwe kodowanie. Poniższy przykład odczytuje drzewo XML z pliku "dane.xml", a następnie bez jego modyfikowania zapisuje je do pliku "dane2.xml":
import xml.etree.ElementTree as et
d = et.parse("dane.xml")
d.write("dane2.xml",encoding="utf-8")
Komentarze (0)
Brak komentarzy...