XML 文檔形成了一種樹結構,它從"根部"開始,然后擴展到"枝葉"。
一個 XML 文檔實例
XML 文檔使用簡單的具有自我描述性的語法:
第一行是 XML 聲明。它定義 XML 的版本(1.0)和所使用的編碼(UTF-8 : 萬國碼, 可顯示各種語言)。
下一行描述文檔的根元素(像在說:"本文檔是一個便簽"):
<note>
接下來 4 行描述根的 4 個子元素(to, from, heading 以及 body):
最后一行定義根元素的結尾:
</note>
您可以假設,從這個實例中,XML 文檔包含了一張 Jani 寫給 Tove 的便簽。
XML 具有出色的自我描述性,您同意嗎?
XML 文檔形成一種樹結構
XML 文檔必須包含根元素。該元素是所有其他元素的父元素。
XML 文檔中的元素形成了一棵文檔樹。這棵樹從根部開始,並擴展到樹的最底端。
所有的元素都可以有子元素:
父、子以及同胞等術語用於描述元素之間的關系。父元素擁有子元素。相同層級上的子元素成為同胞(兄弟或姐妹)。
所有的元素都可以有文本內容和屬性(類似 HTML 中)。
實例:
上圖表示下面的 XML 中的一本書:
XML 文檔實例
實例中的根元素是 <bookstore>。文檔中的所有 <book> 元素都被包含在 <bookstore> 中。
<book> 元素有 4 個子元素:<title>、<author>、<year>、<price>。
XML 語法規則
XML 的語法規則很簡單,且很有邏輯。這些規則很容易學習,也很容易使用。
XML 文檔必須有根元素
XML 必須包含根元素,它是所有其他元素的父元素,比如以下實例中 root 就是根元素:
以下實例中 note 是根元素:
XML 聲明
XML 聲明文件的可選部分,如果存在需要放在文檔的第一行,如下所示:
<?xml version="1.0" encoding="utf-8"?>
以上實例包含 XML 版本(<version="1.0"),甚至包含字符編碼(encoding="utf-8")。< p="">
UTF-8 也是 HTML5, CSS, JavaScript, PHP, 和 SQL 的默認編碼。
所有的 XML 元素都必須有一個關閉標簽
在 HTML 中,某些元素不必有一個關閉標簽:
<p>This is a paragraph. <br>
在 XML 中,省略關閉標簽是非法的。所有元素都必須有關閉標簽:
<p>This is a paragraph.</p> <br />
注釋:從上面的實例中,您也許已經注意到 XML 聲明沒有關閉標簽。這不是錯誤。聲明不是 XML 文檔本身的一部分,它沒有關閉標簽。
XML 標簽對大小寫敏感
XML 標簽對大小寫敏感。標簽 <Letter> 與標簽 <letter> 是不同的。
必須使用相同的大小寫來編寫打開標簽和關閉標簽:
<Message>這是錯誤的</message> <message>這是正確的</message>
注釋:打開標簽和關閉標簽通常被稱為開始標簽和結束標簽。不論您喜歡哪種術語,它們的概念都是相同的。
XML 必須正確嵌套
在 HTML 中,常會看到沒有正確嵌套的元素:
<b><i>This text is bold and italic</b></i>
在 XML 中,所有元素都必須彼此正確地嵌套:
<b><i>This text is bold and italic</i></b>
在上面的實例中,正確嵌套的意思是:由於 <i> 元素是在 <b> 元素內打開的,那么它必須在 <b> 元素內關閉。
XML 屬性值必須加引號
與 HTML 類似,XML 元素也可擁有屬性(名稱/值的對)。
在 XML 中,XML 的屬性值必須加引號。
請研究下面的兩個 XML 文檔。 第一個是錯誤的,第二個是正確的:
<note date=12/11/2007> <to>Tove</to> <from>Jani</from> </note>
<note date="12/11/2007"> <to>Tove</to> <from>Jani</from> </note>
在第一個文檔中的錯誤是,note 元素中的 date 屬性沒有加引號。
實體引用
在 XML 中,一些字符擁有特殊的意義。
如果您把字符 "<" 放在 XML 元素中,會發生錯誤,這是因為解析器會把它當作新元素的開始。
這樣會產生 XML 錯誤:
<message>if salary < 1000 then</message>
為了避免這個錯誤,請用實體引用來代替 "<" 字符:
<message>if salary < 1000 then</message>
在 XML 中,有 5 個預定義的實體引用:
< | < | less than |
> | > | greater than |
& | & | ampersand |
' | ' | apostrophe |
" | " | quotation mark |
注釋:在 XML 中,只有字符 "<" 和 "&" 確實是非法的。大於號是合法的,但是用實體引用來代替它是一個好習慣。
XML 中的注釋
在 XML 中編寫注釋的語法與 HTML 的語法很相似。
<!-- This is a comment -->
在 XML 中,空格會被保留
HTML 會把多個連續的空格字符裁減(合並)為一個:
HTML: | Hello Tove |
Output: | Hello Tove |
在 XML 中,文檔中的空格不會被刪減。
XML 以 LF 存儲換行
在 Windows 應用程序中,換行通常以一對字符來存儲:回車符(CR)和換行符(LF)。
在 Unix 和 Mac OSX 中,使用 LF 來存儲新行。
在舊的 Mac 系統中,使用 CR 來存儲新行。
XML 以 LF 存儲換行。
XML 元素
XML 文檔包含 XML 元素。
什么是 XML 元素?
XML 元素指的是從(且包括)開始標簽直到(且包括)結束標簽的部分。
一個元素可以包含:
- 其他元素
- 文本
- 屬性
- 或混合以上所有...
在上面的實例中,<bookstore> 和 <book> 都有 元素內容,因為他們包含其他元素。<book> 元素也有屬性(category="CHILDREN")。<title>、<author>、<year> 和 <price> 有文本內容,因為他們包含文本。
XML 命名規則
XML 元素必須遵循以下命名規則:
- 名稱可以包含字母、數字以及其他的字符
- 名稱不能以數字或者標點符號開始
- 名稱不能以字母 xml(或者 XML、Xml 等等)開始
- 名稱不能包含空格
可使用任何名稱,沒有保留的字詞。
最佳命名習慣
使名稱具有描述性。使用下划線的名稱也很不錯:<first_name>、<last_name>。
名稱應簡短和簡單,比如:<book_title>,而不是:<the_title_of_the_book>。
避免 "-" 字符。如果您按照這樣的方式進行命名:"first-name",一些軟件會認為您想要從 first 里邊減去 name。
避免 "." 字符。如果您按照這樣的方式進行命名:"first.name",一些軟件會認為 "name" 是對象 "first" 的屬性。
避免 ":" 字符。冒號會被轉換為命名空間來使用(稍后介紹)。
XML 文檔經常有一個對應的數據庫,其中的字段會對應 XML 文檔中的元素。有一個實用的經驗,即使用數據庫的命名規則來命名 XML 文檔中的元素。
在 XML 中,éòá 等非英語字母是完全合法的,不過需要留意,您的軟件供應商不支持這些字符時可能出現的問題。
XML 元素是可擴展的
XML 元素是可擴展,以攜帶更多的信息。
請看下面的 XML 實例:
讓我們設想一下,我們創建了一個應用程序,可將 <to>、<from> 以及 <body> 元素從 XML 文檔中提取出來,並產生以下的輸出:
MESSAGE To: Tove Don't forget me this weekend! |
想象一下,XML 文檔的作者添加的一些額外信息:
那么這個應用程序會中斷或崩潰嗎?
不會。這個應用程序仍然可以找到 XML 文檔中的 <to>、<from> 以及 <body> 元素,並產生同樣的輸出。
XML 的優勢之一,就是可以在不中斷應用程序的情況下進行擴展。
XML 屬性
XML元素具有屬性,類似 HTML。
屬性(Attribute)提供有關元素的額外信息。
XML 屬性
在 HTML 中,屬性提供有關元素的額外信息:
<a href="demo.html">
屬性通常提供不屬於數據組成部分的信息。在下面的實例中,文件類型與數據無關,但是對需要處理這個元素的軟件來說卻很重要:
XML 屬性必須加引號
屬性值必須被引號包圍,不過單引號和雙引號均可使用。比如一個人的性別,person 元素可以這樣寫:
或者這樣也可以:
如果屬性值本身包含雙引號,您可以使用單引號,就像這個實例:
或者您可以使用字符實體:
XML 元素 vs. 屬性
請看這些實例:
<firstname>Anna</firstname>
<lastname>Smith</lastname>
</person>
<sex>female</sex>
<firstname>Anna</firstname>
<lastname>Smith</lastname>
</person>
在第一個實例中,sex 是一個屬性。在第二個實例中,sex 是一個元素。這兩個實例都提供相同的信息。
沒有什么規矩可以告訴我們什么時候該使用屬性,而什么時候該使用元素。我的經驗是在 HTML 中,屬性用起來很便利,但是在 XML 中,您應該盡量避免使用屬性。如果信息感覺起來很像數據,那么請使用元素吧。
我最喜歡的方式
下面的三個 XML 文檔包含完全相同的信息:
第一個實例中使用了 date 屬性:
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
第二個實例中使用了 date 元素:
<date>10/01/2008</date>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
第三個實例中使用了擴展的 date 元素(這是我的最愛):
<date>
<day>10</day>
<month>01</month>
<year>2008</year>
</date>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
避免 XML 屬性?
因使用屬性而引起的一些問題:
- 屬性不能包含多個值(元素可以)
- 屬性不能包含樹結構(元素可以)
- 屬性不容易擴展(為未來的變化)
屬性難以閱讀和維護。請盡量使用元素來描述數據。而僅僅使用屬性來提供與數據無關的信息。
不要做這樣的蠢事(這不是 XML 應該被使用的方式):
to="Tove" from="Jani" heading="Reminder"
body="Don't forget me this weekend!">
</note>
針對元數據的 XML 屬性
有時候會向元素分配 ID 引用。這些 ID 索引可用於標識 XML 元素,它起作用的方式與 HTML 中 id 屬性是一樣的。這個實例向我們演示了這種情況:
<note id="501">
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
<note id="502">
<to>Jani</to>
<from>Tove</from>
<heading>Re: Reminder</heading>
<body>I will not</body>
</note>
</messages>
上面的 id 屬性僅僅是一個標識符,用於標識不同的便簽。它並不是便簽數據的組成部分。
在此我們極力向您傳遞的理念是:元數據(有關數據的數據)應當存儲為屬性,而數據本身應當存儲為元素。
XML 驗證
擁有正確語法的 XML 被稱為"形式良好"的 XML。
通過 DTD 驗證的XML是"合法"的 XML。
形式良好的 XML 文檔
"形式良好"的 XML 文檔擁有正確的語法。
在前面的章節描述的語法規則:
- XML 文檔必須有一個根元素
- XML元素都必須有一個關閉標簽
- XML 標簽對大小寫敏感
- XML 元素必須被正確的嵌套
- XML 屬性值必須加引號
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
驗證 XML 文檔
合法的 XML 文檔是"形式良好"的 XML 文檔,這也符合文檔類型定義(DTD)的規則:
<!DOCTYPE note SYSTEM "Note.dtd">
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
在上面的實例中,DOCTYPE 聲明是對外部 DTD 文件的引用。下面的段落展示了這個文件的內容。
XML DTD
DTD 的目的是定義 XML 文檔的結構。它使用一系列合法的元素來定義文檔結構:
[
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>
如果您想要學習 DTD,請在我們的首頁查找 DTD 教程。
XML Schema
W3C 支持一種基於 XML 的 DTD 代替者,它名為 XML Schema:
<xs:complexType>
<xs:sequence>
<xs:element name="to" type="xs:string"/>
<xs:element name="from" type="xs:string"/>
<xs:element name="heading" type="xs:string"/>
<xs:element name="body" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
http://www.runoob.com/xml/xml-tutorial.html
使用xml.dom解析xml
文件對象模型(Document Object Model,簡稱DOM),是W3C組織推薦的處理可擴展置標語言的標准編程接口。
一個 DOM 的解析器在解析一個 XML 文檔時,一次性讀取整個文檔,把文檔中所有元素保存在內存中的一個樹結構里,之后你可以利用DOM 提供的不同的函數來讀取或修改文檔的內容和結構,也可以把修改過的內容寫入xml文件。
python中用xml.dom.minidom來解析xml文件,實例如下:
#!/usr/bin/python # -*- coding: UTF-8 -*- from xml.dom.minidom import parse import xml.dom.minidom # 使用minidom解析器打開 XML 文檔 DOMTree = xml.dom.minidom.parse("movies.xml") collection = DOMTree.documentElement if collection.hasAttribute("shelf"): print "Root element : %s" % collection.getAttribute("shelf") # 在集合中獲取所有電影 movies = collection.getElementsByTagName("movie") # 打印每部電影的詳細信息 for movie in movies: print "*****Movie*****" if movie.hasAttribute("title"): print "Title: %s" % movie.getAttribute("title") type = movie.getElementsByTagName('type')[0] print "Type: %s" % type.childNodes[0].data format = movie.getElementsByTagName('format')[0] print "Format: %s" % format.childNodes[0].data rating = movie.getElementsByTagName('rating')[0] print "Rating: %s" % rating.childNodes[0].data description = movie.getElementsByTagName('description')[0] print "Description: %s" % description.childNodes[0].data
以上程序執行結果如下:
Root element : New Arrivals *****Movie***** Title: Enemy Behind Type: War, Thriller Format: DVD Rating: PG Description: Talk about a US-Japan war *****Movie***** Title: Transformers Type: Anime, Science Fiction Format: DVD Rating: R Description: A schientific fiction *****Movie***** Title: Trigun Type: Anime, Action Format: DVD Rating: PG Description: Vash the Stampede! *****Movie***** Title: Ishtar Type: Comedy Format: VHS Rating: PG Description: Viewable boredom
用 ElementTree 在 Python 中解析 XML
原文: http://eli.thegreenplace.net/2012/03/15/processing-xml-in-python-with-elementtree/
譯者: TheLover_Z
當你需要解析和處理 XML 的時候,Python 表現出了它 “batteries included” 的一面。 標准庫 中大量可用的模塊和工具足以應對 Python 或者是 XML 的新手。
幾個月前在 Python 核心開發者之間發生了一場 有趣的討論 ,他們討論了 Python 下可用的 XML 處理工具的優點,還有如何將它們最好的展示給用戶看。這篇文章是我本人的拙作,我打算講講哪些工具比較好用還有為什么它們好用,當然,這篇文章也可以當作一個如何使用的基礎教程來看。
這篇文章所使用的代碼基於 Python 2.7,你稍微改動一下就可以在 Python 3.x 上面使用了。
應該使用哪個 XML 庫?
Python 有非常非常多的工具來處理 XML。在這個部分我想對 Python 所提供的包進行一個簡單的瀏覽,並且解釋為什么 ElementTree
是你最應該用的那一個。
xml.dom.*
模塊 - 是 W3C DOM API 的實現。如果你有處理 DOM API 的需要,那么這個模塊適合你。注意:在 xml.dom 包里面有許多模塊,注意它們之間的不同。
xml.sax.*
模塊 - 是 SAX API 的實現。這個模塊犧牲了便捷性來換取速度和內存占用。SAX 是一個基於事件的 API,這就意味着它可以“在空中”(on the fly)處理龐大數量的的文檔,不用完全加載進內存(見注釋1)。
xml.parser.expat
- 是一個直接的,低級一點的基於 C 的 expat
的語法分析器(見注釋2)。 expat
接口基於事件反饋,有點像 SAX 但又不太像,因為它的接口並不是完全規范於 expat
庫的。
最后,我們來看看 xml.etree.ElementTree
(以下簡稱 ET)。它提供了輕量級的 Python 式的 API ,它由一個 C 實現來提供。相對於 DOM 來說,ET 快了很多(見注釋3)而且有很多令人愉悅的 API 可以使用。相對於 SAX 來說,ET 也有 ET.iterparse
提供了 “在空中” 的處理方式,沒有必要加載整個文檔到內存。ET 的性能的平均值和 SAX 差不多,但是 API 的效率更高一點而且使用起來很方便。我一會兒會給你們看演示。
我的建議 是盡可能的使用 ET 來處理 XML ,除非你有什么非常特別的需要。
ElementTree - 一個 API ,兩種實現
ElementTree
生來就是為了處理 XML ,它在 Python 標准庫中有兩種實現。一種是純 Python 實現例如 xml.etree.ElementTree
,另外一種是速度快一點的 xml.etree.cElementTree
。你要記住: 盡量使用 C 語言實現的那種,因為它速度更快,而且消耗的內存更少。如果你的電腦上沒有 _elementtree
(見注釋4) 那么你需要這樣做:
try: import xml.etree.cElementTree as ET except ImportError: import xml.etree.ElementTree as ET
這是一個讓 Python 不同的庫使用相同 API 的一個比較常用的辦法。還是那句話,你的編譯環境和別人的很可能不一樣,所以這樣做可以防止一些莫名其妙的小問題。注意:從 Python 3.3 開始,你沒有必要這么做了,因為 ElementTree
模塊會自動尋找可用的 C 庫來加快速度。所以只需要 import xml.etree.ElementTree
就可以了。但是在 3.3 正式推出之前,你最好還是使用我上面提供的那段代碼。
將 XML 解析為樹的形式
我們來講點基礎的。XML 是一種分級的數據形式,所以最自然的表示方法是將它表示為一棵樹。ET 有兩個對象來實現這個目的 - ElementTree
將整個 XML 解析為一棵樹, Element
將單個結點解析為樹。如果是整個文檔級別的操作(比如說讀,寫,找到一些有趣的元素)通常用 ElementTree
。單個 XML 元素和它的子元素通常用 Element
。下面的例子能說明我剛才啰嗦的一大堆。(見注釋5)
我們用這個 XML 文件來做例子:
<?xml version="1.0"?>
<doc>
<branch name="testing" hash="1cdf045c">
text,source
</branch>
<branch name="release01" hash="f200013e">
<sub-branch name="subrelease01">
xml,sgml
</sub-branch>
</branch>
<branch name="invalid">
</branch>
</doc>
讓我們加載並且解析這個 XML :
>>> import xml.etree.cElementTree as ET >>> tree = ET.ElementTree(file='doc1.xml')
然后抓根結點元素:
>>> tree.getroot() <Element 'doc' at 0x11eb780>
和預期一樣,root 是一個 Element
元素。我們可以來看看:
>>> root = tree.getroot() >>> root.tag, root.attrib ('doc', {})
看吧,根元素沒有任何狀態(見注釋6)。就像任何 Element
一樣,它可以找到自己的子結點:
>>> for child_of_root in root: ... print child_of_root.tag, child_of_root.attrib ... branch {'hash': '1cdf045c', 'name': 'testing'} branch {'hash': 'f200013e', 'name': 'release01'} branch {'name': 'invalid'}
我們也可以進入一個指定的子結點:
>>> root[0].tag, root[0].text ('branch', '\n text,source\n ')
找到我們感興趣的元素
從上面的例子我們可以輕而易舉的看到,我們可以用一個簡單的遞歸獲取 XML 中的任何元素。然而,因為這個操作比較普遍,ET 提供了一些有用的工具來簡化操作.
Element
對象有一個 iter
方法可以對子結點進行深度優先遍歷。 ElementTree
對象也有 iter
方法來提供便利。
>>> for elem in tree.iter(): ... print elem.tag, elem.attrib ... doc {} branch {'hash': '1cdf045c', 'name': 'testing'} branch {'hash': 'f200013e', 'name': 'release01'} sub-branch {'name': 'subrelease01'} branch {'name': 'invalid'}
遍歷所有的元素,然后檢驗有沒有你想要的。ET 可以讓這個過程更便捷。 iter
方法接受一個標簽名字,然后只遍歷那些有指定標簽的元素:
>>> for elem in tree.iter(tag='branch'): ... print elem.tag, elem.attrib ... branch {'hash': '1cdf045c', 'name': 'testing'} branch {'hash': 'f200013e', 'name': 'release01'} branch {'name': 'invalid'}
來自 XPath 的幫助
為了尋找我們感興趣的元素,一個更加有效的辦法是使用 XPath 支持。 Element
有一些關於尋找的方法可以接受 XPath 作為參數。 find
返回第一個匹配的子元素, findall
以列表的形式返回所有匹配的子元素, iterfind
為所有匹配項提供迭代器。這些方法在 ElementTree
里面也有。
給出一個例子:
>>> for elem in tree.iterfind('branch/sub-branch'): ... print elem.tag, elem.attrib ... sub-branch {'name': 'subrelease01'}
這個例子在 branch
下面找到所有標簽為 sub-branch
的元素。然后給出如何找到所有的 branch
元素,用一個指定 name
的狀態即可:
>>> for elem in tree.iterfind('branch[@name="release01"]'): ... print elem.tag, elem.attrib ... branch {'hash': 'f200013e', 'name': 'release01'}
想要深入學習 XPath 的話,請看 這里 。
建立 XML 文檔
ET 提供了建立 XML 文檔和寫入文件的便捷方式。 ElementTree
對象提供了 write
方法。
現在,這兒有兩個常用的寫 XML 文檔的腳本。
修改文檔可以使用 Element
對象的方法:
>>> root = tree.getroot() >>> del root[2] >>> root[0].set('foo', 'bar') >>> for subelem in root: ... print subelem.tag, subelem.attrib ... branch {'foo': 'bar', 'hash': '1cdf045c', 'name': 'testing'} branch {'hash': 'f200013e', 'name': 'release01'}
我們在這里刪除了根元素的第三個子結點,然后為第一個子結點增加新狀態。然后這個樹可以寫回到文件中。
>>> import sys >>> tree.write(sys.stdout) # ET.dump can also serve this purpose <doc> <branch foo="bar" hash="1cdf045c" name="testing"> text,source </branch> <branch hash="f200013e" name="release01"> <sub-branch name="subrelease01"> xml,sgml </sub-branch> </branch> </doc>
注意狀態的順序和原文檔的順序不太一樣。這是因為 ET 講狀態保存在無序的字典中。語義上來說,XML 並不關心順序。
建立一個全新的元素也很容易。ET 模塊提供了 SubElement
函數來簡化過程:
>>> a = ET.Element('elem') >>> c = ET.SubElement(a, 'child1') >>> c.text = "some text" >>> d = ET.SubElement(a, 'child2') >>> b = ET.Element('elem_b') >>> root = ET.Element('root') >>> root.extend((a, b)) >>> tree = ET.ElementTree(root) >>> tree.write(sys.stdout) <root><elem><child1>some text</child1><child2 /></elem><elem_b /></root>
使用 iterparse 來處理 XML 流
就像我在文章一開頭提到的那樣,XML 文檔通常比較大,所以將它們全部讀入內存的庫可能會有點兒小問題。這也是為什么我建議使用 SAX API 來替代 DOM 。
我們剛講過如何使用 ET 來將 XML 讀入內存並且處理。但它就不會碰到和 DOM 一樣的內存問題么?當然會。這也是為什么這個包提供一個特殊的工具,用來處理大型文檔,並且解決了內存問題,這個工具叫 iterparse
。
我給大家演示一個 iterparse
如何使用的例子。我用 自動生成 拿到了一個 XML 文檔來進行說明。這只是開頭的一小部分:
<?xml version="1.0" standalone="yes"?>
<site>
<regions>
<africa>
<item id="item0">
<location>United States</location> <!-- Counting locations -->
<quantity>1</quantity>
<name>duteous nine eighteen </name>
<payment>Creditcard</payment>
<description>
<parlist>
[...]
我已經用注釋標出了我要處理的元素,我們用一個簡單的腳本來計數有多少 location
元素並且文本內容為“Zimbabwe”。這是用 ET.parse
的一個標准的寫法:
tree = ET.parse(sys.argv[2]) count = 0 for elem in tree.iter(tag='location'): if elem.text == 'Zimbabwe': count += 1 print count
所有 XML 樹中的元素都會被檢驗。當處理一個大約 100MB 的 XML 文件時,占用的內存大約是 560MB ,耗時 2.9 秒。
注意:我們並不需要在內存中加載整顆樹。它檢測我們需要的帶特定值的 location
元素。其他元素被丟棄。這是 iterparse
的來源:
count = 0 for event, elem in ET.iterparse(sys.argv[2]): if event == 'end': if elem.tag == 'location' and elem.text == 'Zimbabwe': count += 1 elem.clear() # discard the element print count
這個循環遍歷 iterparse
事件,檢測“閉合的”(end)事件並且尋找 location
標簽和指定的值。在這里 elem.clear()
是關鍵 - iterparse
仍然建立一棵樹,只不過不需要全部加載進內存,這樣做可以有效的利用內存空間(見注釋7)。
處理同樣的文件,這個腳本占用內存只需要僅僅的 7MB ,耗時 2.5 秒。速度的提升歸功於生成樹的時候只遍歷一次。相比較來說, parse
方法首先建立了整個樹,然后再次遍歷來尋找我們需要的元素(所以慢了一點)。
結論
在 Python 眾多處理 XML 的模塊中, ElementTree
真是屌爆了。它將輕量,符合 Python 哲學的 API ,出色的性能完美的結合在了一起。所以說如果要處理 XML ,果斷地使用它吧!
這篇文章簡略地談了談 ET 。我希望這篇拙作可以拋磚引玉。
注釋
注釋1:和 DOM 不一樣,DOM 將整個 XML 加載進內存並且允許隨機訪問任何深度地元素。
注釋2: expat 是一個開源的用於處理 XML 的 C 語言庫。Python 將它融合進自身。
注釋3:Fredrik Lundh,是 ElementTree 的原作者,他提到了一些 基准 。
注釋4:當我提到 _elementtree
的時候,我意思是 C 語言的 cElementTree._elementtree
擴展模塊。
注釋5:確定你手邊有 模塊手冊 然后可以隨時查閱我提到的方法和函數。
注釋6: 狀態 是一個意義太多的術語。Python 對象有狀態,XML 元素也有狀態。希望我能將它們表達的更清楚一點。
注釋7:准確來說,樹的根元素仍然存活。在某些情況下根結點非常大,你也可以丟棄它,但那需要多一點點代碼。
Python基於DOM的XML編程接口
Python自帶支持XML DOM的模塊:xml.dom和xml.dom.minidom。
http://blog.sina.com.cn/s/blog_da4487c40102v3jx.html
使用minidom來處理XML的示例(Python 學習)(轉載)
一.XML的讀取.
在 NewEdit 中有代碼片段的功能,代碼片段分為片段的分類和片段的內容。在缺省情況下都是用XML格式保存的。下面我講述一下,如何使用minidom來讀取和保存XML文件。
下面是片段分類的一個示例文件--catalog.xml
<?xml version="1.0" encoding="utf-8"?>
<catalog>
<maxid>4</maxid>
<item id="1">
<caption>Python</caption>
<item id="4">
<caption>測試</caption>
</item>
</item>
<item id="2">
<caption>Zope</caption>
</item>
</catalog>
分類是樹狀結構,顯示出來可能為:
先簡單介紹一下XML的知識,如果你已經知道了可以跳過去。
1. XML文檔的編碼
此XML文檔的編碼為utf-8,因此你看到的“測試”其實是UTF-8編碼。在XML文檔的處理中都是使用UTF-8編碼進行的,因此,如果你不寫明encoding的話,都是認為文件是UTF-8編碼的。在Python中,好象只支持幾種編碼,象我們常用的GB2312碼就不支持,因此建議大家在處理XML時使用UTF-8編碼。
2. XML文檔的結構
XML文檔有XML頭信息和XML信息體。頭信息如:
<?xml version="1.0" encoding="utf-8"?>
它表明了此XML文檔所用的版本,編碼方式。有些復雜的還有一些文檔類型的定義(DOCTYPE),用於定義此XML文檔所用的DTD或Schema和一些實體的定義。這里並沒有用到,而且我也不是專家,就不再細說了。
XML信息體是由樹狀元素組成。每個XML文檔都有一個文檔元素,也就是樹的根元素,所有其它的元素和內容都包含在根元素中。
3. DOM
DOM是Document Object Model的簡稱,它是以對象樹來表示一個XML文檔的方法,使用它的好處就是你可以非常靈活的在對象中進行遍歷。
4. 元素和結點
元素就是標記,它是成對出現的。XML文檔就是由元素組成的,但元素與元素之間可以有文本,元素的內容也是文本。在minidom中有許多的結點,元素也屬於結點的一種,它不是葉子結點,即它存在子結點;還存在一些葉子結點,如文本結點,它下面不再有子結點。
象catalog.xml中,文檔元素是catalog,它下面有兩種元素:maxid和item。maxid用來表示當前最大的item的id值。每一個item都有一個id屬性,id屬性是唯一的,在 NewEdit 中用來生成每個分類所對應的代碼片段的XML文檔名,因此不能重復,而且它是一個遞增的值。item元素有一個caption子元素,用來表示此分類項的名稱,它還可以包含item元素。這樣,就定義了一個樹狀XML結構,下面讓我們看一看如果把它們讀出來。
一、得到dom對象
>>> import xml.dom.minidom
>>> dom = xml.dom.minidom.parse('d:/catalog.xml')
這樣我們得到了一個dom對象,它的第一個元素應該是catalog。
二、得到文檔元素對象
>>> root = dom.documentElement
這樣我們得到了根元素(catalog)。
三、結點屬性
每一個結點都有它的nodeName,nodeValue,nodeType屬性。nodeName為結點名字。
>>> root.nodeName
u'catalog'
nodeValue是結點的值,只對文本結點有效。nodeType是結點的類型,現在有以下幾種:
'ATTRIBUTE_NODE'
'CDATA_SECTION_NODE'
'COMMENT_NODE'
'DOCUMENT_FRAGMENT_NODE'
'DOCUMENT_NODE'
'DOCUMENT_TYPE_NODE'
'ELEMENT_NODE'
'ENTITY_NODE'
'ENTITY_REFERENCE_NODE'
'NOTATION_NODE'
'PROCESSING_INSTRUCTION_NODE'
'TEXT_NODE'
這些結點通過名字很好理解。catalog是ELEMENT_NODE類型。
>>> root.nodeType
1
>>> root.ELEMENT_NODE
1
四、子元素、子結點的訪問
訪問子元素、子結點的方法很多,對於知道元素名字的子元素,可以使用getElementsByTagName方法,如讀取maxid子元素:
>>> root.getElementsByTagName('maxid')
[<DOM Element: maxid at 0xb6d0a8>]
這樣返回一個列表,由於我們的例子中maxid只有一項,因此列表也只有一項。
如果想得到某個元素下的所有子結點(包括元素),可以使用childNodes屬性:
>>> root.childNodes
[<DOM Text node "\n ">, <DOM Element: maxid at 0xb6d0a8>, <DOM Text node "\n ">, <DOM Element: item at 0xb6d918>, <DOM Text node "\n ">, <DOM Element: item at 0xb6de40>, <DOM Text node "\n ">, <DOM Element: item at 0xb6dfa8>, <DOM Text node "\n">]
可以看出所有兩個標記間的內容都被視為文本結點。象每行后面的回車,都被看到文本結點。從上面的結果我們可以看出每個結點的類型,本例中有文本結點和元素結點;結點的名字(元素結點);結點的值(文本結點)。每個結點都是一個對象,不同的結點對象有不同的屬性和方法,更詳細的要參見文檔。由於本例比較簡單,只涉及文本結點和元素結點。
getElementsByTagName可以搜索當前元素的所有子元素,包括所有層次的子元素。childNodes只保存了當前元素的第一層子結點。
這樣我們可以遍歷childNodes來訪問每一個結點,判斷它的nodeType來得到不同的內容。如,打印出所有元素的名字:
>>> for node in root.childNodes:
if node.nodeType == node.ELEMENT_NODE:
print node.nodeName
maxid
item
item
對於文本結點,想得到它的文本內容可以使用: .data屬性。
對於簡單的元素,如:<caption>Python</caption>,我們可以編寫這樣一個函數來得到它的內容(這里為Python)。
def getTagText(root, tag):
node = root.getElementsByTagName(tag)[0]
rc = ""
for node in node.childNodes:
if node.nodeType in ( node.TEXT_NODE, node.CDATA_SECTION_NODE):
rc = rc + node.data
return rc
這個函數只處理找到的第一個符合的子元素。它會將符合的第一個子元素中的所有文本結點拼在一起。當nodeType為文本類結點時,node.data為文本的內容。如果我們考查一下元素caption,我們可能看到:
[<DOM Text node "Python">]
說明caption元素只有一個文本結點。
如果一個元素有屬性,那么可以使用getAttribute方法,如:
>>> itemlist = root.getElementsByTagName('item')
>>> item = itemlist[0]
>>> item.getAttribute('id')
u'1'
這樣就得到了第一個item元素的屬性值。
下面讓我們簡單地小結一下如何使用minidom來讀取XML中的信息
1. 導入xml.dom.minidom模塊,生成dom對象
2. 得到文檔對象(根對象)
3. 通過getElementsByTagName()方法和childNodes屬性(還有其它一些方法和屬性)找到要處理的元素
4. 取得元素下文本結點的內容
二.寫入.
下面我來演示一下如何從無到有生成象catalog.xml一樣的XML文件。
一、生成dom對象
>>> import xml.dom.minidom
>>> impl = xml.dom.minidom.getDOMImplementation()
>>> dom = impl.createDocument(None, 'catalog', None)
這樣就生成了一個空的dom對象。其中catalog為文檔元素名,即根元素名。
二、顯示生成的XML內容
每一個dom結點對象(包括dom對象本身)都有輸出XML內容的方法,如:toxml(), toprettyxml()
toxml()輸出緊湊格式的XML文本,如:
<catalog><item>test</item><item>test</item></catalog>
toprettyxml()輸出美化后的XML文本,如:
<catalog>
<item>
test
</item>
<item>
test
</item>
</catalog>
可以看出,它是將每個結點后面都加入了回車符,並且自動處理縮近。但對於每一個元素,如果元素只有文本內容,則我希望元素的tag與文本是在一起的,如:
<item>test</item>
而不想是分開的格式,但minidom本身是不支持這樣的處理。關於如何實現形如:
<catalog>
<item>test</item>
<item>test</item>
</catalog>
這樣的XML格式,后面我們再說。
三、生成各種結點對象
dom對象擁有各種生成結點的方法,下面列出文本結點,CDATA結點和元素結點的生成過程。
1. 文本結點的生成
>>> text=dom.createTextNode('test')
test
要注意的是,在生成結點時,minidom並不對文本字符進行檢查,象文本中如果出現了'<','&'之類的字符,應該轉換為相應的實體符號'<','&'才可以,這里沒有做這個處理。
2. CDATA結點的生成
>>> data = dom.createCDATASection('aaaaaa\nbbbbbb')
>>> data.toxml()
'<![CDATA[aaaaaa\nbbbbbb]]>'
CDATA是用於包括大塊文本,同時可以不用轉換'<','&'字符的標記,它是用<![CDATA[文本]]>來包括的。但文本中不可以有"]]>"這樣的串存在。生成結點時minidom不作這些檢查,只有當你輸出時才有可能發現有錯。
3. 元素結點的生成
>>> item = dom.createElement('caption')
>>> item.toxml()
'<caption/>'
對於象元素這樣的結點,生成的元素結點其實是一個空元素,即不包含任何文本,如果要包含文本或其它的元素,我們需要使用appendChild()或insertBefore()之類的方法將子結點加就到元素結點中。如將上面生成的text結點加入到caption元素結點中:
>>> item.appendChild(text)
<DOM Text node "test">
>>> item.toxml()
'<caption>test</caption>'
使用元素對象的setAttribute()方法可以向元素中加入屬性,如:
>>> item.setAttribute('id', 'idvalue')
>>> item.toxml()
'<caption id="idvalue">test</caption>'
四、生成dom對象樹
我們有了dom對象,又知道了如何生成各種結點,包括葉子結點(不包含其它結點的結點,如文本結點)和非葉子結點(包含其它結點的結點,如元素結點)的生成,然后就需要利用結點對象本身的appendChild()或insertBefore()方法將各個結點根據在樹中的位置連起來,串成一棵樹。最后要串到文檔結點上,即根結點上。如一個完整的示例為:
>>> import xml.dom.minidom
>>> impl = xml.dom.minidom.getDOMImplementation()
>>> dom = impl.createDocument(None, 'catalog', None)
>>> root = dom.documentElement
>>> item = dom.createElement('item')
>>> text = dom.createTextNode('test')
>>> item.appendChild(text)
<DOM Text node "test">
>>> root.appendChild(item)
<DOM Element: item at 0xb9cf80>
>>> print root.toxml()
<catalog><item>test</item></catalog>
五、簡單生成元素結點的函數
下面是我寫的一個小函數,用於簡單的生成類似於:
<caption>test</caption>
或形如:
<item><![CDATA[test]]></item>
的元素結點
1 def makeEasyTag(dom, tagname, value, type='text'):
2 tag = dom.createElement(tagname)
3 if value.find(']]>') > -1:
4 type = 'text'
5 if type == 'text':
6 value = value.replace('&', '&')
7 value = value.replace('<', '<')
8 text = dom.createTextNode(value)
9 elif type == 'cdata':
10 text = dom.createCDATASection(value)
11 tag.appendChild(text)
12 return tag
參數說明:
- dom為dom對象
- tagname為要生成元素的名字,如'item'
- value為其文本內容,可以為多行
- type為文本結點的格式,'text'為一般Text結點,'cdata'為CDATA結點
函數處理說明:
- 首先創建元素結點
- 查找文本內容是否有']]>',如果找到,則此文本結點只可以是Text結點
- 如果結點類型為'text',則對文本內容中的'<'替換為'<','&'替換為'&',再生成文本結點
- 如果結點類型為'cdata',則生成CDATA結點
- 將生成的文本結點追加到元素結點上
因此這個小函數可以自動地處理字符轉化及避免CDATA結點中出現']]>'串。
上面生成'item'結點的語句可以改為:
>>> item = makeEasyTag(dom, 'item', 'test')
>>> item.toxml()
'<item>test</item>'
六、寫入到XML文件中
dom對象樹已經生成好了,我們可以調用dom的writexml()方法來將內容寫入文件中。writexml()方法語法格式為:
writexml(writer, indent, addindent, newl, encoding)
- writer是文件對象
- indent是每個tag前填充的字符,如:' ',則表示每個tag前有兩個空格
- addindent是每個子結點的縮近字符
- newl是每個tag后填充的字符,如:'\n',則表示每個tag后面有一個回車
- encoding是生成的XML信息頭中的encoding屬性值,在輸出時minidom並不真正進行編碼的處理,如果你保存的文本內容中有漢字,則需要自已進行編碼轉換。
writexml方法是除了writer參數必須要有外,其余可以省略。下面給出一個文本內容有漢字的示例:
1 >>> import xml.dom.minidom
2 >>> impl = xml.dom.minidom.getDOMImplementation()
3 >>> dom = impl.createDocument(None, 'catalog', None)
4 >>> root = dom.documentElement
5 >>> text = unicode('漢字示例', 'cp936')
6 >>> item = makeEasyTag(dom, 'item', text)
7 >>> root.appendChild(item)
8 <DOM Element: item at 0xb9ceb8>
9 >>> root.toxml()
10 u'<catalog><item>\u6c49\u5b57\u793a\u4f8b</item></catalog>'
11 >>> f=file('d:/test.xml', 'w')
12 >>> import codecs
13 >>> writer = codecs.lookup('utf-8')[3](f)
14 >>> dom.writexml(writer, encoding='utf-8')
15 >>> writer.close()
5行 因為XML處理時內部使用Unicode編碼,因此象漢字首先要轉成Unicode,如果你不做這一步minicode並不檢查,並且保存時可能不會出錯。但讀取時可能會出錯。
12-13行 生成UTF-8編碼的寫入流對象,這樣在保存時會自動將Unicode轉換成UTF-8編碼。
這樣寫XML文件就完成了。
三.美化.
對於dom對象的writexml()方法,雖然可以控制一些格式上的輸出,但結果並不讓人滿意。比如我想實現:
<catalog>
<item>test</item>
<item>test</item>
</catalog>
而不是:
<catalog>
<item>
test
</item>
<item>
test
</item>
</catalog>
如果是象下面的輸出結果我無法區分原來文本中是否帶有空白,而上一種結果則不存在這一問題。好在我在wxPython自帶的XML資源編輯器(xred)發現了美化的代碼。代碼如下:
1 def Indent(dom, node, indent = 0):
2 # Copy child list because it will change soon
3 children = node.childNodes[:]
4 # Main node doesn't need to be indented
5 if indent:
6 text = dom.createTextNode('\n' + '\t' * indent)
7 node.parentNode.insertBefore(text, node)
8 if children:
9 # Append newline after last child, except for text nodes
10 if children[-1].nodeType == node.ELEMENT_NODE:
11 text = dom.createTextNode('\n' + '\t' * indent)
12 node.appendChild(text)
13 # Indent children which are elements
14 for n in children:
15 if n.nodeType == node.ELEMENT_NODE:
16 Indent(dom, n, indent + 1)
參數說明:
dom為dom對象
node為要處理的元素結點
indent指明縮近的層數
函數說明:
Indent是一個遞歸函數,當一個結點有子元素時進行遞歸處理。主要是解決子元素的換行和縮近的處理。這里縮近是寫死的,每一級縮近使用一個制表符。如果你願意可以改為你想要的內容。就是把函數中的'\t'換替一下。或干脆寫成一個全局變量,或參數以后改起來可能要容易的多。不過在 NewEdit 中,這樣的處理足夠了,就沒有做這些工作。
Indent基本的想法就是遞歸遍歷所有子結點,在所有需要加入回車和縮近的地方插入相應的文本結點。這樣再使用writexml()輸出時就是縮近好了的。具體程序不再細說,直接用就行了。
但這里要注意的是:
Indent()要修改原dom對象,因此在調用它之前最好先復制一個臨時dom對象,使用完畢后再清除這個臨時dom對象即可。下面是詳細的調用過程:
1 domcopy = dom.cloneNode(True)
2 Indent(domcopy, domcopy.documentElement)
3 f = file(xmlfile, 'wb')
4 writer = codecs.lookup('utf-8')[3](f)
5 domcopy.writexml(writer, encoding = 'utf-8')
6 domcopy.unlink()
1行 克隆一個dom對象
2行 進行縮近處理
3-4行 進行UTF-8編碼處理
5行 生成XML文件
6行 清除dom對象的內容
Python 使用 ElementTree 處理 XML
一、引用方法
ElementTree 所在文件保存在 Lib/xml/etree/ElementTree.py,所以我們通過下面的代碼引用它,之后就可以使用 ET. 來訪問 ElementTree 中的函數。
1 |
import xml.etree.ElementTree as ET |
二、一個 XML 例子
下面所有的操作都將下面這段 XML 為例,我們將它保存為sample.xml。
1 |
<?xml version="1.0"?> |
先對 XML 的格式做一些說明:
- Tag: 使用 < 和 > 包圍的部分,如 成為 start-tag,是 end-tags;
- Element:被 Tag 包圍的部分,如68,可以認為是一個節點,它可以有子節點;
- Attribute:在 Tag 中可能存在的 name/value 對,如 中的 name=”Liechtenstein”,一般表示屬性。
三、解析 XML
讀入 XML 數據
首先讀入 XML,有兩種途徑,從文件讀入和從字符串讀入。
從文件讀入:
1 |
import xml.etree.ElementTree as ET |
從字符串讀入:
1 |
root = ET.fromstring(sample_as_string) |
tree 和 root 分布是 ElementTree 中兩個很重要的類的對象:
- ElementTree
- Element
查看 Tag 和 Attribute
這時得到的 root 是一個指向Element 對象,我們可以通過查看 root 的 tag 和 attrib 來驗證這一點:
1 |
|
上面的代碼說明了查看一個 Element 的 Tag 和 Attribute 的方法,Tag 是一個 字符串 ,而 Attribute 得到的是一個 字典。
另外,還可以使用
- Element.get(AttributeName)
來代替 Element.attrib[AttributeName]來訪問。
查看孩子
root.attrib 返回的是一個空字典,如果看 root 的孩子,可以得到非空的 attrib 字典。
1、使用 for…in…訪問
1 |
for child in root: |
得到
country {‘name’: ‘Liechtenstein’}
country {‘name’: ‘Singapore’}
country {‘name’: ‘Panama’}
2、使用下標訪問
如:
1 |
|
3、使用 Tag 名稱訪問
下標訪問的方法雖然簡單,但是在未知 XML 具體結構的時候並不適用,通過 Tag 名稱訪問的方法更具有普適性。這里用到 Element 類的幾個函數,分別是
- Element.iter()
- Element.findall()
- Element.find()
這兩個函數使用的場景有所差異:
Element.iter()用來尋找 所有 符合要求的 Tag,注意,這里查找的范圍 是所有孩子和孩子的孩子 and so on。如果查看所有的 year,可以使用下面的代碼:
1 |
for neighbor in root.iter('year'): |
返回
2008
2011
2011
Element.findall()只查找 直接的孩子 ,返回所有符合要求的 Tag 的 Element,而Element.find() 只返回符合要求的第一個 Element。如果查看 Singapore 的 year 的值,可以使用下面的代碼:
1 |
for country in root.findall('country'): |
1 |
for country in root.findall('country'): |
查看 Element 的值
我們可以直接用 Element.text 來得到這個 Element 的值。
四、修改 XML
前面已經介紹了如何獲取一個 Element 的對象,以及查看它的 Tag、Attribute、值和它的孩子。下面介紹如何修改一個 Element 並對 XML 文件進行保存
修改 Element
修改 Element 可以直接訪問 Element.text。
修改 Element 的 Attribute,也可以用來新增 Attribute:
Element.set(‘AttributeName’,’AttributeValue’)
新增孩子節點:
Element.append(childElement)
刪除孩子節點:
Element.remove(childElement)
保存 XML
我們從文件解析的時候,我們用了一個 ElementTree 的對象 tree,在完成修改之后,還用 tree 來保存 XML 文件。
1 |
tree.write('output.xml') |
構建 XML
ElementTree 提供了兩個靜態函數(直接用類名訪問,這里我們用的是 ET)可以很方便的構建一個 XML,如:
1 |
root = ET.Element('data') |
就可以得到
12008
五、XPath 支持
XPath 表達式用來在 XML 中定位 Element,下面給一個例子來說明:
1 |
import xml.etree.ElementTree as ET |
參考
ElementTree 主頁
ElementTree 的函數與類介紹
【ElementTree解析】
兩種實現
ElementTree生來就是為了處理XML ,它在python標准庫中有兩種實現。
一種是純Python實現,例如: xml.etree.ElementTree
另外一種是速度快一點的: xml.etree.cElementTree
盡量使用C語言實現的那種,因為它速度更快,而且消耗的內存更少! 在程序中可以這樣寫:
- try:
- import xml.etree.cElementTree as ET
- except ImportError:
- import xml.etree.ElementTree as ET
常用方法
- # 當要獲取屬性值時,用attrib方法。
- # 當要獲取節點值時,用text方法。
- # 當要獲取節點名時,用tag方法。
示例XML
- <?xml version="1.0" encoding="utf-8"?>
- <info>
- <intro>Book message</intro>
- <list id='001'>
- <head>bookone</head>
- <name>python check</name>
- <number>001</number>
- <page>200</page>
- </list>
- <list id='002'>
- <head>booktwo</head>
- <name>python learn</name>
- <number>002</number>
- <page>300</page>
- </list>
- </info>
###########
## 加載XML
###########
方法一:加載文件
- root = ET.parse('book.xml')
方法二:加載字符串
- root = ET.fromstring(xmltext)
###########
## 獲取節點
###########
方法一:獲得指定節點->getiterator()方法
- book_node = root.getiterator('list')
方法二:獲得指定節點->findall()方法
- book_node = root.findall('list')
方法三:獲得指定節點->find()方法
- book_node = root.find('list')
方法四:獲得兒子節點->getchildren()
- for node in book_node:
- book_node_child = node.getchildren()[0]
- print book_node_child.tag, '=> ', book_node_child.text
###########
## 例子01
###########
- # coding=utf-8
- try: # 導入模塊
- import xml.etree.cElementTree as ET
- except ImportError:
- import xml.etree.ElementTree as ET
- root = ET.parse('book.xml') # 分析XML文件
- books = root.findall('/list') # 查找所有根目錄下的list的子節點
- for book_list in books: # 對查找后的結果遍歷
- print "=" * 30 # 輸出格式
- for book in book_list: # 對每個子節點再進行遍歷,找出里面你的屬性及值
- if book.attrib.has_key('id'): # 一句id來做條件判斷
- print "id:", book.attrib['id'] # 根據id打印出屬性值
- print book.tag + '=> ' + book.text # 輸出標簽及文本內容
- print "=" * 30
輸出結果:
- ==============================
- head=> bookone
- name=> python check
- number=> 001
- page=> 200
- ==============================
- head=> booktwo
- name=> python learn
- number=> 002
- page=> 300
- ==============================
Python 標准庫之 xml.etree.ElementTree
簡介
Element類型是一種靈活的容器對象,用於在內存中存儲結構化數據。
[注意]xml.etree.ElementTree模塊在應對惡意結構數據時顯得並不安全。
每個element對象都具有以下屬性:
1. tag:string對象,表示數據代表的種類。
2. attrib:dictionary對象,表示附有的屬性。
3. text:string對象,表示element的內容。
4. tail:string對象,表示element閉合之后的尾跡。
5. 若干子元素(child elements)。
<tag attrib1=1>text</tag>tail
1 2 3 4
創建元素的方法有Element或者SubElement(),前者稱作元素的構建函數(constructor),用以構建任一獨存的元素;后者稱作元素的制造函數(factory function),用以制造某一元素的子元素。
有了一串元素之后,使用ElementTree類來將其打包,把一串元素轉換為xml文件或者從xml文件中解析出來。
若想加快速度,可以使用C語言編譯的API xml.etree.cElementTree。
<?xml version="1.0"?> <data> <country name="Liechtenstein"> <rank>1</rank> <year>2008</year> <gdppc>141100</gdppc> <neighbor name="Austria" direction="E"/> <neighbor name="Switzerland" direction="W"/> </country> <country name="Singapore"> <rank>4</rank> <year>2011</year> <gdppc>59900</gdppc> <neighbor name="Malaysia" direction="N"/> </country> <country name="Panama"> <rank>68</rank> <year>2011</year> <gdppc>13600</gdppc> <neighbor name="Costa Rica" direction="W"/> <neighbor name="Colombia" direction="E"/> </country> </data>
XML操作
-
讀取
#從變量讀取,參數為XML段,返回的是一個根Element對象 root = ET.fromstring(country_data_as_string) #從xml文件中讀取,用getroot獲取根節點,根節點也是Element對象 tree = ET.parse('file.xml') root = tree.getroot()
-
訪問
- 訪問Element對象的標簽、屬性和值
tag = element.tag attrib = element.attrib value = element.text
-
- 訪問子節點
#打印根節點的標簽和屬性,獲取 for child in root: print(child.tag, child.attrib)
-
查找操作
- Element元素迭代子元素:Element.iter("tag"),可以羅列該節點所包含的所有其他節點(element對象)
#打印根節點中所有的neighbor對象的name屬性 for neighbor in root.iter('neighbor'): print(neighbor.attrib['name'])
-
- Element.findall("tag"):查找當前元素為“tag”的直接子元素
#findall只能用來查找直接子元素,不能用來查找rank,neighbor等element for country in root.findall('country'): rank = country.find('rank').text name = country.find('rank').text neig = country.find('neighbor').attrib print(rank, name,neig)
-
- Element.find("tag"):查找為tag的第一個直接子元素
#返回第一個tag為country的element,如沒有,返回None firstCountry = root.find("country") print(firstCountry)
-
創建xml文件
__author__ = 'xua' import xml.etree.ElementTree as ET #創建根節點 a = ET.Element("root") #創建子節點,並添加屬性 b = ET.SubElement(a,"sub1") b.attrib = {"name":"name attribute"} #創建子節點,並添加數據 c = ET.SubElement(a,"sub2") c.text = "test" #創建elementtree對象,寫文件 tree = ET.ElementTree(a) tree.write("test.xml")
創建的新文件內容為:<root><sub1 name="name attribute" /><sub2>test</sub2></root>
-
修改XML文件
- ElementTree.write("xmlfile"):更新xml文件
- Element.append():為當前element對象添加子元素(element)
- Element.set(key,value):為當前element的key屬性設置value值
- Element.remove(element):刪除為element的節點
#讀取待修改文件 updateTree = ET.parse("test.xml") root = updateTree.getroot() #創建新節點並添加為root的子節點 newEle = ET.Element("NewElement") newEle.attrib = {"name":"NewElement","age":"20"} newEle.text = "This is a new element" root.append(newEle) #修改sub1的name屬性 sub1 = root.find("sub1") sub1.set("name","New Name") #修改sub2的數據值 sub2 = root.find("sub2") sub2.text = "New Value" #寫回原文件 updateTree.write("test.xml")
更新完的文件為:<root><sub1 name="New Name" /><sub2>New Value</sub2><NewElement age="20" name="NewElement">This is a new element</NewElement></root>
總結
XML的操作比較常見,當然也有很多第三方的庫可以使用,所需要做的操作無非就是常用的讀寫xml文件、元素節點的增刪改查,大家還可以在python官方文檔上學習更多的操作。
https://docs.python.org/3.5/library/xml.etree.elementtree.html
xml源文件格式[例]
- <?xml version="1.0" encoding="UTF-8"?>
- <framework>
- <processers>
- <processer name="AProcesser" file="lib64/A.so"
- path="/tmp">
- </processer>
- <processer name="BProcesser" file="lib64/B.so" value="fordelete">
- </processer>
- <processer name="BProcesser" file="lib64/B.so2222222"/>
- <services>
- <service name="search" prefix="/bin/search?"
- output_formatter="OutPutFormatter:service_inc">
- <chain sequency="chain1"/>
- <chain sequency="chain2"></chain>
- </service>
- <service name="update" prefix="/bin/update?">
- <chain sequency="chain3" value="fordelete"/>
- </service>
- </services>
- </processers>
- </framework>
使用庫:
xml.etree.ElementTree
官方文檔地址:http://docs.python.org/library/xml.etree.elementtree.html
實現思想:
使用ElementTree,先將文件讀入,解析成樹,之后,根據路徑,可以定位到樹的每個節點,再對節點進行修改,最后直接將其輸出
代碼附文檔:
- #!/usr/bin/python
- # -*- coding=utf-8 -*-
- # author : wklken@yeah.net
- # date: 2012-05-25
- # version: 0.1
- from xml.etree.ElementTree import ElementTree,Element
- def read_xml(in_path):
- '''''讀取並解析xml文件
- in_path: xml路徑
- return: ElementTree'''
- tree = ElementTree()
- tree.parse(in_path)
- return tree
- def write_xml(tree, out_path):
- '''''將xml文件寫出
- tree: xml樹
- out_path: 寫出路徑'''
- tree.write(out_path, encoding="utf-8",xml_declaration=True)
- def if_match(node, kv_map):
- '''''判斷某個節點是否包含所有傳入參數屬性
- node: 節點
- kv_map: 屬性及屬性值組成的map'''
- for key in kv_map:
- if node.get(key) != kv_map.get(key):
- return False
- return True
- #---------------search -----
- def find_nodes(tree, path):
- '''''查找某個路徑匹配的所有節點
- tree: xml樹
- path: 節點路徑'''
- return tree.findall(path)
- def get_node_by_keyvalue(nodelist, kv_map):
- '''''根據屬性及屬性值定位符合的節點,返回節點
- nodelist: 節點列表
- kv_map: 匹配屬性及屬性值map'''
- result_nodes = []
- for node in nodelist:
- if if_match(node, kv_map):
- result_nodes.append(node)
- return result_nodes
- #---------------change -----
- def change_node_properties(nodelist, kv_map, is_delete=False):
- '''''修改/增加 /刪除 節點的屬性及屬性值
- nodelist: 節點列表
- kv_map:屬性及屬性值map'''
- for node in nodelist:
- for key in kv_map:
- if is_delete:
- if key in node.attrib:
- del node.attrib[key]
- else:
- node.set(key, kv_map.get(key))
- def change_node_text(nodelist, text, is_add=False, is_delete=False):
- '''''改變/增加/刪除一個節點的文本
- nodelist:節點列表
- text : 更新后的文本'''
- for node in nodelist:
- if is_add:
- node.text += text
- elif is_delete:
- node.text = ""
- else:
- node.text = text
- def create_node(tag, property_map, content):
- '''''新造一個節點
- tag:節點標簽
- property_map:屬性及屬性值map
- content: 節點閉合標簽里的文本內容
- return 新節點'''
- element = Element(tag, property_map)
- element.text = content
- return element
- def add_child_node(nodelist, element):
- '''''給一個節點添加子節點
- nodelist: 節點列表
- element: 子節點'''
- for node in nodelist:
- node.append(element)
- def del_node_by_tagkeyvalue(nodelist, tag, kv_map):
- '''''同過屬性及屬性值定位一個節點,並刪除之
- nodelist: 父節點列表
- tag:子節點標簽
- kv_map: 屬性及屬性值列表'''
- for parent_node in nodelist:
- children = parent_node.getchildren()
- for child in children:
- if child.tag == tag and if_match(child, kv_map):
- parent_node.remove(child)
- if __name__ == "__main__":
- #1. 讀取xml文件
- tree = read_xml("./test.xml")
- #2. 屬性修改
- #A. 找到父節點
- nodes = find_nodes(tree, "processers/processer")
- #B. 通過屬性准確定位子節點
- result_nodes = get_node_by_keyvalue(nodes, {"name":"BProcesser"})
- #C. 修改節點屬性
- change_node_properties(result_nodes, {"age": "1"})
- #D. 刪除節點屬性
- change_node_properties(result_nodes, {"value":""}, True)
- #3. 節點修改
- #A.新建節點
- a = create_node("person", {"age":"15","money":"200000"}, "this is the firest content")
- #B.插入到父節點之下
- add_child_node(result_nodes, a)
- #4. 刪除節點
- #定位父節點
- del_parent_nodes = find_nodes(tree, "processers/services/service")
- #准確定位子節點並刪除之
- target_del_node = del_node_by_tagkeyvalue(del_parent_nodes, "chain", {"sequency" : "chain1"})
- #5. 修改節點文本
- #定位節點
- text_nodes = get_node_by_keyvalue(find_nodes(tree, "processers/services/service/chain"), {"sequency":"chain3"})
- change_node_text(text_nodes, "new text")
- #6. 輸出到結果文件
- write_xml(tree, "./out.xml")
通過main處理后的結果文件:
- <?xml version='1.0' encoding='utf-8'?>
- <framework>
- <processers>
- <processer file="lib64/A.so" name="AProcesser" path="/tmp">
- </processer>
- <processer age="1" file="lib64/B.so" name="BProcesser">
- <person age="15" money="200000">this is the firest content</person>
- </processer>
- <processer age="1" file="lib64/B.so2222222" name="BProcesser">
- <person age="15" money="200000">this is the firest content</person>
- </processer>
- <services>
- <service name="search" output_formatter="OutPutFormatter:service_inc"
- prefix="/bin/search?">
- <chain sequency="chain2" />
- </service>
- <service name="update" prefix="/bin/update?">
- <chain sequency="chain3" value="fordelete">new text</chain>
- </service>
- </services>
- </processers>
- </framework>
導入ElementTree
在使用xml.etree.ElementTree時,一般都按如下導入:
try: import xml.etree.cElementTree as ET except ImportError: import xml.etree.ElementTree as ET
XML是中結構化數據形式,在ET中使用ElementTree代表整個XML文檔,並視其為一棵樹,Element代表這個文檔樹中的單個節點。
ET對象具有多種方法從不同來源導入數據,如下:
#從硬盤的xml文件讀取數據 import xml.etree.ElementTree as ET tree = ET.parse('country_data.xml') #載入數據 root = tree.getroot() #獲取根節點 #從字符串讀取數據 root = ET.fromstring(country_data_as_string)
[注意]fromstring()是直接獲取string對象中的根節點,因此以上root其實是一個Element。
作為一個Element對象,本身是具有子元素,因此可以直接對Element進行迭代取值:
>>> for child in root: ... print child.tag, child.attrib ... country {'name': 'Liechtenstein'} country {'name': 'Singapore'} country {'name': 'Panama'}
或者直接使用索引尋找子節點:
>>> root[0][1].text '2008'
Element中的遍歷與查詢
Element.iter(tag=None):遍歷該Element所有后代,也可以指定tag進行遍歷尋找。
Element.findall(path):查找當前元素下tag或path能夠匹配的直系節點。
Element.find(path):查找當前元素下tag或path能夠匹配的首個直系節點。
Element.text: 獲取當前元素的text值。
Element.get(key, default=None):獲取元素指定key對應的屬性值,如果沒有該屬性,則返回default值。
Element對象
class xml.etree.ElementTree.Element(tag, attrib={}, **extra) tag:string,元素代表的數據種類。 text:string,元素的內容。 tail:string,元素的尾形。 attrib:dictionary,元素的屬性字典。 #針對屬性的操作 clear():清空元素的后代、屬性、text和tail也設置為None。 get(key, default=None):獲取key對應的屬性值,如該屬性不存在則返回default值。 items():根據屬性字典返回一個列表,列表元素為(key, value)。 keys():返回包含所有元素屬性鍵的列表。 set(key, value):設置新的屬性鍵與值。 #針對后代的操作 append(subelement):添加直系子元素。 extend(subelements):增加一串元素對象作為子元素。#python2.7新特性 find(match):尋找第一個匹配子元素,匹配對象可以為tag或path。 findall(match):尋找所有匹配子元素,匹配對象可以為tag或path。 findtext(match):尋找第一個匹配子元素,返回其text值。匹配對象可以為tag或path。 insert(index, element):在指定位置插入子元素。 iter(tag=None):生成遍歷當前元素所有后代或者給定tag的后代的迭代器。#python2.7新特性 iterfind(match):根據tag或path查找所有的后代。 itertext():遍歷所有后代並返回text值。 remove(subelement):刪除子元素。
ElementTree對象
class xml.etree.ElementTree.ElementTree(element=None, file=None)
element如果給定,則為新的ElementTree的根節點。
_setroot(element):用給定的element替換當前的根節點。慎用。
# 以下方法與Element類中同名方法近似,區別在於它們指定以根節點作為操作對象。
find(match)
findall(match)
findtext(match, default=None)
getroot():獲取根節點.
iter(tag=None)
iterfind(match)
parse(source, parser=None):裝載xml對象,source可以為文件名或文件類型對象.
write(file, encoding="us-ascii", xml_declaration=None, default_namespace=None,method="xml")
模塊方法
- xml.etree.ElementTree. Comment ( text=None )
-
創建一個特別的element,通過標准序列化使其代表了一個comment。comment可以為bytestring或unicode。
- xml.etree.ElementTree. dump ( elem )
-
生成一個element tree,通過sys.stdout輸出,elem可以是元素樹或單個元素。這個方法最好只用於debug。
- xml.etree.ElementTree. fromstring ( text )
-
text是一個包含XML數據的字符串,與XML()方法類似,返回一個Element實例。
- xml.etree.ElementTree. fromstringlist ( sequence, parser=None )
-
從字符串的序列對象中解析xml文檔。缺省parser為XMLParser,返回Element實例。
New in version 2.7.
- xml.etree.ElementTree. iselement ( element )
-
檢查是否是一個element對象。
- xml.etree.ElementTree. iterparse ( source, events=None, parser=None )
-
將文件或包含xml數據的文件對象遞增解析為element tree,並且報告進度。events是一個匯報列表,如果忽略,將只有end事件會匯報出來。
注意,iterparse()只會在看見開始標簽的">"符號時才會拋出start事件,因此屆時屬性是已經定義了,但是text和tail屬性在那時還沒有定義,同樣子元素也沒有定義,因此他們可能不能被顯示出來。如果你想要完整的元素,請查找end事件。
- xml.etree.ElementTree. parse ( source, parser=None )
-
將一個文件或者字符串解析為element tree。
- xml.etree.ElementTree. ProcessingInstruction ( target, text=None )
-
這個方法會創建一個特別的element,該element被序列化為一個xml處理命令。
- xml.etree.ElementTree. register_namespace ( prefix, uri )
-
注冊命名空間前綴。這個注冊是全局有效,任何已經給出的前綴或者命名空間uri的映射關系會被刪除。
New in version 2.7.
- xml.etree.ElementTree. SubElement ( parent, tag, attrib={}, **extra )
-
子元素工廠,創建一個Element實例並追加到已知的節點。
-
xml.etree.ElementTree.
tostring
(
element,
encoding="us-ascii",
method="xml"
)
-
生成一個字符串來表示表示xml的element,包括所有子元素。element是Element實例,method為"xml","html","text"。返回包含了xml數據的字符串。
- xml.etree.ElementTree. tostringlist ( element, encoding="us-ascii", method="xml" )
-
生成一個字符串來表示表示xml的element,包括所有子元素。element是Element實例,method為"xml","html","text"。返回包含了xml數據的字符串列表。
New in version 2.7.
- xml.etree.ElementTree. XML ( text, parser=None )
-
從一個字符串常量中解析出xml片段。返回Element實例。
- xml.etree.ElementTree. XMLID ( text, parser=None )
-
從字符串常量解析出xml片段,同時返回一個字典,用以映射element的id到其自身。