DOM說明:
DOM:Document Object Model API
DOM是一種跨語言的XML解析機制,DOM把整個XML文件或字符串在內存中解析為樹型結構方便訪問。
xml.dom.minidom就是DOM在Python中實現,本文主要結合minidom解釋DOM架構。
API導入:
from xml.dom.minidom import parse from xml.dom.minidom import parseString import xml.dom.minidom dom和etree是xml package目錄下的兩個subpackage,minidom和ElementTree是dom和etree下的兩個module文件,以.py后綴,其中定義了一系列的類和方法。 Document.documentElement相當於Etree中的tree.getroot()用於獲取整個樹唯一的根節點
概念解析:
xml.dom中包含以下類:
1.DOMImplementation 2.Node Node是最重要的類,XML被解析為一個樹,所有的節點都是都是node的子類,這些節點可以是element、comments等等,官網列出的節點類型就有: ELEMENT_NODE, ATTRIBUTE_NODE, TEXT_NODE, CDATA_SECTION_NODE, ENTITY_NODE, PROCESSING_INSTRUCTION_NODE, COMMENT_NODE,DOCUMENT_NODE, DOCUMENT_TYPE_NODE, NOTATION_NODE,每個節點有一個數字表示,只要是node類型就可以使用node.nodeType來判斷他屬於哪一種node,例如if child.nodeType==child.ELEMENT_NODE:或者if child.nodeType==1: --兩者等價 3.NodeList --通過getElementsByTagName()方法返回的nodelist,此方法只有element和document兩個類有。 4.DocumentType 5.Document --整個XML文件解析樹,包含所有element、attribute、comments、text等等,也是node的子類。 6.Element 7.Attr --element的屬性,這個類型的node只能由Element.getAttributeNode(attrname)來獲取,無法遍歷獲取,而且其既不是element node的子節點也不是兄弟節點。幾乎從無必要獲取此節點,直接使用element類的getAttribute(attrname)來得到屬性的值即可。 8.Comment --comment節點,表示XML文件注釋節點 9.Text --xml.etree.ElementTree中的text表示的是element中的內容,而這里的text類型表示一個node,這個node可以是element中的data節點也可以是element之間的換行和制表符(\n\t),如果是element的data內容那么此text是element的唯一子節點,通過childNodes[0].data或firstChild.data獲取element內容,如果是換行制表符那么此節點element的兄弟節點。 10.ProcessingInstruction 除node類之外,對於XML解析最重要的就是Document類和Element、text、Comment、Attr等類,前者配合parse()或parseString()將xml文件或字符串在內存中實例化為一個tree(document類型),后邊的類用於對XML樹做各種操作和查詢。
鑒於幾乎所有的可操作對象類都是繼承於node類,這里貼一下node的各種屬性和方法的鏈接:
另外再列出node一些常見的屬性和方法: Node.nodeType --詳見上邊對Node類的解釋 Node.attributes --只有element類型的node才有此屬性 Node.childNodes --返回節點的子節點nodelist,與通過getElementsByTagName()獲取nodelist的區別在於此方法只返回直接子節點而非全部子節點,此外這兩個方法的最大區別是:childNodes返回的是所有子節點的集合,而getElementsByTagName(tagName)必須指定tagName。 Node.previousSibling --node的左兄弟節點,如果沒有則返回none Node.nextSibling --node的右兄弟節點,如果沒有則返回none Node.nodeName --不常用,因為繼承於node的各種類都有自己的更便於識別的name屬性,例如element.tagName Node.appendChild(newChild)
另:如果要熟練的使用minidom API,那么請務必將https://docs.python.org/2/library/xml.dom.html 熟讀,以上列出的各種繼承於node的類都有一些自己獨特的屬性和方法,除了熟悉node類之外,熟悉這些繼承子類的方法也是很有必要的。
XML文件解析示例:
--有一個如下的XML文件:proxool.xml: <?xml version="1.0" encoding="utf-8"?> <something-else-entirely> <proxool> <alias>myPool</alias> <!-- mysql 連接配置,注意修改database_hostname為相應的數據庫主機名、或IP地址 --> <driver-url> jdbc:mysql://dbsrv:3306/TEST?useUnicode=true&characterEncoding=UTF8 </driver-url> <driver-class>com.mysql.jdbc.Driver</driver-class> <!-- 用戶名、密碼 --> <driver-properties> <property name="user" value="leo" /> <property name="password" value="leo" /> </driver-properties> <!--自動偵察各個連接狀態的時間間隔(毫秒),偵察到空閑的連接就馬上回收,超時的銷毀 --> <house-keeping-sleep-time>30000</house-keeping-sleep-time> <house-keeping-test-sql>select CURRENT_DATE from dual </house-keeping-test-sql> <!--最大連接數(默認5個),超過了這個連接數,再有請求時,就排在隊列中等候,最大的等待請求數由maximum-new-connections決定 --> <maximum-connection-count>120</maximum-connection-count> <!--最小連接數(默認2個) --> <minimum-connection-count>5</minimum-connection-count> <!--沒有空閑連接可以分配而在隊列中等候的最大請求數,超過這個請求數的用戶連接就不會被接受,該參數已經不建議使用,由simultaneous-build-throttle替代 --> <!--一個活動連接最大活動時間默認5分鍾 --> <maximum-active-time>3600000</maximum-active-time> <!--最少保持的空閑連接數(默認2個),如果當前的連接池中的可用連接少於這個數值, 新的連接將被建立 --> <prototype-count>5</prototype-count> <!--可一次建立的最大連接數 --> <simultaneous-build-throttle>20</simultaneous-build-throttle> <!--如果為true,那么每個被執行的SQL語句將會在執行期被log記錄 --> <trace>false</trace> </proxool> </something-else-entirely>
現在將其中的內容解析為如下格式:
***** 描述:最大連接數(默認5個),超過了這個連接數,再有請求時,就排在隊列中等候,最大的等待請求數由maximum-new-connections決定 配置項:maximum-connection-count 配置值:120 ***** 描述:xxx 配置項:xxx 配置值:xxx ***** ......
代碼如下:
# -*- coding:utf-8 -*- # 本腳本適用於Python2和3 from xml.dom.minidom import parse import xml.dom.minidom import sys # file = sys.argv[1] file = "/root/proxool.xml" # 先寫一個判斷節點是否包含element類型子節點的判斷函數 def has_element_child(nodename): has_element_child = 0 for child in nodename.childNodes: if child.nodeType==1: has_element_child += 1 return has_element_child # 定義解析示例XML文件的方法 def parse_xml(file): if not file: sys.exit(0) tree = parse(file) # document類型的解析樹 root = tree.getElementsByTagName('proxool')[0] # 將父節點定位到proxool element for child in root.childNodes: if child.nodeType==child.ELEMENT_NODE and has_element_child(child)==0: # 當node為element類型,且無element類型的子節點時 print u'配置項'+": %s" % child.tagName print u'配置值'+": %s" % child.firstChild.data.strip() elif child.nodeType==child.ELEMENT_NODE and has_element_child(child)>0: # 當節點包含element類型子節點時 for child_child in child.childNodes: if child_child.nodeType==child.ELEMENT_NODE: print u'配置項'+": %s" % child_child.getAttribute('name') print u'配置值'+": %s" % child_child.getAttribute('value') elif child.nodeType==child.COMMENT_NODE: # 當node為comment類型時 print "*****" print u'描述'+": %s" % child.data else: pass # 處理示例XML文件 parse_xml(file)
XML文件比較修改示例:
minidom相比於DOM API最大的差別就是添加了node.writexml()、node.toprettyxml()等方法,這兩個方法可以將你對XML解析樹作出的修改寫入文件中,現在我們將proxool.xml copy到proxool.xml.new中,並在proxool節點下添加一個子節點<new_tag name="Leo">For_Test</new_tag>,我們要比較新XML文件中比舊XML文件新增的配置項,對舊XML的配置項不做修改,代碼如下:
# -*- coding:utf-8 -*- # 本腳本適用於Python2和3 from xml.dom.minidom import parse import xml.dom.minidom import sys reload(sys) sys.setdefaultencoding("utf-8") old_file = sys.argv[1] new_file = sys.argv[2] # 先寫一個判斷節點是否包含element類型子節點的判斷函數 def has_element_child(nodename): has_element_child = 0 for child in nodename.childNodes: if child.nodeType==1: has_element_child += 1 return has_element_child # 定義解析示例XML文件的方法 def match_xml(old_file,new_file): if not new_file: sys.exit(0) tree_old = parse(old_file) # document類型的解析樹 tree_new = parse(new_file) root_old = tree_old.getElementsByTagName('proxool')[0] # 將父節點定位到proxool root_new = tree_new.getElementsByTagName('proxool')[0] old_dict = {} # 定義舊XML文件的tag和data的字典 new_dict = {} for child in root_old.childNodes: #將tagName和data存入old_dict{}中 if child.nodeType==child.ELEMENT_NODE and has_element_child(child)==0: # 當node為element類型,且無element類型的子節點時 old_dict[child.tagName] = child.firstChild.data.replace("\n", "").replace("\t", "") for child in root_new.childNodes: if child.nodeType==child.ELEMENT_NODE and has_element_child(child)==0: new_dict[child.tagName] = child.firstChild.data.replace("\n", "").replace("\t", "") for tag,data in new_dict.items(): if not old_dict.get(tag): # 當舊XML中找不到對應的tag時,進行tag新增操作 new_element=tree_new.getElementsByTagName(tag) for child in new_element: root_old.appendChild(child) # 新增element節點 with open('proxool_modified.xml','w') as f: tree_old.writexml(f) f.close # 處理示例XML文件 match_xml(old_file,new_file)
--比較XML文件: # python xml_match_dom.py proxool.xml proxool.xml.new --然后就可以在proxool_modified.xml中看到新的XML內容了