用途
話說JSON在寫法上比較方便, 但遇到大型的文件, 如C# WPF的XAML, Swing的UI Form, 都是以XML的格式儲存. 這些具有複雜的階層關係, 使用JSON是無法讓人輕易的了解.
所以解析XML似乎成了必要的功能. . 那要如何轉換成C#/Java的語言呢, 第一步當然是要先作解析
解析模組
常見解析XML的接口有三個
DOM(Document Object Model) : 將XML解析成一顆樹, 效能差
SAX(Simple API for XML) : 使用事件驅動模型, 效能好, 佔用記憶体少. 但不方便使用
ElementTree(元素樹) : 為上面二個的綜合体, 方便使用, 且效能跟SAX一樣, 且不佔太多的記憶体.
由以上的分析, 盡可能使用ElementTree
Import
在Python 3.3之前, 請使用如下import. cElementTree是使用C語言寫成的, 速度較快. ElementTree則是由純Python寫成. 因有的環境沒有cElementTree, 所以需使用try-except
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
但如果是Python 3.3及以上, 使用如下即可. 它會自動去找cElementTree, 若沒有, 才找ElementTree
import xml.etree.ElementTree as ET
請注意, 開啟新檔案測試時, 檔案名稱千萬別命名為 xml.py, 因為xml.py為系統的套件檔案, xml.etree就是寫在這個檔案裏, 請不要覆蓋這個檔案. 否則會發生 No module named ‘xml.etree’
XML檔案內容
請在專案目錄下新增如下文字檔 index.html
<?xml version="1.0"?>
<html>
<head>
<title>Mahaljsp</title>
</head>
<body>
<table name="table1" width="400dp" align="center" border="1" cellspacing="0" cellpadding="0">
<tr name="tr1" bgcolor="#00ffff">
<td>hello1</td>
<td>hello2</td>
<td>hello3</td>
<td rowspan="2">列合併</td>
</tr>
<tr name="tr2" bgcolor="#ff0000">
<td>hello4</td>
<td colspan="2">欄合併</td>
</tr>
</table>
</body>
</html>
開啟檔案
如下使用ET.ElementTree(file=’xxx’) 即可取得整顆樹tree, 再使用tree.getroot()即可取得根節點
tree=ET.ElementTree(file="index.html")
root=tree.getroot()
print("%s,%s" % (root.tag, root.attrib))
子節點
root為根節點, 若想取得root的下一層子節點, 可使用 root[i], 如下代碼
root=tree.getroot()
for i in range(len(root)):
print("%s,%s" % (root[i].tag, root[i].attrib))
遞迴
如果連同子節點也要搜尋拜訪, 可使用root.iter(),或者是 tree.iter(),如下代碼。 另外iter()也可傳入tag參數, 只列出所需的tag名稱
root=tree.getroot()
for node in root.iter(tag="tr"):
print("%s,%s" % (node.tag, node.attrib))
XPATH
XPATH可以使用絕對路徑由根開始指定所需的節點, 比如 branch/sub-branch. 此路徑可配合如下指令搜尋
tree.find
tree.findall
tree.iterfind
請注意, find只會傳回第一個節點, 不是list, 所以不可以放在 foreach裏面. 但findall及iterfind則傳回list, 才可以放在foreach
import xml.etree.ElementTree as ET
tree = ET.ElementTree(file='index.html')
node= tree.find('body/table/tr')
print("find : %s,%s" % (node.tag, node.attrib))
for node in tree.iterfind('body/table/tr'):
print("findall : %s,%s" % (node.tag, node.attrib))
結果 :
find : tr,{'name': 'tr1', 'bgcolor': '#00ffff'}
findall : tr,{'name': 'tr1', 'bgcolor': '#00ffff'}
findall : tr,{'name': 'tr2', 'bgcolor': '#ff0000'}
find, findall亦可接attrib的屬性, 如 tree.find(‘body/table/tr[@name=”tr2″]’)
“@” 緊接著 “name” 的屬性
import xml.etree.ElementTree as ET
tree = ET.ElementTree(file='xml.xml')
node= tree.find('body/table/tr[@name="tr2"]')
修改XML文檔
修改節點屬性
首先必需使用 find()找到要修改的節點 node,再使用node.set(“name”,”屬”) 新增或修改節點的屬性。
import xml.etree.ElementTree as ET
tree = ET.ElementTree(file='index.html')
node= tree.find('body/table/tr[@name="tr2"]')
node.set("bgcolor", "#ffff00")
for node in tree.iterfind('body/table/tr'):
print("findall : %s,%s" % (node.tag, node.attrib))
結果
findall : tr,{'name': 'tr1', 'bgcolor': '#00ffff'}
findall : tr,{'name': 'tr2', 'bgcolor': '#ffff00'}
新增節點
同樣先使用 find()找到要操作的節點node,再使用ET.SubElement(node, “tag”)
import xml.etree.ElementTree as ET
tree=ET.ElementTree(file="index.html")
node=tree.find('body/table')
tr=ET.SubElement(node, "tr")
td=ET.SubElement(tr,"td")
td.set("colspan","4")
td.text="new cell"
tree.write('test1.xml')
執行後,會多出如下藍色的節點
<html>
<head>
<title>Mahaljsp</title>
</head>
<body>
<table align="center" border="1" cellpadding="0" cellspacing="0" name="table1" width="400dp">
<tr bgcolor="#00ffff" name="tr1">
<td>hello1</td>
<td>hello2</td>
<td>hello3</td>
<td rowspan="2">列合併</td>
</tr>
<tr bgcolor="#ffff00" name="tr2">
<td>hello4</td>
<td colspan="2">欄合併</td>
<tr><td colspan="4">new cell</td></tr></tr>
</table>
</body>
</html>
另外一種方式,就是使用ET.Element(“tr”)產生節點tr,再使用父節點 node.extend((tr,))加入node之下
import xml.etree.ElementTree as ET tree=ET.ElementTree(file="index.html") node=tree.find('body/table') tr=ET.Element("tr") td1=ET.Element("td") td1.text="new cell 1" td2=ET.Element("td") td2.set("colspan","3") td2.text="new cell2" tr.extend((td1,td2)) node.extend((tr,)) tree.write('test2.html')
刪除節點
使用 find()找到要刪除的節點(div1)及其父節點(node),再使用父節點 node 的刪除功能 : node.remove(div1)
請注意, 不可以使用網路上的del, 而且不可以使用tree.remove(), 因為tree沒有remove()方法
import xml.etree.ElementTree as ET
tree=ET.ElementTree(file="index.html")
node=tree.find('body/table')
tr=tree.find("body/table/tr[@name='tr2']")
node.remove(tr)
tree.write("test2.html")
結果如下
<?xml version="1.0"?>
<html>
<head>
<title>Mahaljsp</title>
</head>
<body>
<table name="table1" width="400dp" align="center" border="1" cellspacing="0" cellpadding="0">
<tr name="tr1" bgcolor="#00ffff">
<td>hello1</td>
<td>hello2</td>
<td>hello3</td>
<td rowspan="2">列合併</td>
</tr>
<tr name="tr2" bgcolor="#ff0000">
<td>hello4</td>
<td colspan="2">欄合併</td>
</tr>
</table>
</body>
</html>
匯出檔案
tree.write(“test.xml”). 請注意, 只有tree才可以匯出
import xml.etree.ElementTree as ET
tree = ET.ElementTree(file='xml.xml')
root=tree.getroot()
node= root.find('branch[@name="invalid"]')
root.remove(node)
for node in root.iter():
print("%s,%s" % (node.tag, node.attrib))
tree.write("test.xml")
完全列印
tree.write()會將整個xml寫在一行之中,甚難閱讀。若要寫入具有換行及縮排的效果,需使用minidom 編排字串,再由file寫入。
from xml.dom import minidom
xmlstr = minidom.parseString(ET.tostring(html)).toprettyxml(indent=" ")
with open("test5.html", "w") as f:
f.write(xmlstr)
建立xml檔
底下代碼, 用來產生一個網頁的html檔
import xml.etree.ElementTree as ET
html=ET.Element("html")
head=ET.Element("head")
title=ET.SubElement(head,"title")
title.text="Mahaljsp Web"
body=ET.Element("body")
body.text="Test Web"
div1=ET.SubElement(body, "div")
div1.set("style","background-color:pink;")
div1.text="區塊1"
div2=ET.SubElement(body, "div")
div2.text="區塊2"
html.extend((head, body))
tree=ET.ElementTree(html)
#tree.write("index.html")
from xml.dom import minidom
xmlstr = minidom.parseString(ET.tostring(html)).toprettyxml(indent=" ")
with open("test5.html", "w") as f:
f.write(xmlstr)
