參考網址:
菜鳥教程提供了基本的XML編程接口DOM、SAX,以及輕量級ElementTree的簡易概念說明和一些示例。DOM是一種跨語言的XML解析機制,通過將整個XML在內存中解析為一個樹來操作,ElementTree未做太多介紹,你可以到官網網址查看其詳細的方法釋義。
ElementTree是Python中最快捷的XML解析方式,可以看做一個輕量級的DOM,本文主要講ElementTree,ElementTree在解析XML時非常方便,DOM比較笨重但是功能齊全,例如ElementTree處理XML注釋時就很不方便(詳見https://bugs.python.org/issue8277),此時用DOM比較好。
API名稱:
from xml.etree import ElementTree as ET
概念定義:
<country name="Liechtenstein">
<rank>1</rank>
<year>2008</year>
<gdppc>141100</gdppc>
<neighbor name="Austria" direction="E"/>
<neighbor name="Switzerland" direction="W"/>
</country>
我們把<country>xxx</contry>這種結構稱為一個element,country稱作element的tag,<></>之間的內容稱作element的text或data,<>中的name稱作element的attrib,而整個XML樹被稱作ElementTree。
element是一個名為xml.etree.ElementTree.Element的類,其描述為:
class xml.etree.ElementTree.Element(tag, attrib={}, **extra)
此類的所有屬性和方法查看:
https://docs.python.org/2/library/xml.etree.elementtree.html#element-objects
方法釋義:
讀取XML數據:
--讀取XML文件
import xml.etree.ElementTree as ET
tree = ET.parse('country_data.xml')
root = tree.getroot()
--讀取XML字符串
root = ET.fromstring(country_data_as_string)
--獲取element object的四大屬性tag、text、attrib以及tail
root.tag #root element的tag
root.text #root element的text
root.attrib #root element本身的attrib,dict格式的
root.tail #root element的tag結束到下一個tag之間的text
--通過DICT邏輯獲取樹形結構的text,表示第一個child的第二個child element的text
root[0][1].text
element object的方法:
Element.iter(tag) --遍歷當前element樹所有子節點的element(無論是子節點還是子節點的子節點),找到符合指定tag名的所有element,如果tag為空則遍歷當前element樹,返回所有節點element(包含當前父節點)。2.7和3.2之前的版本無此方法,可以用getiterator()代替。 Element.findall(tag) --遍歷當前節點的直接子節點,找到符合指定tag名的element,返回由element組成的list Element.find(tag) --遍歷當前節點的直接子節點,找到符合指定tag名的第一個element Element.get(key) --在當前element中獲取符合指定attrib名的value ...其他方法參考官網
修改XML內容:
ElementTree.write(file, encoding="us-ascii", xml_declaration=None, default_namespace=None, method="xml") --將之前的修改寫入XML
Element.set(key,value) --設置element attrib
Element.append(subelement) --新增一個子element,extends(subelements)是3.2的新增用法,輸入參數必須是一個element序列
Element.remove(subelement) --刪除指定tag的element
示例:
>>> for rank in root.iter('rank'):
... new_rank = int(rank.text) + 1
... rank.text = str(new_rank)
... rank.set('updated', 'yes')
...
>>> tree.write('output.xml')
處理含有Namespaces的XML文件:
--有一個如下的XML字符串:
<?xml version="1.0"?>
<actors xmlns:fictional="http://characters.example.com"
xmlns="http://people.example.com">
<actor>
<name>John Cleese</name>
<fictional:character>Lancelot</fictional:character>
<fictional:character>Archie Leach</fictional:character>
</actor>
<actor>
<name>Eric Idle</name>
<fictional:character>Sir Robin</fictional:character>
<fictional:character>Gunther</fictional:character>
<fictional:character>Commander Clement</fictional:character>
</actor>
</actors>
其中包含fictional和default兩個命名空間,這意味fictional:xxx格式的tags、attributes都會被自動擴展為{uri}xxx格式。而如果還定義了默認命名空間xmlns,那么所有無前綴的tags也會被擴展為{url}xxx格式。
有兩種將此類XML處理為普通格式的方法:
方法一:在匹配時直接手動加上{uri}前綴
root = fromstring(xml_text)
for actor in root.findall('{http://people.example.com}actor'):
name = actor.find('{http://people.example.com}name')
print name.text
for char in actor.findall('{http://characters.example.com}character'):
print ' |-->', char.text
方法二:創建自己的namespace別名(其實只是在ns uri很長時可以少寫點,實質並沒有效率提升)
ns = {'real_person': 'http://people.example.com','role': 'http://characters.example.com'}
for actor in root.findall('real_person:actor', ns):
name = actor.find('real_person:name', ns)
print name.text
for char in actor.findall('role:character', ns):
print ' |-->', char.text
--兩種方式的輸出結果都是:
John Cleese
|--> Lancelot
|--> Archie Leach
Eric Idle
|--> Sir Robin
|--> Gunther
|--> Commander Clement
一個比較proxool.xml文件的示例代碼:
# -*- coding:utf-8 -*-
# 用於進行配置文件的差異比較,2.7和3.2之前element沒有iter()的遍歷方法可以用getiterator()代替
import sys
from xml.etree import ElementTree as ET
from xml.dom import minidom
# 定義新舊XML文件分別為輸入參數1和2
old_file = sys.argv[1]
new_file = sys.argv[2]
# 定義將新增tag加入舊XML文件的方法
def modify_xml(old_file,new_file):
if not new_file:
sys.exit(0)
tree_old = ET.parse(old_file) # 解析出整個ElementTree
tree_new = ET.parse(new_file)
global root # 定義全局變量root,只解析一次方便prettify_xml方法調用
root = tree_old.getroot()
root_old = tree_old.getroot().find("proxool") # 定位舊XML父節點proxool
root_new = tree_new.getroot().find("proxool")
old_dict = {} # 定義舊XML文件的tag/text字典
new_dict = {}
for e in root_old.getiterator(): # 遍歷proxool樹的所有節點element,包含其作為父節點的自身
# text為空時不能使用replace方法,因此加上判斷;if e.text不能排除空字符' ',只能過濾none和''因此加上strip()過濾
if e.text and e.tag != 'proxool' and e.text.strip() != '':
old_dict[e.tag] = e.text.replace("\n", "").replace("\t", "")
for e in root_new.getiterator():
if e.text and e.tag != 'proxool' and e.text.strip() != '':
new_dict[e.tag] = e.text.replace("\n", "").replace("\t", "")
# 至此新舊XML文件的tag/text已經作為字典的元素存在了old_dict和new_dict中,只要比較這兩個字典就可以拿到新增tag
for tag,text in new_dict.items():
if not old_dict.get(tag): # 當舊XML中找不到對應的tag時,進行tag新增操作
new_tag = ET.Element(tag) # 構造一個element
new_tag.text = text # 設置此element的text
root_old.append(new_tag) #將此element加入root_old節點下作為其子節點
else:
pass # 只為美觀,可以不寫else
tree_old.write(old_file + "_fixed",encoding="UTF-8") # 最后將append的整個ElementTree寫入舊XML_fixed文件中,這樣注釋會丟失
# 新寫入的XML項不是那么美觀,再美化一下(發現結果更難看了,有待優化)
def prettify_xml(filename):
strTree = ET.tostring(root) #使用全局變量root
new_strTree = minidom.parseString(strTree).toprettyxml()
with open(filename,'w') as output:
output.write(new_strTree)
# 執行函數
modify_xml(old_file,new_file)
prettify_xml(old_file + "_fixed")
# Ps:后來發現使用ElementTree解析的XML文件很難美化,且不能處理注釋,所以轉用minidom處理XML文件了,詳見《Python XML解析之DOM》
