<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # Chapter 12 XML > " In the archonship of Aristaechmus, Draco enacted his ordinances. " > — [Aristotle](http://www.perseus.tufts.edu/cgi-bin/ptext?doc=Perseus:text:1999.01.0046;query=chapter%3D%235;layout=;loc=3.1) ## 概述 這本書的大部分章節都是以樣例代碼為中心的。但是XML這章不是;它以數據為中心。最常見的XML應用為“聚合訂閱(syndication feeds)”,它用來展示博客,論壇或者其他會經常更新的網站的最新內容。大多數的博客軟件都會在新文章,新的討論區,或者新博文發布的時候自動生成和更新feed。我們可以通過“訂閱(subscribe)”feed來關注它們,還可以使用專門的“[feed聚合工具(feed aggregator)](http://en.wikipedia.org/wiki/List_of_feed_aggregators)”,比如[Google Reader](http://www.google.com/reader/)。 以下的XML數據是我們這一章中要用到的。它是一個feed?—?更確切地說是一個[Atom聚合feed](http://atompub.org/rfc4287.html) ``` <?xml version='1.0' encoding='utf-8'?> <feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'> <title>dive into mark</title> <subtitle>currently between addictions</subtitle> <id>tag:diveintomark.org,2001-07-29:/</id> <updated>2009-03-27T21:56:07Z</updated> <link rel='alternate' type='text/html' href='http://diveintomark.org/'/> <link rel='self' type='application/atom+xml' href='http://diveintomark.org/feed/'/> <entry> <author> <name>Mark</name> <uri>http://diveintomark.org/</uri> </author> <title>Dive into history, 2009 edition</title> <link rel='alternate' type='text/html' href='http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition'/> <id>tag:diveintomark.org,2009-03-27:/archives/20090327172042</id> <updated>2009-03-27T21:56:07Z</updated> <published>2009-03-27T17:20:42Z</published> <category scheme='http://diveintomark.org' term='diveintopython'/> <category scheme='http://diveintomark.org' term='docbook'/> <category scheme='http://diveintomark.org' term='html'/> <summary type='html'>Putting an entire chapter on one page sounds bloated, but consider this &amp;mdash; my longest chapter so far would be 75 printed pages, and it loads in under 5 seconds&amp;hellip; On dialup.</summary> </entry> <entry> <author> <name>Mark</name> <uri>http://diveintomark.org/</uri> </author> <title>Accessibility is a harsh mistress</title> <link rel='alternate' type='text/html' href='http://diveintomark.org/archives/2009/03/21/accessibility-is-a-harsh-mistress'/> <id>tag:diveintomark.org,2009-03-21:/archives/20090321200928</id> <updated>2009-03-22T01:05:37Z</updated> <published>2009-03-21T20:09:28Z</published> <category scheme='http://diveintomark.org' term='accessibility'/> <summary type='html'>The accessibility orthodoxy does not permit people to question the value of features that are rarely useful and rarely used.</summary> </entry> <entry> <author> <name>Mark</name> </author> <title>A gentle introduction to video encoding, part 1: container formats</title> <link rel='alternate' type='text/html' href='http://diveintomark.org/archives/2008/12/18/give-part-1-container-formats'/> <id>tag:diveintomark.org,2008-12-18:/archives/20081218155422</id> <updated>2009-01-11T19:39:22Z</updated> <published>2008-12-18T15:54:22Z</published> <category scheme='http://diveintomark.org' term='asf'/> <category scheme='http://diveintomark.org' term='avi'/> <category scheme='http://diveintomark.org' term='encoding'/> <category scheme='http://diveintomark.org' term='flv'/> <category scheme='http://diveintomark.org' term='GIVE'/> <category scheme='http://diveintomark.org' term='mp4'/> <category scheme='http://diveintomark.org' term='ogg'/> <category scheme='http://diveintomark.org' term='video'/> <summary type='html'>These notes will eventually become part of a tech talk on video encoding.</summary> </entry> </feed> ``` ## 5分鐘XML速成 如果你已經了解XML,可以跳過這一部分。 XML是一種描述層次結構化數據的通用方法。XML_文檔_包含由_起始和結束標簽(tag)_分隔的一個或多個_元素(element)_。以下也是一個完整的(雖然空洞)XML文件: 1. 這是`foo`元素的_起始標簽_。 2. 這是`foo`元素對應的_結束標簽_。就如寫作、數學或者代碼中需要平衡括號一樣,每一個起始標簽必須有對應的結束標簽來_閉合_(匹配)。 元素可以_嵌套_到任意層次。位于`foo`中的元素`bar`可以被稱作其_子元素_。 ``` <foo> <mark><bar></bar></mark> </foo> ``` XML文檔中的第一個元素叫做_根元素(root element)_。并且每份XML文檔只能有一個根元素。以下不是一個XML文檔,因為它存在兩個“根元素”。 ``` <foo></foo> <bar></bar> ``` 元素可以有其_屬性(attribute)_,它們是一些名字-值(name-value)對。屬性由空格分隔列舉在元素的起始標簽中。一個元素中_屬性名_不能重復。_屬性值_必須用引號包圍起來。單引號、雙引號都是可以。 ``` </foo> ``` 1. `foo`元素有一個叫做`lang`的屬性。`lang`的值為`en` 2. `bar`元素則有兩個屬性,分別為`id`和`lang`。其中`lang`屬性的值為`fr`。它不會與`foo`的那個屬性產生沖突。每個元素都其獨立的屬性集。 如果元素有多個屬性,書寫的順序并不重要。元素的屬性是一個無序的鍵-值對集,跟Python中的列表對象一樣。另外,元素中屬性的個數是沒有限制的。 元素可以有其_文本內容(text content)_ ``` <foo lang='en'> <bar lang='fr'><mark>PapayaWhip</mark></bar> </foo> ``` 如果某一元素既沒有文本內容,也沒有子元素,它也叫做_空元素_。 ``` <foo></foo> ``` 表達空元素有一種簡潔的方法。通過在起始標簽的尾部添加`/`字符,我們可以省略結束標簽。上一個例子中的XML文檔可以寫成這樣: ``` <foo<mark>/</mark>> ``` 就像Python函數可以在不同的_模塊(modules)_中聲明一樣,也可以在不同的_名字空間(namespace)_中聲明XML元素。XML文檔的名字空間通常看起來像URL。我們可以通過聲明`xmlns`來定義_默認名字空間_。名字空間聲明跟元素屬性看起來很相似,但是它們的作用是不一樣的。 ``` </feed> ``` 1. `feed`元素處在名字空間`http://www.w3.org/2005/Atom`中。 2. `title`元素也是。名字空間聲明不僅會作用于當前聲明它的元素,還會影響到該元素的所有子元素。 也可以通過`xmlns:`prefix``聲明來定義一個名字空間并取其名為_prefix_。然后該名字空間中的每個元素都必須顯式地使用這個前綴(`prefix`)來聲明。 ``` </atom:feed> ``` 1. `feed`元素屬于名字空間`http://www.w3.org/2005/Atom`。 2. `title`元素也在那個名字空間。 對于XML解析器而言,以上兩個XML文檔是_一樣的_。名字空間 + 元素名 = XML標識。前綴只是用來引用名字空間的,所以對于解析器來說,這些前綴名(`atom:`)其實無關緊要的。名字空間相同,元素名相同,屬性(或者沒有屬性)相同,每個元素的文本內容相同,則XML文檔相同。 最后,在根元素之前,[字符編碼信息](strings.html#one-ring-to-rule-them-all)可以出現在XML文檔的第一行。(這里存在一個兩難的局面(catch-22),直觀上來說,解析XML文檔需要這些編碼信息,而這些信息又存在于XML文檔中,如果你對XML如何解決此問題有興趣,請參閱[XML規范中 F 章節](http://www.w3.org/TR/REC-xml/#sec-guessing-no-ext-info)) ``` <?xml version='1.0' <mark>encoding='utf-8'</mark>?> ``` 現在我們已經知道足夠多的XML知識,可以開始探險了! ## Atom Feed的結構 想像一下網絡上的博客,或者互聯網上任何需要頻繁更新的網站,比如[CNN.com](http://www.cnn.com/)。該站點有一個標題(“CNN.com”),一個子標題(“Breaking News, U.S., World, Weather, Entertainment _&_ Video News”),包含上次更新的日期(“updated 12:43 p.m. EDT, Sat May 16, 2009”),還有在不同時期發布的文章的列表。每一篇文章也有自己的標題,第一次發布的日期(如果曾經修訂過或者改正過某個輸入錯誤,或許也有一個上次更新的日期),并且每篇文章有自己唯一的URL。 Atom聚合格式被設計成可以包含所有這些信息的標準格式。我的博客無論在設計,主題還是讀者上都與CNN.com大不相同,但是它們的基本結構是相同的。CNN.com能做的事情,我的博客也能做… 每一個Atom訂閱都共享著一個_根元素_:即在名字空間`http://www.w3.org/2005/Atom`中的元素`feed`。 1. `http://www.w3.org/2005/Atom`表示名字空間Atom。 2. 每一個元素都可以包含`xml:lang`屬性,它用來聲明該元素及其子元素使用的語言。在當前樣例中,`xml:lang`在根元素中被聲明了一次,也就意味著,整個feed都使用英文。 描述Atom feed自身的一些信息在根元素`feed`的子元素中被聲明。 ``` <feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'> ``` 1. 該行表示這個feed的標題為`dive into mark`。 2. 這一行表示子標題為`currently between addictions`。 3. 每一個feed都要有一個全局唯一標識符(globally unique identifier)。想要知道如何創建它,請查閱[RFC 4151](http://www.ietf.org/rfc/rfc4151.txt)。 4. 表示當前feed上次更新的時間為March 27, 2009, at 21:56 GMT。通常來說,它與最近一篇文章最后一次被修改的時間是一樣的。 5. 事情開始變得有趣了…`link`元素沒有文本內容,但是它有三個屬性:`rel`,`type`和`href`。`rel`元素的值能告訴我們鏈接的類型;`rel='alternate'`表示這個鏈接指向當前feed的另外一個版本。`type='text/html'`表示鏈接的目標是一個HTML頁面。然后目標地址在`href`屬性中指出。 現在我們知道這個feed上一更新是在on March 27, 2009,它是為一個叫做“dive into mark”的站點準備的,并且站點的地址為[`http://diveintomark.org/`](http://diveintomark.org/)。 > ?在有一些XML文檔中,元素的排列順序是有意義的,但是Atom feed中不需要這樣做。 feed級的元數據后邊就是最近文章的列表了。單獨的一篇文章就像這樣: ``` <entry> <name>Mark</name> <uri>http://diveintomark.org/</uri> </author> href='http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition'/> <published>2009-03-27T17:20:42Z</published> <category scheme='http://diveintomark.org' term='docbook'/> <category scheme='http://diveintomark.org' term='html'/> bloated, but consider this &amp;mdash; my longest chapter so far would be 75 printed pages, and it loads in under 5 seconds&amp;hellip; On dialup.</summary> ``` 1. `author`元素指示文章的作者:一個叫做Mark的伙計,并且我們可以在`http://diveintomark.org/`找到他的事跡。(這就像是feed元素里的備用鏈接,但是沒有規定一定要這樣。許多網絡日志由多個作者完成,他們都有自己的個人主頁。)* `title`元素給出這篇文章的標題,即“Dive into history, 2009 edition”。 2. `如`feed`元素中的備用鏈接一樣,`link`元素給出這篇文章的HTML`版本地址。 3. 每個條目也像feed一樣,需要一個唯一的標識。 4. 每個條目有兩個日期與其相關:第一次發布日期(`published`)和上次修改日期(`updated`)。 5. 條目可以屬于任意多個類別。這篇文章被歸類到`diveintopython`,`docbook`,和`html`。 6. `summary`元素中有這篇文章的概要性描述。(還有一個元素這里沒有展示出來,即`content`,我們可以把整篇文章的內容都放在里邊。)當前樣例中,`summary`元素含有一個Atom特有的`type='html'`屬性,它用來告知這份概要為HTML格式,而非純文本。這非常重要,因為概要內容中包含了HTML中特有的實體(`&mdash;`和`&hellip;`),它們不應該以純文本直接顯示,正確的形式應該為“—”和“…”。 7. 最后就是`entry`元素的結束標記了,它指示文章元數據的結尾。 ## 解析XML Python可以使用幾種不同的方式解析XML文檔。它包含了[DOM](http://en.wikipedia.org/wiki/XML#DOM)和[SAX](http://en.wikipedia.org/wiki/Simple_API_for_XML)解析器,但是我們焦點將放在另外一個叫做ElementTree的庫上邊。 ``` <Element {http://www.w3.org/2005/Atom}feed at cd1eb0> ``` 1. ElementTree屬于Python標準庫的一部分,它的位置為`xml.etree.ElementTree`。 2. `parse()`函數是ElementTree庫的主要入口,它使用文件名或者[流對象](files.html#file-like-objects)作為參數。`parse()`函數會立即解析完整個文檔。如果內存資源緊張,也可以[增量式地解析XML文檔](http://effbot.org/zone/element-iterparse.htm) 3. `parse()`函數會返回一個能代表整篇文檔的對象。這_不是_根元素。要獲得根元素的引用可以調用`getroot()`方法。 4. 如預期的那樣,根元素即`http://www.w3.org/2005/Atom`名字空間中的`feed`。該字符串表示再次重申了非常重要的一點:XML元素由名字空間和標簽名(也稱作_本地名(local name)_)組成。這篇文檔中的每個元素都在名字空間Atom中,所以根元素被表示為`{http://www.w3.org/2005/Atom}feed`。 > ?ElementTree使用`{`namespace`}`localname``來表達XML元素。我們將會在ElementTree的API中多次見到這種形式。 ### 元素即列表 在ElementTree API中,元素的行為就像列表一樣。列表中的項即該元素的子元素。 ``` # continued from the previous example '{http://www.w3.org/2005/Atom}feed' 8 ... <Element {http://www.w3.org/2005/Atom}title at e2b5d0> <Element {http://www.w3.org/2005/Atom}subtitle at e2b4e0> <Element {http://www.w3.org/2005/Atom}id at e2b6c0> <Element {http://www.w3.org/2005/Atom}updated at e2b6f0> <Element {http://www.w3.org/2005/Atom}link at e2b4b0> <Element {http://www.w3.org/2005/Atom}entry at e2b720> <Element {http://www.w3.org/2005/Atom}entry at e2b510> <Element {http://www.w3.org/2005/Atom}entry at e2b750> ``` 1. 緊接前一例子,根元素為`{http://www.w3.org/2005/Atom}feed`。 2. 根元素的“長度”即子元素的個數。 3. 我們可以像使用迭代器一樣來遍歷其子元素。 4. 從輸出可以看到,根元素總共有8個子元素:所有feed級的元數據(`title`,`subtitle`,`id`,`updated`和`link`),還有緊接著的三個`entry`元素。 也許你已經注意到了,但我還是想要指出來:該列表只包含_直接_子元素。每一個`entry`元素都有其子元素,但是并沒有包括在這個列表中。這些子元素本可以包括在`entry`元素的列表中,但是確實不屬于`feed`的子元素。但是,無論這些元素嵌套的層次有多深,總是有辦法定位到它們的;在這章的后續部分我們會介紹兩種方法。 ### 屬性即字典 XML不只是元素的集合;每一個元素還有其屬性集。一旦獲取了某個元素的引用,我們可以像操作Python的字典一樣輕松獲取到其屬性。 ``` # continuing from the previous example {'{http://www.w3.org/XML/1998/namespace}lang': 'en'} <Element {http://www.w3.org/2005/Atom}link at e181b0> {'href': 'http://diveintomark.org/', 'type': 'text/html', 'rel': 'alternate'} <Element {http://www.w3.org/2005/Atom}updated at e2b4e0> {} ``` 1. `attrib`是一個代表元素屬性的字典。這個地方原來的標記語言是這樣描述的:`&lt;feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'&gt;`。前綴`xml:`指示一個內置的名字空間,每一個XML不需要聲明就可以使用它。 2. 第五個子元素?—?以0為起始的列表中即`[4]`?—?為元素`link`。 3. `link`元素有三個屬性:`href`,`type`,和`rel`。 4. 第四個子元素?—?`[3]`?—?為`updated`。 5. 元素`updated`沒有子元素,所以`.attrib`是一個空的字典對象。 ## 在XML文檔中查找結點 到目前為止,我們已經“自頂向下“地從根元素開始,一直到其子元素,走完了整個文檔。但是許多情況下我們需要找到XML中特定的元素。Etree也能完成這項工作。 ``` >>> import xml.etree.ElementTree as etree >>> tree = etree.parse('examples/feed.xml') >>> root = tree.getroot() [<Element {http://www.w3.org/2005/Atom}entry at e2b4e0>, <Element {http://www.w3.org/2005/Atom}entry at e2b510>, <Element {http://www.w3.org/2005/Atom}entry at e2b540>] >>> root.tag '{http://www.w3.org/2005/Atom}feed' [] [] ``` 1. `findfall()`方法查找匹配特定格式的子元素。(關于查詢的格式稍后會講到。) 2. 每個元素?—?包括根元素及其子元素?—?都有`findall()`方法。它會找到所有匹配的子元素。但是為什么沒有看到任何結果呢?也許不太明顯,這個查詢只會搜索其子元素。由于根元素`feed`中不存在任何叫做`feed`的子元素,所以查詢的結果為一個空的列表。 3. 這個結果也許也在你的意料之外。[在這篇文檔中確實存在`author`元素](#divingin);事實上總共有三個(每個`entry`元素中都有一個)。但是那些`author`元素不是根元素的_直接子元素_。我們可以在任意嵌套層次中查找`author`元素,但是查詢的格式會有些不同。 ``` [<Element {http://www.w3.org/2005/Atom}entry at e2b4e0>, <Element {http://www.w3.org/2005/Atom}entry at e2b510>, <Element {http://www.w3.org/2005/Atom}entry at e2b540>] [] ``` 1. 為了方便,對象`tree`(調用`etree.parse()`的返回值)中的一些方法是根元素中這些方法的鏡像。在這里,如果調用`tree.getroot().findall()`,則返回值是一樣的。 2. 也許有些意外,這個查詢請求也沒有找到文檔中的`author`元素。為什么沒有呢?因為它只是`tree.getroot().findall('{http://www.w3.org/2005/Atom}author')`的一種簡潔表示,即“查詢所有是根元素的子元素的`author`”。因為這些`author`是`entry`元素的子元素,所以查詢沒有找到任何匹配的。 `find()`方法用來返回第一個匹配到的元素。當我們認為只會有一個匹配,或者有多個匹配但我們只關心第一個的時候,這個方法是很有用的。 ``` >>> len(entries) 3 >>> title_element.text 'Dive into history, 2009 edition' >>> foo_element >>> type(foo_element) <class 'NoneType'> ``` 1. 在前一樣例中已經看到。這一句返回所有的`atom:entry`元素。 2. `find()`方法使用ElementTree作為參數,返回第一個匹配到的元素。 3. 在`entries[0]`中沒有叫做`foo`的元素,所以返回值為`None`。 > ?可逮住你了,在這里`find()`方法非常容易被誤解。在布爾上下文中,如果ElementTree元素對象不包含子元素,其值則會被認為是`False`(_即_如果`len(element)`等于0)。這就意味著`if element.find('...')`并非在測試是否`find()`方法找到了匹配項;這條語句是在測試匹配到的元素是否包含子元素!想要測試`find()`方法是否返回了一個元素,則需使用`if element.find('...') is not None`。 也_可以_在所有_派生(descendant)_元素中搜索,_即_任意嵌套層次的子元素,孫子元素等… ``` >>> all_links [<Element {http://www.w3.org/2005/Atom}link at e181b0>, <Element {http://www.w3.org/2005/Atom}link at e2b570>, <Element {http://www.w3.org/2005/Atom}link at e2b480>, <Element {http://www.w3.org/2005/Atom}link at e2b5a0>] {'href': 'http://diveintomark.org/', 'type': 'text/html', 'rel': 'alternate'} {'href': 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition', 'type': 'text/html', 'rel': 'alternate'} >>> all_links[2].attrib {'href': 'http://diveintomark.org/archives/2009/03/21/accessibility-is-a-harsh-mistress', 'type': 'text/html', 'rel': 'alternate'} >>> all_links[3].attrib {'href': 'http://diveintomark.org/archives/2008/12/18/give-part-1-container-formats', 'type': 'text/html', 'rel': 'alternate'} ``` 1. `//{http://www.w3.org/2005/Atom}link`與前一樣例很相似,除了開頭的兩條斜線。這兩條斜線告訴`findall()`方法“不要只在直接子元素中查找;查找的范圍可以是_任意_嵌套層次”。 2. 查詢到的第一個結果_是_根元素的直接子元素。從它的屬性中可以看出,它是一個指向該feed的HTML版本的備用鏈接。 3. 其他的三個結果分別是低一級的備用鏈接。每一個`entry`都有單獨一個`link`子元素,由于在查詢語句前的兩條斜線的作用,我們也能定位到他們。 總的來說,ElementTree的`findall()`方法是其一個非常強大的特性,但是它的查詢語言卻讓人有些出乎意料。官方描述它為“[有限的XPath支持](http://effbot.org/zone/element-xpath.htm)。”[XPath](http://www.w3.org/TR/xpath)是一種用于查詢XML文檔的W3C標準。對于基礎地查詢來說,ElementTree與XPath語法上足夠相似,但是如果已經會XPath的話,它們之間的差異可能會使你感到不快。現在,我們來看一看另外一個第三方XML庫,它擴展了ElementTree的API以提供對XPath的全面支持。 ## 深入lxml [`lxml`](http://codespeak.net/lxml/)是一個開源的第三方庫,以流行的[libxml2 解析器](http://www.xmlsoft.org/)為基礎開發。提供了與ElementTree完全兼容的API,并且擴展它以提供了對XPath 1.0的全面支持,以及改進了一些其他精巧的細節。提供[Windows的安裝程序](http://pypi.python.org/pypi/lxml/);Linux用戶推薦使用特定發行版自帶的工具比如`yum`或者`apt-get`從它們的程序庫中安裝預編譯好了的二進制文件。要不然,你就得手工[安裝](http://codespeak.net/lxml/installation.html)他們了。 ``` [<Element {http://www.w3.org/2005/Atom}entry at e2b4e0>, <Element {http://www.w3.org/2005/Atom}entry at e2b510>, <Element {http://www.w3.org/2005/Atom}entry at e2b540>] ``` 1. 導入`lxml`以后,可以發現它與內置的ElementTree庫提供相同的API。 2. `parse()`函數:與ElementTree相同。 3. `getroot()`方法:相同。 4. `findall()`方法:完全相同。 對于大型的XML文檔,`lxml`明顯比內置的ElementTree快了許多。如果現在只用到了ElementTree的API,并且想要使用其最快的實現(implementation),我們可以嘗試導入`lxml`,并且將內置的ElementTree作為備用。 ``` try: from lxml import etree except ImportError: import xml.etree.ElementTree as etree ``` 但是`lxml`不只是一個更快速的ElementTree。它的`findall()`方法能夠支持更加復雜的表達式。 ``` >>> tree = lxml.etree.parse('examples/feed.xml') [<Element {http://www.w3.org/2005/Atom}link at eeb8a0>, <Element {http://www.w3.org/2005/Atom}link at eeb990>, <Element {http://www.w3.org/2005/Atom}link at eeb960>, <Element {http://www.w3.org/2005/Atom}link at eeb9c0>] [<Element {http://www.w3.org/2005/Atom}link at eeb930>] >>> NS = '{http://www.w3.org/2005/Atom}' [<Element {http://www.w3.org/2005/Atom}author at eeba80>, <Element {http://www.w3.org/2005/Atom}author at eebba0>] ``` 1. 在這個樣例中,我使用了`import lxml.etree`(而非`from lxml import etree`),以強調這些特性只限于`lxml`。 2. 這一句在整個文檔范圍內搜索名字空間Atom中具有`href`屬性的所有元素。在查詢語句開頭的`//`表示“搜索的范圍為整個文檔(不只是根元素的子元素)。” `{http://www.w3.org/2005/Atom}`指示“搜索范圍僅在名字空間Atom中。” `*` 表示“任意本地名(local name)的元素。” `[@href]`表示“含有`href`屬性。” 3. 該查詢找出所有包含`href`屬性并且其值為`http://diveintomark.org/`的Atom元素。 4. 在簡單的[字符串格式化](strings.html#formatting-strings)后(要不然這條復合查詢語句會變得特別長),它搜索名字空間Atom中包含`uri`元素作為子元素的`author`元素。該條語句只返回了第一個和第二個`entry`元素中的`author`元素。最后一個`entry`元素中的`author`只包含有`name`屬性,沒有`uri`。 仍然不夠用?`lxml`也集成了對任意XPath 1.0表達式的支持。我們不會深入講解XPath的語法;那可能需要一整本書!但是我會給你展示它是如何集成到`lxml`去的。 ``` >>> import lxml.etree >>> tree = lxml.etree.parse('examples/feed.xml') ... namespaces=NSMAP) [<Element {http://www.w3.org/2005/Atom}entry at e2b630>] >>> entry = entries[0] ['Accessibility is a harsh mistress'] ``` 1. 要查詢名字空間中的元素,首先需要定義一個名字空間前綴映射。它就是一個Python字典對象。 2. 這就是一個XPath查詢請求。這個XPath表達式目的在于搜索`category`元素,并且該元素包含有值為`accessibility`的`term`屬性。但是那并不是查詢的結果。請看查詢字符串的尾端;是否注意到了`/..`這一塊?它的意思是,“然后返回已經找到的`category`元素的父元素。”所以這條XPath查詢語句會找到所有包含`&lt;category term='accessibility'&gt;`作為子元素的條目。 3. `xpath()`函數返回一個ElementTree對象列表。在這篇文檔中,只有一個`category`元素,并且它的`term`屬性值為`accessibility`。 4. XPath表達式并不總是會返回一個元素列表。技術上說,一個解析了的XML文檔的DOM模型并不包含元素;它只包含_結點(node)_。依據它們的類型,結點可以是元素,屬性,甚至是文本內容。XPath查詢的結果是一個結點列表。當前查詢返回一個文本結點列表:`title`元素(`atom:title`)的文本內容(`text()`),并且`title`元素必須是當前元素的子元素(`./`)。 ## 生成XML Python對XML的支持不只限于解析已存在的文檔。我們也可以從頭來創建XML文檔。 ``` >>> import xml.etree.ElementTree as etree <ns0:feed xmlns:ns0='http://www.w3.org/2005/Atom' xml:lang='en'/> ``` 1. 實例化`Element`類來創建一個新元素。可以將元素的名字(名字空間 + 本地名)作為其第一個參數。當前語句在Atom名字空間中創建一個`feed`元素。它將會成為我們文檔的根元素。 2. 將屬性名和值構成的字典對象傳遞給`attrib`參數,這樣就可以給新創建的元素添加屬性。請注意,屬性名應該使用標準的ElementTree格式,`{`namespace`}`localname``。 3. 在任何時候,我們可以使用ElementTree的`tostring()`函數序列化任意元素(還有它的子元素)。 這種序列化結果有使你感到意外嗎?技術上說,ElementTree使用的序列化方法是精確的,但卻不是最理想的。在本章開頭給出的XML樣例文檔中定義了一個_默認名字空間(default namespace)_(`xmlns='http://www.w3.org/2005/Atom'`)。對于每個元素都在同一個名字空間中的文檔?—?比如Atom feeds?—?定義默認的名字空間非常有用,因為只需要聲明一次名字空間,然后在聲明每個元素的時候只需要使用其本地名即可(`&lt;feed&gt;`,`&lt;link&gt;`,`&lt;entry&gt;`)。除非想要定義另外一個名字空間中的元素,否則沒有必要使用前綴。 對于XML解析器來說,它不會“注意”到使用默認名字空間和使用前綴名字空間的XML文檔之間有什么不同。當前序列化結果的DOM為: ``` <ns0:feed xmlns:ns0='http://www.w3.org/2005/Atom' xml:lang='en'/> ``` 與下列序列化的DOM是一模一樣的: ``` <feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'/> ``` 實際上唯一不同的只是第二個序列化短了幾個字符長度。如果我們改動整個樣例feed,使每一個起始和結束標簽都有一個`ns0:`前綴,這將為每個起始標簽增加 4 個字符 × 79 個標簽 + 4 個名字空間聲明本身用到的字符,總共320個字符。假設我們使用[UTF-8編碼](strings.html#byte-arrays),那將是320個額外的字節。(使用gzip壓縮以后,大小可以降到21個字節,但是,21個字節也是字節。)也許對個人來說這算不了什么,但是對于像Atom feed這樣的東西,只要稍有改變就有可能被下載上千次,每一個請求節約的幾個字節就會迅速累加起來。 內置的ElementTree庫沒有提供細粒度地對序列化時名字空間內的元素的控制,但是`lxml`有這樣的功能。 ``` >>> import lxml.etree <feed xmlns='http://www.w3.org/2005/Atom'/> >>> print(lxml.etree.tounicode(new_feed)) <feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'/> ``` 1. 首先,定義一個用于名字空間映射的字典對象。其值為名字空間;字典中的鍵即為所需要的前綴。使用`None`作為前綴來定義默認的名字空間。 2. 現在我們可以在創建元素的時候,給`lxml`專有的`nsmap`參數傳值,并且`lxml`會參照我們所定義的名字空間前綴。 3. 如所預期的那樣,該序列化使用Atom作為默認的名字空間,并且在聲明`feed`元素的時候沒有使用名字空間前綴。 4. 啊噢… 我們忘了加上`xml:lang`屬性。我們可以使用`set()`方法來隨時給元素添加所需屬性。該方法使用兩個參數:標準ElementTree格式的屬性名,然后,屬性值。(該方法不是`lxml`特有的。在該樣例中,只有`nsmap參數是`lxml`特有的,它用來控制序列化輸出時名字空間的前綴。)` 難道每個XML文檔只能有一個元素嗎?當然不了。我們可以創建子元素。 ``` >>> print(lxml.etree.tounicode(new_feed)) <feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'><title type='html'/></feed> <feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'><title type='html'>dive into &amp;hellip;</title></feed> <feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'> <title type='html'>dive into&amp;hellip;</title> </feed> ``` 1. 給已有元素創建子元素,我們需要實例化`SubElement`類。它只要求兩個參數,父元素(即該樣例中的`new_feed`)和子元素的名字。由于該子元素會從父元素那兒繼承名字空間的映射關系,所以這里不需要再聲明名字空間前綴。 2. 我們也可以傳遞屬性字典給它。字典的鍵即屬性名;值為屬性的值。 3. 如預期的那樣,新創建的`title`元素在Atom名字空間中,并且它作為子元素插入到`feed`元素中。由于`title`元素沒有文件內容,也沒有其子元素,所以`lxml`將其序列化為一個空元素(使用`/&gt;`)。 4. 設定元素的文本內容,只需要設定其`.text`屬性。 5. 當前`title`元素序列化的時候就使用了其文本內容。任何包含了`&lt;`或者`&`符號的內容在序列化的時候需要被轉義。`lxml`會自動處理轉義。 6. 我們也可以在序列化的時候應用“漂亮的輸出(pretty printing)”,這會在每個結束標簽的末尾,或者含有子元素但沒有文本內容的標簽的末尾添加換行符。用術語說就是,`lxml`添加“無意義的空白(insignificant whitespace)”以使輸出更具可讀性。 > ?你也許也想要看一看[xmlwitch](http://github.com/galvez/xmlwitch/tree/master),它也是用來生成XML的另外一個第三方庫。它大量地使用了[`with`語句](special-method-names.html#context-managers)來使生成的XML代碼更具可讀性。 ## 解析破損的XML XML規范文檔中指出,要求所有遵循XML規范的解析器使用“嚴厲的(draconian)錯誤處理”。即,當它們在XML文檔中檢測到任何編排良好性(wellformedness)錯誤的時候,應當立即停止解析。編排良好性錯誤包括不匹配的起始和結束標簽,未定義的實體(entity),非法的Unicode字符,還有一些只有內行才懂的規則(esoteric rules)。這與其他的常見格式,比如HTML,形成了鮮明的對比?—?即使忘記了封閉HTML標簽,或者在屬性值中忘了轉義`&`字符,我們的瀏覽器也不會停止渲染一個Web頁面。(通常大家認為HTML沒有錯誤處理機制,這是一個常見的誤解。[HTML的錯誤處理](http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#parsing)實際上被很好的定義了,但是它比“遇見第一個錯誤即停止”這種機制要復雜得多。) 一些人(包括我自己)認為XML的設計者強制實行這種嚴格的錯誤處理本身是一個失誤。請不要誤解我;我當然能看到簡化錯誤處理機制的優勢。但是在現實中,“編排良好性”這種構想比乍聽上去更加復雜,特別是對XML(比如Atom feeds)這種發布在網絡上,通過HTTP傳播的文檔。早在1997年XML就標準化了這種嚴厲的錯誤處理,盡管XML已經非常成熟,研究一直表明,網絡上相當一部分的Atom feeds仍然存在著編排完整性錯誤。 所以,從理論上和實際應用兩種角度來看,我有理由“不惜任何代價”來解析XML文檔,即,當遇到編排良好性錯誤時,_不會_中斷解析操作。如果你認為你也需要這樣做,`lxml`可以助你一臂之力。 以下是一個破損的XML文檔的片斷。其中的編排良好性錯誤已經被高亮標出來了。 ``` <?xml version='1.0' encoding='utf-8'?> <feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'> <title>dive into <mark>&hellip;</mark></title> ... </feed> ``` 因為實體`&hellip;`并沒有在XML中被定義,所以這算作一個錯誤。(它在HTML中被定義。)如果我們嘗試使用默認的設置來解析該破損的feed,`lxml`會因為這個未定義的實體而停下來。 ``` >>> import lxml.etree >>> tree = lxml.etree.parse('examples/feed-broken.xml') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "lxml.etree.pyx", line 2693, in lxml.etree.parse (src/lxml/lxml.etree.c:52591) File "parser.pxi", line 1478, in lxml.etree._parseDocument (src/lxml/lxml.etree.c:75665) File "parser.pxi", line 1507, in lxml.etree._parseDocumentFromURL (src/lxml/lxml.etree.c:75993) File "parser.pxi", line 1407, in lxml.etree._parseDocFromFile (src/lxml/lxml.etree.c:75002) File "parser.pxi", line 965, in lxml.etree._BaseParser._parseDocFromFile (src/lxml/lxml.etree.c:72023) File "parser.pxi", line 539, in lxml.etree._ParserContext._handleParseResultDoc (src/lxml/lxml.etree.c:67830) File "parser.pxi", line 625, in lxml.etree._handleParseResult (src/lxml/lxml.etree.c:68877) File "parser.pxi", line 565, in lxml.etree._raiseParseError (src/lxml/lxml.etree.c:68125) lxml.etree.XMLSyntaxError: Entity 'hellip' not defined, line 3, column 28 ``` 為了解析該破損的XML文檔,忽略它的編排良好性錯誤,我們需要創建一個自定義的XML解析器。 ``` examples/feed-broken.xml:3:28:FATAL:PARSER:ERR_UNDECLARED_ENTITY: Entity 'hellip' not defined >>> tree.findall('{http://www.w3.org/2005/Atom}title') [<Element {http://www.w3.org/2005/Atom}title at ead510>] >>> title = tree.findall('{http://www.w3.org/2005/Atom}title')[0] 'dive into ' <feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'> <title>dive into </title> . . [rest of serialization snipped for brevity] . ``` 1. 實例化`lxml.etree.XMLParser`類來創建一個自定義的解析器。它可以使用[許多不同的命名參數](http://codespeak.net/lxml/parsing.html#parser-options)。在此,我們感興趣的為`recover`參數。當它的值被設為`True`,XML解析器會盡力嘗試從編排良好性錯誤中“恢復”。 2. 為使用自定的解析器來處理XML文檔,將對象`parser`作為第二個參數傳遞給`parse()`函數。注意,`lxml`沒有因為那個未定義的`&hellip;`實體而拋出異常。 3. 解析器會記錄它所遇到的所有編排良好性錯誤。(無論它是否被設置為需要從錯誤中恢復,這個記錄總會存在。) 4. 由于不知道如果處理該未定義的`&hellip;`實體,解析器默認會將其省略掉。`title`元素的文本內容變成了`'dive into '`。 5. 從序列化的結果可以看出,實體`&hellip;`并沒有被移到其他地方去;它就是被省略了。 在此,必須反復強調,這種“可恢復的”XML解析器沒有**互用性(interoperability)保證**。另一個不同的解析器可能就會認為`&hellip;`來自HTML,然后將其替換為`&amp;hellip;`。這樣“更好”嗎?也許吧。這樣“更正確”嗎?不,兩種處理方法都不正確。正確的行為(根據XML規范)應該是終止解析操作。如果你已經決定不按規范來,你得自己負責。 ## 進一步閱讀 * [維基百科上的詞條 XML](http://en.wikipedia.org/wiki/XML) * [ElementTree的XML API](http://docs.python.org/3.1/library/xml.etree.elementtree.html) * [元素和樹狀元素](http://effbot.org/zone/element.htm) * [ElementTree中對XPath的支持](http://effbot.org/zone/element-xpath.htm) * [ElementTree的迭代式解析(iterparse)功能](http://effbot.org/zone/element-iterparse.htm) * [`lxml`](http://codespeak.net/lxml/) * [使用`lxml`解析XML和HTML with](http://codespeak.net/lxml/1.3/parsing.html) * [使用`lxml`解析XPath和XSLT](http://codespeak.net/lxml/1.3/xpathxslt.html) * [xmlwitch](http://github.com/galvez/xmlwitch/tree/master)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看