XML解析

      在〈XML解析〉中尚無留言

用途

話說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">&#21015;&#21512;&#20341;</td>
            </tr>
            <tr bgcolor="#ffff00" name="tr2">
                <td>hello4</td>
                <td colspan="2">&#27396;&#21512;&#20341;</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)

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *