用途
話說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)