寫在之前

帶分隔符的文件僅有兩維的數據:行 & 列。如果我們想在程序之間交換數據結構,需要一種方法把層次結構,序列,集合和其它的數據結構編碼成文本。

今天要說的 XML 是最突出的處理上述這種轉換的標記格式,它使用標籤(tag)分隔數據。XML 在軟體領域的用途非常廣泛。

XML

XML 是什麼?如果非要對其做一個定義式的說明,那這裡我不得不引用一下 w3school 裡面簡潔而明快的說明:

XML 指可擴展標記語言(EXtensible Markup Language);

XML 是一種標記語言,類似於 HTML;

XML 的設計宗旨是傳輸數據,而非顯示數據;

XML 標籤沒有被預定義,需要自行定義標籤; XML 被設計為具有自我描述性; XML 是 W3C 的推薦標準。

如果你想要詳細瞭解和學習 XML 的話,可以去閱讀 w3school 的 XML 教程即可,裡面講述的很詳細,在下面我還會引用一些裡面的內容。

XML 的重要性在於它是用來傳輸數據的,因此,特別是在 Web 編程中我們經常會用到它。有了它,讓數據傳輸變的更加簡單,這麼重要的東西,我大 Python 當然支持。

有大佬曾經說過:「一個引人關注的東西總會有很多人從不同側面去研究它」。這個在編程中也同樣適用,所以對於 XML 這個紅得發紫的東西,Python 提供了多種模塊來處理。

  • xml.dom.* 模塊:Document Object Model。適合用於處理 DOM API。它能夠將 XML 數據在內存中解析成一個樹,然後通過對樹的操作來操作 XML。但是這種方式由於將 XML 數據映射到內存中的樹,導致比較慢,且消耗更多內存。
  • xml.sax.* 模塊:simple API for XML。由於 SAX 以流式讀取 XML 文件,從而速度較快,佔用內存少,但是在操作上稍微複雜,需要用戶實現回調函數。

當然還有一些別的,比如 xml.parse.expat,xml.etree.ElementTree 等等,我就不在列舉了,碰到的時候再去查查,否則光看這些東西頭就大了,而且無聊的很。

遍歷查詢

先要做一個 XML 文檔,我自己想也想不出個啥太好的來,所以直接用 w3school 中的一個例子,如下圖所示:

上圖表示下面的 XML 中的一本書:

<bookstore>
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="CHILDREN">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>

將上述的 XML 保存並且命名為 test.xml 文件,接下來就是以它為對象,練習各種操作了。

>>> import xml.etree.ElementTree as ET
>>> tree = ET.ElementTree(file = test.xml)
>>> tree
<xml.etree.ElementTree.ElementTree object at 0x00000000025B8630>

上面建立起 XML 解析樹對象,然後通過根節點向下開始讀取各個元素(element 對象)。

在上述 XML 文檔中,根元素是 bookstore,它沒有屬性,也可以說是屬性為空。

>>> root = tree.getroot()
>>> root.tag
bookstore
>>> root.attrib
{}

要想將根下面的元素都讀取出來,可以進行如下操作:

>>> for child in root:
... print(child.tag,child.attrib)
...
(book, {category: COOKING})
(book, {category: CHILDREN})
(book, {category: WEB})

也可以像下面這樣讀取指定元素的信息:

>>> root[0].tag
book
>>> root[0].attrib
{category: COOKING}
>>> root[0].text

上述的 root[0].text 無內容,再深入一層,我們就可以看到內容了:

>>> root[0][0].tag
title
>>> root[0][0].attrib
{lang: en}
>>> root[0][0].text
Everyday Italian

對於 ElementTree 對象,有一個 iter() 方法可以對指定名稱的子節點進行深度優先遍歷,例如下面這樣:

>>> for ele in tree.iter(tag=book):
... print(ele.tag,ele.attrib)
...
(book, {category: COOKING})
(book, {category: CHILDREN})
(book, {category: WEB})

上述代碼是遍歷名稱為 book 的節點,如果不指定節點的話,就是將所有的元素遍歷一遍:

>>> for ele in tree.iter():
... print(ele.tag,ele.attrib)
...
(bookstore, {})
(book, {category: COOKING})
(title, {lang: en})
(author, {})
(year, {})
(price, {})
(book, {category: CHILDREN})
(title, {lang: en})
(author, {})
(year, {})
(price, {})
(book, {category: WEB})
(title, {lang: en})
(author, {})
(year, {})
(price, {})

除了上面的方法外,還可以通過路徑搜索到指定的元素,然後讀取其內容,這就是 xpath,關於 xpath 是什麼,在這不多做介紹,感興趣的可以去 Google。

編輯(增刪改查)

我們還是用上面的例子,為了方便查看,我把內容再粘貼過來,下面的內容記得保存並且命名為 test.xml。

<bookstore>
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="CHILDREN">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>

上一篇文章我們主要是對 xml 進行了讀取的有關操作,其實還可以對 XML 進行編輯,也就是增刪改查的功能,下面我們來操作一下:

>>> import xml.etree.ElementTree as ET
>>> tree = ET.ElementTree(file = "test.xml")
>>> root = tree.getroot() #獲得根
>>> root[1].tag
book
>>> del root[1]
>>> for ele in root:
... print(ele.tag)
...
book
book

如上,我們成功的刪除了一個節點,原來有 3 個 book 節點,現在就只剩下兩個了。接下來讓我們打開源文件看看,是不是正好缺少了第 2 個節點呢?結果讓我們很失望,源文件並沒有什麼變化。

確實如此,源文件並沒有變,因為到了這一步的修改動作還只是停留在內存裏,還沒有將修改的結果輸出到文件,不要忘記我們是在內存中建立的 ElementTree 對象。那麼該如何做呢?請接著往下看:

>>> import os
>>> outpath = os.getcwd()
>>> file = outpath + "/test.xml"

把當前文件的路徑拼裝好。

>>> tree.write(file)

做完上面的操作以後再去看源文件,已經變成兩個節點了。

除了刪除,也是可以修改的:

>>> for price in root.iter(price): #原來每本書的價格
... print(price.text)
...
30.00
39.95
>>> for price in root.iter(price): #每本上漲 10 元並做標記
... new_price = float(price.text) + 10
... price.text = str(new_price)
... price.set("updated","up")
...
>>> tree.write(file)

然後我們來查看一下源文件:

<bookstore>
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price updated="up">50.0</price>
</book>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price updated="up">49.95</price>
</book>
</bookstore>

通過對比我們可以發現,不僅價格改變了,而且在 price 標籤裡面增加了屬性標記。

上面我們是用 del 來刪除某個元素,其實這個在編程中我們用的並不多,一般情況下更喜歡用 remove() 方法。比如要刪除 price = 50 的書,可以像下面這樣操作:

>>> tree.write(file)
>>> for book in root.findall("book"):
... price = book.find("price").text
... if float(price) == 50:
... root.remove(book)
...
>>> tree.write(file)

於是就有了下面的結果:

<bookstore>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price updated="up">49.95</price>
</book>
</bookstore>

接下來我們來看看增加元素:

>>> import xml.etree.ElementTree as ET
>>> tree = ET.ElementTree(file = test.xml)
>>> root = tree.getroot()
>>> ET.SubElement(root,"book") # 在root裡面添加book節點
<Element book at 0x000000000209C778>
>>> for ele in root:
... print(ele.tag)
...
book
book
>>> b2 = root[1]
>>> b2.text = python
>>> tree.write(test.xml)

這樣就大功告成了,然後再像上面一樣看一下源文件,發現果真增加了。

常用的屬性 & 方法

ET 裡面的屬性 & 方法很多,這裡列出常用的幾個,供使用中備查。

1.Element 對象

常用的屬性如下:

  • tag:string,元素數據種類
  • text:string,元素的內容
  • attrib:dictionary,元素的屬性字典
  • tail:string,元素的尾形

針對屬性的操作如下:

  • clear():清空元素的後代,屬性,text 和 tail 也設置為 None。
  • items():根據屬性字典返回一個列表,列表元素為(key,value)。
  • keys():返回包含所有元素屬性鍵的列表。
  • set(key,value):設置新的屬性鍵和值。

針對後代的操作如下:

  • append(subelement):添加直系子元素。
  • extend(sunelements):增加一串元素對象作為子元素。
  • find(match):尋找第一個匹配子元素,匹配對象可以為 tag 或 path。
  • findall(match):尋找所有匹配子元素,匹配對象可以為 tag 或 path。
  • insert(index,element):在指定位置插入子元素。
  • remove(subelement):刪除子元素

2.ElementTree 對象

  • find(match)。
  • findall(match)。
  • getroot():獲取根結點。
  • parse(source,parser = None):裝載 XML 對象,source 可以為文件名或文件類型對象。

寫在之後

更多內容,歡迎關注公眾號「Python空間」,期待和你的交流。


推薦閱讀:
相關文章