詳解Python中DOM方法的動態(tài)性
文檔對象模型
xml.dom 模塊對于 Python 程序員來說,可能是使用 XML 文檔時功能最強(qiáng)大的工具。不幸的是,XML-SIG 提供的文檔目前來說還比較少。W3C 語言無關(guān)的 DOM 規(guī)范填補(bǔ)了這方面的部分空白。但 Python 程序員最好有一個特定于 Python 語言的 DOM 的快速入門指南。本文旨在提供這樣一個指南。在 上一篇專欄文章 中,某些樣本中使用了樣本 quotations.dtd 文件,并且這些文件可以與本文中的代碼樣本檔案文件一起使用。
有必要了解 DOM 的確切含義。這方面,正式解釋非常好:
“文檔對象模型”是平臺無關(guān)和語言無關(guān)的接口,它允許程序和腳本動態(tài)訪問和更新文檔的內(nèi)容、結(jié)構(gòu)和樣式??梢赃M(jìn)一步處理文檔,而處理的結(jié)果也可以合并到已顯示的頁面中。(萬維網(wǎng)聯(lián)盟 DOM 工作組)
DOM 將 XML 文檔轉(zhuǎn)換成樹 -- 或森林 -- 表示。萬維網(wǎng)聯(lián)盟 (W3C) 規(guī)范給出了一個 HTML 表的 DOM 版本作為例子。

如上圖所示,DOM 從一個更加抽象的角度定義了一組可以遍歷、修剪、改組、輸出和操作樹的方法,而這種方法要比 XML 文檔的線性表示更為便利。
將 HTML 轉(zhuǎn)換成 XML
有效的 HTML 幾乎就是有效的 XML,但又不完全相同。這里有兩個主要的差異,XML 標(biāo)記是區(qū)分大小寫的,并且所有 XML 標(biāo)記都需要一個顯式的結(jié)束符號(作為結(jié)束標(biāo)記,而這對于某些 HTML 標(biāo)記是可選的;例如: <img src="X.png" /> )。使用 xml.dom 的一個簡單示例就是使用 HtmlBuilder() 類將 HTML 轉(zhuǎn)換成 XML。
try_dom1.py
"""Convert a valid HTML document to XML
USAGE: python try_dom1.py < infile.html > outfile.xml
"""
import
sys
from
xml.dom
import
core
from
xml.dom.html_builder
import
HtmlBuilder
# Construct an HtmlBuilder object and feed the data to it
b = HtmlBuilder()
b.feed(sys.stdin.read())
# Get the newly-constructed document object
doc = b.document
# Output it as XML
print
doc.toxml()
HtmlBuilder() 類很容易實現(xiàn)它繼承的部分基本 xml.dom.builder 模板的功能,它的源碼值得研究。然而,即使我們自己實現(xiàn)了模板功能,DOM 程序的輪廓還是相似的。在一般情況下,我們將用一些方法構(gòu)建一個 DOM 實例,然后對該實例進(jìn)行操作。DOM 實例的 .toxml() 方法是一種生成 DOM 實例的字符串表示的簡單方法(在以上的情況中,只要在生成后將它打印出來)。
將 Python 對象轉(zhuǎn)換成 XML
Python 程序員可以通過將任意 Python 對象導(dǎo)出為 XML 實例來實現(xiàn)相當(dāng)多的功能和通用性。這就允許我們以習(xí)慣的方式來處理 Python 對象,并且可以選擇最終是否使用實例屬性作為生成 XML 中的標(biāo)記。只需要幾行(從 building.py 示例派生出),我們就可以將 Python“原生”對象轉(zhuǎn)換成 DOM 對象,并對包含對象的那些屬性執(zhí)行遞歸處理。
try_dom2.py
"""Build a DOM instance from scratch, write it to XML
USAGE: python try_dom2.py > outfile.xml
"""
import
types
from
xml.dom
import
core
from
xml.dom.builder
import
Builder
# Recursive function to build DOM instance from Python instance
defobject_convert
(builder, inst):
# Put entire object inside an elem w/ same name as the class.
builder.startElement(inst.__class__.__name__)
for
attr
in
inst.__dict__.keys():
if
attr[0] ==
'_':
# Skip internal attributes
continue
value = getattr(inst, attr)
if
type(value) == types.InstanceType:
# Recursively process subobjects
object_convert(builder, value)
else
:
# Convert anything else to string, put it in an element
builder.startElement(attr)
builder.text(str(value))
builder.endElement(attr)
builder.endElement(inst.__class__.__name__)
if
__name__ ==
'__main__':
# Create container classes
classquotations
:
pass
classquotation
:
pass
# Create an instance, fill it with hierarchy of attributes
inst = quotations()
inst.title =
"Quotations file (not quotations.dtd conformant)"
inst.quot1 = quot1 = quotation()
quot1.text =
"""'"is not a quine" is not a quine' is a quine"""
quot1.source =
"Joshua Shagam, kuro5hin.org"
inst.quot2 = quot2 = quotation()
quot2.text =
"Python is not a democracy. Voting doesn't help. "+\
"Crying may..."
quot2.source =
"Guido van Rossum, comp.lang.python"
# Create the DOM Builder
builder = Builder()
object_convert(builder, inst)
print
builder.document.toxml()
函數(shù) object_convert() 有一些限制。例如,不可能用以上的過程生成符合 XML 文檔的 quotations.dtd:#PCDATA 文本不能直接放到 quotation 類中,而只能放到類的屬性中(如 .text )。一個簡單的變通方法就是讓 object_convert() 以特殊方式處理一個帶有名稱的屬性,例如 .PCDATA ??梢杂酶鞣N方法使對 DOM 的轉(zhuǎn)換變得更巧妙,但該方法的妙處在于我們可以從整個 Python 對象開始,以簡明的方式將它們轉(zhuǎn)換成 XML 文檔。
還應(yīng)值得注意的是在生成的 XML 文檔中,處于同一個級別的元素沒有什么明顯的順序關(guān)系。例如,在作者的系統(tǒng)中使用特定版本的 Python,源碼中定義的第二個 quotation 在輸出中卻第一個出現(xiàn)。但這種順序關(guān)系在不同的版本和系統(tǒng)之間會改變。Python 對象的屬性并不是按固定順序排列的,因此這種特性就具有意義。對于與數(shù)據(jù)庫系統(tǒng)相關(guān)的數(shù)據(jù),我們希望它們具有這種特性,但是對于標(biāo)記為 XML 的文章卻顯然不希望具有這種特性(除非我們想要更新 William Burroughs 的 "cut-up" 方法)。
將 XML 文檔轉(zhuǎn)換成 Python 對象
從 XML 文檔生成 Python 對象就像其逆向過程一樣簡單。在多數(shù)情況下,用 xml.dom 方法就可以了。但在某些情況下,最好使用與處理所有“類屬”Python 對象相同的技術(shù)來處理從 XML 文檔生成的對象。例如,在以下的代碼中,函數(shù) pyobj_printer() 也許是已經(jīng)用來處理任意 Python 對象的函數(shù)。
try_dom3.py
"""Read in a DOM instance, convert it to a Python object
"""
from
xml.dom.utils
import
FileReader
classPyObject
:
pass
defpyobj_printer
(py_obj, level=0):
"""Return a "deep" string description of a Python object"""
from
string
import
join, split
import
types
descript =
''
for
membname
in
dir(py_obj):
member = getattr(py_obj,membname)
if
type(member) == types.InstanceType:
descript = descript + (
' '*level) +
'{'+membname+
'}\n'
descript = descript + pyobj_printer(member, level+3)
elif
type(member) == types.ListType:
descript = descript + (
' '*level) +
'['+membname+
']\n'
for
i
in
range(len(member)):
descript = descript+(
' '*level)+str(i+1)+
': '+ \
pyobj_printer(member[i],level+3)
else
:
descript = descript + membname+
'='
descript = descript + join(split(str(member)[:50]))+
'...\n'
return
descript
defpyobj_from_dom
(dom_node):
"""Converts a DOM tree to a "native" Python object"""
py_obj = PyObject()
py_obj.PCDATA =
''
for
node
in
dom_node.get_childNodes():
if
node.name ==
'#text':
py_obj.PCDATA = py_obj.PCDATA + node.value
elif
hasattr(py_obj, node.name):
getattr(py_obj, node.name).append(pyobj_from_dom(node))
else
:
setattr(py_obj, node.name, [pyobj_from_dom(node)])
return
py_obj
# Main test
dom_obj = FileReader(
"quotes.xml").document
py_obj = pyobj_from_dom(dom_obj)
if
__name__ ==
"__main__":
print
pyobj_printer(py_obj)
這里的關(guān)注焦點應(yīng)該是函數(shù) pyobj_from_dom() ,特別是起實際作用的 xml.dom 方法 .get_childNodes() 。在 pyobj_from_dom() 中,我們直接抽取標(biāo)記之間的所有文本,將它放到保留屬性 .PCDATA 中。對于任何遇到的嵌套標(biāo)記,我們創(chuàng)建一個新屬性,其名稱與標(biāo)記匹配,并將一個列表分配給該屬性,這樣就可以潛在地包含在在父代塊中多次出現(xiàn)的標(biāo)記。當(dāng)然,使用列表要維護(hù)在 XML 文檔中遇到的標(biāo)記的順序。
除了使用舊的 pyobj_printer() 類屬函數(shù)(或者,更復(fù)雜和健壯的函數(shù))之外,我們可以使用正常的屬性記號來訪問 py_obj 的元素。
Python 交互式會話
>>>
from
try_dom3
import
*
>>> py_obj.quotations[0].quotation[3].source[0].PCDATA
'Guido van Rossum, '
重新安排 DOM 樹
DOM 的一大優(yōu)點是它可以讓程序員以非線性方式對 XML 文檔進(jìn)行操作。由相匹配的開/關(guān)標(biāo)記括起的每一塊都只是 DOM 樹中的一個“節(jié)點”。當(dāng)以類似于列表的方式維護(hù)節(jié)點以保留順序信息時,則順序并沒有什么特殊之處,也并非不可改變。我們可以輕易地剪下某個節(jié)點,嫁接到 DOM 樹的另一個位置(如果 DTD 允許,甚至嫁接到另一層上)?;蛘咛砑有碌墓?jié)點、刪除現(xiàn)有節(jié)點,等等。
try_dom4.py
"""Manipulate the arrangement of nodes in a DOM object
"""
from
try_dom3
import
*
#-- Var 'doc' will hold the single <quotations> "trunk"
doc = dom_obj.get_childNodes()[0]
#-- Pull off all the nodes into a Python list
# (each node is a <quotation> block, or a whitespace text node)
nodes = []
while
1:
try
: node = doc.removeChild(doc.get_childNodes()[0])
except
:
break
nodes.append(node)
#-- Reverse the order of the quotations using a list method
# (we could also perform more complicated operations on the list:
# delete elements, add new ones, sort on complex criteria, etc.)
nodes.reverse()
#-- Fill 'doc' back up with our rearranged nodes
for
node
in
nodes:
# if second arg is None, insert is to end of list
doc.insertBefore(node, None)
#-- Output the manipulated DOM
print
dom_obj.toxml()
如果我們將 XML 文檔只看作一個文本文件,或者使用一個面向序列的模塊(如 xmllib 或 xml.sax),那么在以上幾行中執(zhí)行對 quotation 節(jié)點的重新安排操作將引出一個值得考慮的問題。然而如果使用 DOM,則問題就如同對 Python 列表執(zhí)行的任何其它操作一樣簡單。
相關(guān)文章
python數(shù)據(jù)可視化Seaborn繪制山脊圖
這篇文章主要介紹了利用python數(shù)據(jù)可視化Seaborn繪制山脊圖,山脊圖一般由垂直堆疊的折線圖組成,這些折線圖中的折線區(qū)域間彼此重疊,此外它們還共享相同的x軸.下面來看看具體的繪制過程吧,需要的小伙伴可以參考一下2022-01-01
PyTorch 導(dǎo)數(shù)應(yīng)用的使用教程
這篇文章主要介紹了PyTorch 導(dǎo)數(shù)應(yīng)用的使用教程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
利用Python操作MongoDB數(shù)據(jù)庫的詳細(xì)指南
MongoDB是由C++語言編寫的非關(guān)系型數(shù)據(jù)庫,是一個基于分布式文件存儲的開源數(shù)據(jù)庫系統(tǒng),其內(nèi)容存儲形式類似JSON對象,下面這篇文章主要給大家介紹了關(guān)于利用Python操作MongoDB數(shù)據(jù)庫的相關(guān)資料,需要的朋友可以參考下2022-06-06
python數(shù)據(jù)挖掘需要學(xué)的內(nèi)容
在本篇文章中我們給大家整理了關(guān)于python數(shù)據(jù)挖掘需要學(xué)什么的知識點指南,有興趣的朋友們跟著參考下。2019-06-06
使用opencv相關(guān)函數(shù)確定圖片中的直線問題
這篇文章主要介紹了使用opencv相關(guān)函數(shù)確定圖片中的直線問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11
django 解決manage.py migrate無效的問題
今天小編就為大家分享一篇django 解決manage.py migrate無效的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-05-05
Python實現(xiàn)大數(shù)據(jù)收集至excel的思路詳解
這篇文章主要介紹了Python實現(xiàn)大數(shù)據(jù)收集至excel的思路,本文通過完整代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2020-01-01
Python解決非線性規(guī)劃中經(jīng)濟(jì)調(diào)度問題
Scipy是Python算法庫和數(shù)學(xué)工具包,包括最優(yōu)化、線性代數(shù)、積分、插值、特殊函數(shù)、傅里葉變換等模塊。scipy.optimize模塊中提供了多個用于非線性規(guī)劃問題的方法,適用于不同類型的問題。本文將利用起解決經(jīng)濟(jì)調(diào)度問題,感興趣的可以了解一下2022-05-05

