Archiwa blogu

Python – odczyt oraz modyfikacja dokumentów XML

2017-03-30

W dzisiejszym wpisie pokażę, w jaki sposób odczytywać oraz modyfikować dokumenty XML w języku Python.

Poniżej znajduje się przykładowy dokument XML zawierający informacje o książkach:

<?xml version='1.0' encoding='UTF-8'?>
<books>
  <book>
    <title>Krótka historia czasu</title>
    <author>Stephen Hawking</author>
    <publisher>Zysk i S-ka</publisher>
    <publication_date>2015</publication_date>
    <chapters>
      <chapter number="1">
        <title>Nasz obraz Wszechświata</title>
        <page>13</page>
      </chapter>
      <chapter number="2">
        <title>Czas i przestrzeń</title>
        <page>25</page>
      </chapter>
    </chapters>
  </book>
  <book>
    <title>Filozofia kosmologii</title>
    <author>Michał Heller</author>
    <publisher>Copernicus Center Press</publisher>
    <publication_date>2013</publication_date>
    <chapters>
      <chapter number="1">
        <title>Kosmologia przed Einsteinem</title>
        <page>13</page>
      </chapter>
      <chapter number="2">
        <title>Kosmologia 1917-1965</title>
        <page>39</page>
      </chapter>
    </chapters>
  </book>
</books>

A oto klasy, które reprezentują obiekty książki i rozdziału z powyższego pliku:

class Book():
    def __init__(self, title):
        self.title = title
        self.author = None
        self.publisher = None
        self.publicationDate = None
        self.chapters = list()

class Chapter():
    def __init__(self, book, title, page):
        self.book = book
        self.title = title
        self.page = page
        self.number = None

Mając dokument XML oraz klasy odpowiednich obiektów, możemy przejść do operacji odczytu oraz modyfikacji dokumentu.

Odczyt danych z pliku XML:

from xml.etree.ElementTree import parse, Element, SubElement

def ReadBooks():
    file = r"D:\App\!Python\Test\books.xml"
    books = list()
    doc = parse(file)
    root = doc.getroot()
    for bookElement in root.iterfind("book"):
        title = bookElement.findtext("title")
        book = Book(title)
        book.author = bookElement.findtext("author")
        book.publisher = bookElement.findtext("publisher")
        book.publicationDate = bookElement.findtext("publication_date")
        for chapterElement in bookElement.iterfind("chapters/chapter"):
            title = chapterElement.findtext("title")
            page = chapterElement.findtext("page")
            chapter = Chapter(book, title, page)
            chapter.number = chapterElement.get("number")
            book.chapters.append(chapter)
        book.chapters.sort(key = lambda c : int(c.page))
        books.append(book)
    return books

Dodanie elementu do pliku XML:

from xml.etree.ElementTree import parse, Element, SubElement

def AddBook(book):
    file = r"D:\App\!Python\Test\books.xml"
    doc = parse(file)
    root = doc.getroot()                    
    bookElement = Element("book")
    SubElement(bookElement, "title").text = book.title
    SubElement(bookElement, "author").text = book.author
    SubElement(bookElement, "publisher").text = book.publisher
    SubElement(bookElement, "publication_date").text = book.publicationDate
    chaptersElement = SubElement(bookElement, "chapters")
    for chapter in book.chapters:
        chapterElement = Element("chapter")
        chapterElement.set("number", chapter.number)
        SubElement(chapterElement, "title").text = chapter.title
        SubElement(chapterElement, "page").text = chapter.page
        chaptersElement.append(chapterElement)
    root.append(bookElement)
    doc.write(file, encoding = "UTF-8", xml_declaration = True)

Modyfikacja elementu w pliku XML:

from xml.etree.ElementTree import parse, Element, SubElement

def EditBook(oldBook, newBook):
    file = r"D:\App\!Python\Test\books.xml"
    doc = parse(file)
    root = doc.getroot()
    bookElement = [b for b in root.iterfind("book") if b.findtext("title") == oldBook.title][0]
    bookElement.find("title").text = newBook.title
    bookElement.find("author").text = newBook.author
    bookElement.find("publisher").text = newBook.publisher
    bookElement.find("publication_date").text = newBook.publicationDate
    doc.write(file, encoding = "UTF-8", xml_declaration = True)

def EditChapter(oldChapter, newChapter):
    file = r"D:\App\!Python\Test\books.xml"
    doc = parse(file)
    root = doc.getroot()
    bookElement = [b for b in root.iterfind("book") if b.findtext("title") == oldChapter.book.title][0]
    chapterElement = [c for c in bookElement.iterfind("chapters/chapter") if c.findtext("title") == oldChapter.title][0]
    chapterElement.set("number", newChapter.number)
    chapterElement.find("title").text = newChapter.title
    chapterElement.find("page").text = newChapter.page
    doc.write(file, encoding = "UTF-8", xml_declaration = True)

Usunięcie elementu z pliku XML:

from xml.etree.ElementTree import parse, Element, SubElement

def DeleteBook(book):
    file = r"D:\App\!Python\Test\books.xml"
    doc = parse(file)
    root = doc.getroot()
    bookElement = [b for b in root.iterfind("book") if b.findtext("title") == book.title][0]
    root.remove(bookElement)
    doc.write(file, encoding = "UTF-8", xml_declaration = True)

Kolekcja obiektów i plik XML – zapis, odczyt i modyfikacja danych przy użyciu LINQ to XML

2012-05-18

Na platformie .NET od wersji 3.5 dostępna jest technologia LINQ oferująca uniwersalny mechanizm zadawania zapytań do obiektów. W tym wpisie pokażę w jaki sposób przy użyciu LINQ to XML przenieść dane z kolekcji obiektów do dokumentu XML, wczytać dane z pliku XML do kolekcji oraz zmodyfikować zawartość XML-a. Posłużę się w tym celu klasą System.Xml.Linq.XDocument.

Załóżmy, że mamy klasę Osoba o następującej strukturze:

class Osoba
{
    public string Pesel { get; set; }
    public string Imie { get; set; }
    public string Nazwisko { get; set; }
    public int Wzrost { get; set; }

    public Osoba(string pesel, string imie, string nazwisko, int wzrost)
    {
        Pesel = pesel;
        Imie = imie;
        Nazwisko = nazwisko;
        Wzrost = wzrost;
    }
}

Poniższy kod tworzy kolekcję trzech obiektów typu Osoba, po czym dane z tej kolekcji (posortowane po nazwisku i imieniu) przenosi do dokumentu XML. Na końcu dokument zapisywany jest do pliku Osoby.xml:

List<Osoba> listaOsob = new List<Osoba>();
listaOsob.Add(new Osoba("70010123456", "Jan", "Kowalski", 180));
listaOsob.Add(new Osoba("75061598765", "Janina", "Kowalska", 160));
listaOsob.Add(new Osoba("80122012398", "Marian", "Kowalski", 185));

XDocument xml = new XDocument(
    new XDeclaration("1.0", "utf-8", "yes"),
    new XComment("Lista osób z kolekcji"),
    new XElement("osoby",
        from osoba in listaOsob
        orderby osoba.Nazwisko, osoba.Imie
        select new XElement("osoba",
            new XAttribute("pesel", osoba.Pesel),
            new XElement("imie", osoba.Imie),
            new XElement("nazwisko", osoba.Nazwisko),
            new XElement("wzrost", osoba.Wzrost)
            )
        )
    );

xml.Save("Osoby.xml");

Zawartość utworzonego pliku XML:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!--Lista osób z kolekcji-->
<osoby>
  <osoba pesel="75061598765">
    <imie>Janina</imie>
    <nazwisko>Kowalska</nazwisko>
    <wzrost>160</wzrost>
  </osoba>
  <osoba pesel="70010123456">
    <imie>Jan</imie>
    <nazwisko>Kowalski</nazwisko>
    <wzrost>180</wzrost>
  </osoba>
  <osoba pesel="80122012398">
    <imie>Marian</imie>
    <nazwisko>Kowalski</nazwisko>
    <wzrost>185</wzrost>
  </osoba>
</osoby>

Jeżeli mamy plik XML, możemy na podstawie jego zawartości utworzyć kolekcję obiektów typu Osoba:

XDocument xml = XDocument.Load("Osoby.xml");

List<Osoba> listaOsob = (
    from osoba in xml.Root.Elements("osoba")
    select new Osoba(
        osoba.Attribute("pesel").Value,
        osoba.Element("imie").Value,
        osoba.Element("nazwisko").Value,
        int.Parse(osoba.Element("wzrost").Value)
        )
    ).ToList<Osoba>();

Ostatnią operacją jest modyfikacja danych w istniejącym pliku XML. Pokażę dwa sposoby jej realizacji. Załóżmy, że osobie o numerze pesel 75061598765 chcemy zmienić nazwisko na Nowak.

Przykład 1:

XDocument xml = XDocument.Load("Osoby.xml");

var osoby = xml.Root.Elements("osoba").Where(
    osoba => osoba.Attribute("pesel").Value == "75061598765");
if (osoby.Any())
    osoby.First().Element("nazwisko").Value = "Nowak";

xml.Save("Osoby.xml");

Przykład 2:

XDocument xml = XDocument.Load("Osoby.xml");

var osoby = from osoba in xml.Root.Elements("osoba")
            where osoba.Attribute("pesel").Value == "75061598765"
            select osoba;
if (osoby.Any())
    osoby.First().Element("nazwisko").Value = "Nowak";

xml.Save("Osoby.xml");

W obu przykładach w pliku XML zmodyfikowane zostało nazwisko odpowiedniej osoby:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!--Lista osób z kolekcji-->
<osoby>
  <osoba pesel="75061598765">
    <imie>Janina</imie>
    <nazwisko>Nowak</nazwisko>
    <wzrost>160</wzrost>
  </osoba>
  <osoba pesel="70010123456">
    <imie>Jan</imie>
    <nazwisko>Kowalski</nazwisko>
    <wzrost>180</wzrost>
  </osoba>
  <osoba pesel="80122012398">
    <imie>Marian</imie>
    <nazwisko>Kowalski</nazwisko>
    <wzrost>185</wzrost>
  </osoba>
</osoby>

W przypadku zapytań na dokumentach XML, do kolekcji ich elementów mamy dostęp poprzez odpowiednie właściwości i metody:

  • Root – element główny wraz z całą zawartością dokumentu
  • Elements – elementy-dzieci bieżącego elementu z możliwością podania ich nazwy
  • Nodes – węzły-dzieci bieżącego elementu (w odróżnieniu od Elements zwracane są także komentarze i tekst)
  • Descendants – elementy-dzieci bieżącego elementu na dowolnym poziomie z możliwością podania ich nazwy
  • Parent – rodzic bieżącego elementu
  • Ancestors – rodzice bieżącego elementu na dowolnym poziomie z możliwością podania ich nazwy
  • ElementsBeforeSelf – elementy o tym samym rodzicu znajdujące się przed bieżącym elementem z możliwością podania ich nazwy
  • ElementsAfterSelf – elementy o tym samym rodzicu znajdujące się po bieżącym elemencie z możliwością podania ich nazwy
  • Attributes – atrybuty bieżącego elementu z możliwością podania ich nazwy

Więcej informacji o technologii LINQ to XML: MSDN