標(biāo)準(zhǔn)庫(kù)(7)

2018-02-24 15:48 更新

xml在軟件領(lǐng)域用途非常廣泛,有名人曰:

“當(dāng) XML(擴(kuò)展標(biāo)記語(yǔ)言)于 1998 年 2 月被引入軟件工業(yè)界時(shí),它給整個(gè)行業(yè)帶來(lái)了一場(chǎng)風(fēng)暴。有史以來(lái)第一次,這個(gè)世界擁有了一種用來(lái)結(jié)構(gòu)化文檔和數(shù)據(jù)的通用且適應(yīng)性強(qiáng)的格式,它不僅僅可以用于 WEB,而且可以被用于任何地方?!?/p>

---《Designing With Web Standards Second Edition》, Jeffrey Zeldman

對(duì)于xml如果要做一個(gè)定義式的說(shuō)明,就不得不引用w3cschool里面簡(jiǎn)潔而明快的說(shuō)明:

  • XML 指可擴(kuò)展標(biāo)記語(yǔ)言(EXtensible Markup Language)
  • XML 是一種標(biāo)記語(yǔ)言,很類似 HTML
  • XML 的設(shè)計(jì)宗旨是傳輸數(shù)據(jù),而非顯示數(shù)據(jù)
  • XML 標(biāo)簽沒有被預(yù)定義。您需要自行定義標(biāo)簽。
  • XML 被設(shè)計(jì)為具有自我描述性。
  • XML 是 W3C 的推薦標(biāo)準(zhǔn)

如果讀者要詳細(xì)了解和學(xué)習(xí)有關(guān)xml,可以閱讀w3cschool的教程

xml的重要,關(guān)鍵在于它是用來(lái)傳輸數(shù)據(jù),因?yàn)閭鬏敂?shù)據(jù),特別是在web編程中,經(jīng)常要用到的。有了這樣一種東西,就讓數(shù)據(jù)傳輸變得簡(jiǎn)單了。對(duì)于這么重要的,python當(dāng)然有支持。

一般來(lái)講,一個(gè)引人關(guān)注的東西,總會(huì)有很多人從不同側(cè)面去關(guān)注。在編程語(yǔ)言中也是如此,所以,對(duì)xml這個(gè)明星式的東西,python提供了多種模塊來(lái)處理。

  • xml.dom.* 模塊:Document Object Model。適合用于處理 DOM API。它能夠?qū)ml數(shù)據(jù)在內(nèi)存中解析成一個(gè)樹,然后通過對(duì)樹的操作來(lái)操作xml。但是,這種方式由于將xml數(shù)據(jù)映射到內(nèi)存中的樹,導(dǎo)致比較慢,且消耗更多內(nèi)存。
  • xml.sax.* 模塊:simple API for XML。由于SAX以流式讀取xml文件,從而速度較快,切少占用內(nèi)存,但是操作上稍復(fù)雜,需要用戶實(shí)現(xiàn)回調(diào)函數(shù)。
  • xml.parser.expat:是一個(gè)直接的,低級(jí)一點(diǎn)的基于 C 的 expat 的語(yǔ)法分析器。 expat接口基于事件反饋,有點(diǎn)像 SAX 但又不太像,因?yàn)樗慕涌诓⒉皇峭耆?guī)范于 expat 庫(kù)的。
  • xml.etree.ElementTree (以下簡(jiǎn)稱 ET):元素樹。它提供了輕量級(jí)的python式的API,相對(duì)于DOM,ET快了很多 ,而且有很多令人愉悅的API可以使用;相對(duì)于SAX,ET也有ET.iterparse提供了 “在空中” 的處理方式,沒有必要加載整個(gè)文檔到內(nèi)存,節(jié)省內(nèi)存。ET的性能的平均值和SAX差不多,但是API的效率更高一點(diǎn)而且使用起來(lái)很方便。

所以,我用xml.etree.ElementTree

ElementTree在標(biāo)準(zhǔn)庫(kù)中有兩種實(shí)現(xiàn)。一種是純Python實(shí)現(xiàn):xml.etree.ElementTree ,另外一種是速度快一點(diǎn):xml.etree.cElementTree 。

如果讀者使用的是python2.x,可以像這樣引入模塊:

try:
    import xml.etree.cElementTree as ET
except ImportError:
    import xml.etree.ElementTree as ET

如果是Python3.3以上,就沒有這個(gè)必要了,只需要一句話import xml.etree.ElementTree as ET即可,然后由模塊自動(dòng)來(lái)尋找適合的方式。顯然python3.x相對(duì)python2.x有了很大進(jìn)步。但是,本教程礙于很多工程項(xiàng)目還沒有升級(jí)換代,暫且忍受了。

遍歷查詢

先要搞一個(gè)xml文檔。為了圖省事,我就用w3cschool中的一個(gè)例子:

這是一個(gè)xml樹,只不過是用圖來(lái)表示的,還沒有用ET解析呢。把這棵樹寫成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保存為名為22601.xml的文件,然后對(duì)其進(jìn)行如下操作:

>>> import xml.etree.cElementTree as ET

為了簡(jiǎn)化,我用這種方式引入,如果在編程實(shí)踐中,推薦讀者使用try...except...方式。

>>> tree = ET.ElementTree(file="22601.xml")
>>> tree
<ElementTree object at 0xb724cc2c>

建立起xml解析樹。然后可以通過根節(jié)點(diǎn)向下開始讀取各個(gè)元素(element對(duì)象)。

在上述xml文檔中,根元素是,它沒有屬性,或者屬性為空。

>>> root = tree.getroot()      #獲得根
>>> root.tag
'bookstore'
>>> root.attrib
{}

要想將根下面的元素都讀出來(lái),可以:

>>> 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        #無(wú)內(nèi)容
'\n        '

再深點(diǎn),就有感覺了:

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

對(duì)于ElementTree對(duì)象,有一個(gè)iter方法可以對(duì)指定名稱的子節(jié)點(diǎn)進(jìn)行深度優(yōu)先遍歷。例如:

>>> for ele in tree.iter(tag="book"):        #遍歷名稱為book的節(jié)點(diǎn)
...     print ele.tag, ele.attrib
... 
book {'category': 'COOKING'} 
book {'category': 'CHILDREN'} 
book {'category': 'WEB'} 

>>> for ele in tree.iter(tag="title"):        #遍歷名稱為title的節(jié)點(diǎn)
...     print ele.tag, ele.attrib, ele.text
... 
title {'lang': 'en'} Everyday Italian
title {'lang': 'en'} Harry Potter
title {'lang': 'en'} Learning XML

如果不指定元素名稱,就是將所有的元素遍歷一邊。

>>> 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 {}

除了上面的方法,還可以通過路徑,搜索到指定的元素,讀取其內(nèi)容。這就是xpath。此處對(duì)xpath不詳解,如果要了解可以到網(wǎng)上搜索有關(guān)信息。

>>> for ele in tree.iterfind("book/title"):
...     print ele.text
... 
Everyday Italian
Harry Potter
Learning XML

利用findall()方法,也可以是實(shí)現(xiàn)查找功能:

>>> for ele in tree.findall("book"):
...     title = ele.find('title').text
...     price = ele.find('price').text
...     lang = ele.find('title').attrib
...     print title, price, lang
... 
Everyday Italian 30.00 {'lang': 'en'}
Harry Potter 29.99 {'lang': 'en'}
Learning XML 39.95 {'lang': 'en'}

編輯

除了讀取有關(guān)數(shù)據(jù)之外,還能對(duì)xml進(jìn)行編輯,即增刪改查功能。還是以上面的xml文檔為例:

>>> root[1].tag
'book'
>>> del root[1]
>>> for ele in root:
...     print ele.tag
... 
book
book

如此,成功刪除了一個(gè)節(jié)點(diǎn)。原來(lái)有三個(gè)book節(jié)點(diǎn),現(xiàn)在就還剩兩個(gè)了。打開源文件再看看,是不是正好少了第二個(gè)節(jié)點(diǎn)呢?一定很讓你失望,源文件居然沒有變化。

的確如此,源文件沒有變化,這就對(duì)了。因?yàn)橹链说男薷膭?dòng)作,還是停留在內(nèi)存中,還沒有將修改結(jié)果輸出到文件。不要忘記,我們是在內(nèi)存中建立的ElementTree對(duì)象。再這樣做:

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

把當(dāng)前文件路徑拼裝好。然后:

>>> tree.write(file)

再看源文件,已經(jīng)變成兩個(gè)節(jié)點(diǎn)了。

除了刪除,也能夠修改:

>>> for price in root.iter("price"):        #原來(lái)每本書的價(jià)格
...     print price.text
... 
30.00
39.95
>>> for price in root.iter("price"):        #每本上漲7元,并且增加屬性標(biāo)記
...     new_price = float(price.text) + 7
...     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">37.0</price> 
    </book>
    <book category="WEB">
        <title lang="en">Learning XML</title> 
        <author>Erik T. Ray</author> 
        <year>2003</year> 
        <price updated="up">46.95</price> 
    </book>
</bookstore>

不僅價(jià)格修改了,而且在price標(biāo)簽里面增加了屬性標(biāo)記。干得不錯(cuò)。

上面用del來(lái)刪除某個(gè)元素,其實(shí),在編程中,這個(gè)用的不多,更喜歡用remove()方法。比如我要?jiǎng)h除price > 40的書??梢赃@么做:

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

于是就這樣了:

<bookstore>
    <book category="COOKING">
        <title lang="en">Everyday Italian</title> 
        <author>Giada De Laurentiis</author> 
        <year>2005</year> 
        <price updated="up">37.0</price> 
    </book>
</bookstore>

接下來(lái)就要增加元素了。

>>> import xml.etree.cElementTree as ET
>>> tree = ET.ElementTree(file="22601.xml")
>>> root = tree.getroot()
>>> ET.SubElement(root, "book")        #在root里面添加book節(jié)點(diǎn)
<Element 'book' at 0xb71c7578>
>>> for ele in root:
...    print ele.tag
... 
book
book
>>> b2 = root[1]                      #得到新增的book節(jié)點(diǎn)
>>> b2.text = "python"                #添加內(nèi)容
>>> tree.write("22601.xml")

查看源文件:

<bookstore>
    <book category="COOKING">
        <title lang="en">Everyday Italian</title> 
        <author>Giada De Laurentiis</author> 
        <year>2005</year> 
        <price updated="up">37.0</price> 
    </book>
    <book>python</book>
</bookstore>

常用屬性和方法總結(jié)

ET里面的屬性和方法不少,這里列出常用的,供使用中備查。

Element對(duì)象

常用屬性:

  • tag:string,元素?cái)?shù)據(jù)種類
  • text:string,元素的內(nèi)容
  • attrib:dictionary,元素的屬性字典
  • tail:string,元素的尾形

針對(duì)屬性的操作

  • clear():清空元素的后代、屬性、text和tail也設(shè)置為None
  • get(key, default=None):獲取key對(duì)應(yīng)的屬性值,如該屬性不存在則返回default值
  • items():根據(jù)屬性字典返回一個(gè)列表,列表元素為(key, value)
  • keys():返回包含所有元素屬性鍵的列表
  • set(key, value):設(shè)置新的屬性鍵與值

針對(duì)后代的操作

  • append(subelement):添加直系子元素
  • extend(subelements):增加一串元素對(duì)象作為子元素
  • find(match):尋找第一個(gè)匹配子元素,匹配對(duì)象可以為tag或path
  • findall(match):尋找所有匹配子元素,匹配對(duì)象可以為tag或path
  • findtext(match):尋找第一個(gè)匹配子元素,返回其text值。匹配對(duì)象可以為tag或path
  • insert(index, element):在指定位置插入子元素
  • iter(tag=None):生成遍歷當(dāng)前元素所有后代或者給定tag的后代的迭代器
  • iterfind(match):根據(jù)tag或path查找所有的后代
  • itertext():遍歷所有后代并返回text值
  • remove(subelement):刪除子元素

ElementTree對(duì)象

  • find(match)
  • findall(match)
  • findtext(match, default=None)
  • getroot():獲取根節(jié)點(diǎn).
  • iter(tag=None)
  • iterfind(match)
  • parse(source, parser=None):裝載xml對(duì)象,source可以為文件名或文件類型對(duì)象.
  • write(file, encoding="us-ascii", xml_declaration=None, default_namespace=None,method="xml") 

一個(gè)實(shí)例

最后,提供一個(gè)參考,這是一篇來(lái)自網(wǎng)絡(luò)的文章:Python xml屬性、節(jié)點(diǎn)、文本的增刪改,本文的源碼我也復(fù)制到下面,請(qǐng)讀者參考:

實(shí)現(xiàn)思想:

使用ElementTree,先將文件讀入,解析成樹,之后,根據(jù)路徑,可以定位到樹的每個(gè)節(jié)點(diǎn),再對(duì)節(jié)點(diǎn)進(jìn)行修改,最后直接將其輸出.

#!/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):  
    '''
        判斷某個(gè)節(jié)點(diǎn)是否包含所有傳入?yún)?shù)屬性 
        node: 節(jié)點(diǎn) 
        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):  
    '''
        查找某個(gè)路徑匹配的所有節(jié)點(diǎn) 
        tree: xml樹 
        path: 節(jié)點(diǎn)路徑
    '''  
    return tree.findall(path)  

def get_node_by_keyvalue(nodelist, kv_map):  
    '''
        根據(jù)屬性及屬性值定位符合的節(jié)點(diǎn),返回節(jié)點(diǎn) 
        nodelist: 節(jié)點(diǎn)列表 
        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):  
    '''
        修改/增加 /刪除 節(jié)點(diǎn)的屬性及屬性值 
        nodelist: 節(jié)點(diǎn)列表 
        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):  
    '''
        改變/增加/刪除一個(gè)節(jié)點(diǎn)的文本 
        nodelist:節(jié)點(diǎn)列表 
        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):  
    '''
        新造一個(gè)節(jié)點(diǎn) 
        tag:節(jié)點(diǎn)標(biāo)簽 
        property_map:屬性及屬性值map 
        content: 節(jié)點(diǎn)閉合標(biāo)簽里的文本內(nèi)容 
        return 新節(jié)點(diǎn)
    '''  
    element = Element(tag, property_map)  
    element.text = content  
    return element  

def add_child_node(nodelist, element):  
    '''
        給一個(gè)節(jié)點(diǎn)添加子節(jié)點(diǎn) 
        nodelist: 節(jié)點(diǎn)列表 
        element: 子節(jié)點(diǎn)
    '''  
    for node in nodelist:  
        node.append(element)  

def del_node_by_tagkeyvalue(nodelist, tag, kv_map):  
    '''
        同過屬性及屬性值定位一個(gè)節(jié)點(diǎn),并刪除之 
        nodelist: 父節(jié)點(diǎn)列表 
        tag:子節(jié)點(diǎn)標(biāo)簽 
        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. 找到父節(jié)點(diǎn)  
    nodes = find_nodes(tree, "processers/processer")  

    #B. 通過屬性準(zhǔn)確定位子節(jié)點(diǎn)  
    result_nodes = get_node_by_keyvalue(nodes, {"name":"BProcesser"})  

    #C. 修改節(jié)點(diǎn)屬性  
    change_node_properties(result_nodes, {"age": "1"})  

    #D. 刪除節(jié)點(diǎn)屬性  
    change_node_properties(result_nodes, {"value":""}, True)  

    #3\. 節(jié)點(diǎn)修改  
    #A.新建節(jié)點(diǎn)  
    a = create_node("person", {"age":"15","money":"200000"}, "this is the firest content")  

    #B.插入到父節(jié)點(diǎn)之下  
    add_child_node(result_nodes, a)  

    #4\. 刪除節(jié)點(diǎn)  
    #定位父節(jié)點(diǎn)  
    del_parent_nodes = find_nodes(tree, "processers/services/service")  

    #準(zhǔn)確定位子節(jié)點(diǎn)并刪除之  
    target_del_node = del_node_by_tagkeyvalue(del_parent_nodes, "chain", {"sequency" : "chain1"})  

    #5\. 修改節(jié)點(diǎn)文本  
    #定位節(jié)點(diǎn)  
    text_nodes = get_node_by_keyvalue(find_nodes(tree, "processers/services/service/chain"), {"sequency":"chain3"})  
    change_node_text(text_nodes, "new text")  

    #6\. 輸出到結(jié)果文件  
    write_xml(tree, "./out.xml")  

操作對(duì)象(原始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> 

執(zhí)行程序之后,得到的結(jié)果文件:

<?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>  
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)