VOC數據集的標注文件都是xml格式,最近需要頻繁處理xml文件的讀取和更新,整理下python處理xml文件的方法。
XML 指可擴展標記語言(eXtensible Markup Language), 被設計用來傳輸和存儲數據。python中有三個模塊解析xml文件:DOM, ElementTree,SAX
1. DOM(Document Object Model)
DOM是個跨平台的標准模型,W3C定義了DOM模型里的概念。DOM將XML數據在內存中解析成一個樹,通過對樹的操作來操作XML。python的xml.dom.minimom模塊實現了DOM
1.1 DOM寫入xml文件
DOM寫入xml文件主要是創建dom樹,然后創建根結點,創建子節點並加入到根節點,最后將整個dom樹寫入文件,相關API如下:
DOM寫入相關API
doc = minidom.Document() #創建樹 doc.createElement("folder") #創建名為folder的結點 doc.createTextNode('user') #創建文本結點,文本為 user root_node.appendChild(folder_node) #root_node結點添加folder_node為子節點
book_node.setAttribute('price','199') #book_node設置屬性值
# 每一個結點對象(包括dom對象本身)都有輸出XML內容的方法,如:toxml()--字符串, toprettyxml()--美化樹形格式。 doc.toxml(encoding="utf-8") # 輸出字符串 doc.toprettyxml(indent='', addindent='\t', newl='\n', encoding="utf-8")) #輸出帶格式的字符串 doc.writexml() #將prettyxml字符串寫入文件
下面為寫入一個VOC標注文件示例代碼

def write_xml(): #1. 創建dom樹對象 doc = minidom.Document() #2. 創建根結點,並用dom對象添加根結點 root_node = doc.createElement("annotation") doc.appendChild(root_node) #3. 創建結點,結點包含一個文本結點, 再將結點加入到根結點 folder_node = doc.createElement("folder") folder_value = doc.createTextNode('user') folder_node.appendChild(folder_value) root_node.appendChild(folder_node) filename_node = doc.createElement("filename") filename_value = doc.createTextNode('0000001.jpg') filename_node.appendChild(filename_value) root_node.appendChild(filename_node) path_node = doc.createElement("path") path_value = doc.createTextNode('/home') path_node.appendChild(path_value) root_node.appendChild(path_node) source_node = doc.createElement("source") database_node = doc.createElement("database") database_node.appendChild(doc.createTextNode("Unknown")) source_node.appendChild(database_node) root_node.appendChild(source_node) size_node = doc.createElement("size") for item, value in zip(["width", "height", "depth"], [1920, 1080, 3]): elem = doc.createElement(item) elem.appendChild(doc.createTextNode(str(value))) size_node.appendChild(elem) root_node.appendChild(size_node) seg_node = doc.createElement("segmented") seg_node.appendChild(doc.createTextNode(str(0))) root_node.appendChild(seg_node) obj_node = doc.createElement("object") name_node = doc.createElement("name") name_node.appendChild(doc.createTextNode("boat")) obj_node.appendChild(name_node) pose_node = doc.createElement("pose") pose_node.appendChild(doc.createTextNode("Unspecified")) obj_node.appendChild(pose_node) trun_node = doc.createElement("truncated") trun_node.appendChild(doc.createTextNode(str(1))) obj_node.appendChild(trun_node) trun_node = doc.createElement("difficult") trun_node.appendChild(doc.createTextNode(str(0))) obj_node.appendChild(trun_node) bndbox_node = doc.createElement("bndbox") for item, value in zip(["xmin", "ymin", "xmax", "ymax"], [103, 1, 634, 402]): elem = doc.createElement(item) elem.appendChild(doc.createTextNode(str(value))) bndbox_node.appendChild(elem) obj_node.appendChild(bndbox_node) root_node.appendChild(obj_node) with open("0000001.xml", "w", encoding="utf-8") as f: # 4.writexml()第一個參數是目標文件對象,第二個參數是根節點的縮進格式,第三個參數是其他子節點的縮進格式, # 第四個參數制定了換行格式,第五個參數制定了xml內容的編碼。 doc.writexml(f, indent='', addindent='\t', newl='\n', encoding="utf-8") # 每一個結點對象(包括dom對象本身)都有輸出XML內容的方法,如:toxml()--字符串, toprettyxml()--美化樹形格式。 # print(doc.toxml(encoding="utf-8")) # 輸出字符串 # print(doc.toprettyxml(indent='', addindent='\t', newl='\n', encoding="utf-8")) #輸出帶格式的字符串 # doc.writexml() #將prettyxml字符串寫入文件
1.2 讀取和更新xml文件
解析xml文件為DOM樹,獲取樹的根節點,隨后即可通過根節點尋找相關的子節點,並獲取相關的屬性和文本,相關API如下:
讀取xml的API doc = minidom.parse(xml_path) #解析xml文件(句柄或文件路徑) doc = minidom.parseString() #解析xml字符串 root_node = doc.documentElement #獲得根節點 print(root_node.nodeName) #結點名稱 print(root_node.nodeType) #結點類型 (元素結點,文本結點,屬性結點) print(root_node.childNodes) #所有子節點,為列表 print(node.parentNode) # 獲取父節點 filename_node = root_node.getElementsByTagName('filename')[0] #通過結點名稱尋找結點,返回列表
#文本結點 filename = filename_node.childNodes[0].data #子節點為文本結點,文本結點有data屬性即為文本值 #屬性結點 # node.getAttribute('price') #屬性結點node,獲取其price屬性
下面為一個讀取xml文件並更新指定結點文本值的代碼:

def read_xml(xml_path): with open(xml_path, "r", encoding="utf-8") as f: doc = minidom.parse(xml_path) #解析xml文件(句柄或文件路徑) #doc = minidom.parseString() #解析xml字符串 root_node = doc.documentElement #獲得根節點 #找到xmin結點並更新其對應的文本值 xmin_node = root_node.getElementsByTagName("xmin")[0] print(xmin_node.childNodes[0].data) xmin_node.childNodes[0].data = str(200) print(xmin_node.childNodes[0].data) with open(xml_path, "w", encoding="utf-8") as f: doc.writexml(f)
2. ElementTree
ElementTree就像一個輕量級的DOM, Python專有,使用起來更加簡單,常用API如下:
2.1 讀取和解析xml文件
支持遍歷結點,查找結點和訪問結點,如下所示:
def element_read_xml(xml_path): #1. 獲取root結點 tree = ET.parse(xml_path) #方式一 root = tree.getroot() # tree = ET.ElementTree(file=xml_path) # 方式二 # root = tree.getroot() # with open(xml_path, "r", encoding="utf-8") as f: # 方式三 # root = ET.fromstring(f.read()) print(root) #2.訪問特定結點屬性 (屬性包括tag, text, attrib) #遍歷結點, 每一個結點都是一個迭代器,能遍歷其子節點 for i in root: print(i.tag, i.attrib) for j in i: print(j, j.attrib) #3.下標方式訪問子節點 print(root[0].tag, root[0].attrib, root[0].text) #4. 查找結點,支持tag名字和xpath語法 print(tree.find("folder")) #在當前結點的子節點中尋找標簽為folder的子節點 print(tree.find(".//name")) #尋找所有子孫結點中第一個標簽為name的子節點 print(tree.findall(".//name")) #尋找所有子孫結點中標簽為name的子節點(返回列表) print(tree.findtext(".//name")) #尋找所有子孫結點中第一個標簽為name的子節點,並返回其text屬性
2.2 創建xml文件並寫入
需要創建根結點,然后添加子節點,最后創建節點樹並寫入,如下所示:
def write_xml(): root = ET.Element("node") folder_node = ET.Element("folder") folder_node.text = "/home" folder_node.tail = "\n" print(dir(folder_node)) root.append(folder_node) #添加子節點 #extend(subments) #添加多個子節點 elem3 = ET.Element("test_extend") elem3.text = "elem 3" elem3.tail = "\n" #結點尾部添加換行 elem4 = ET.Element("test_extend") elem4.text = "elem 4" elem4.tail = "\n" root.extend([elem3, elem4]) #insert(index, subment) #插入子節點 #remove(subment) #刪除子節點 folder_node = ET.SubElement(root, "folder") # 為root添加子節點 folder_node.text = "/home" tree = ET.ElementTree(root) tree.write("output.xml", encoding="utf-8", xml_declaration=True) #保存時無縮進,添加縮進需要借用dom #借用dom,添加縮進 # rawtext = ET.tostring(root) # dom = minidom.parseString(rawtext) # with open("output.xml", "w") as f: # dom.writexml(f, indent="\t", newl="", encoding="utf-8")
2.3 讀取並更新結點
除了下面修改結點的名稱外,還可以進行添加子節點,刪除子節點等操作
def update_xml(): #查找節點並更新 root = ET.parse("output.xml") for node in root.findall(".//folder"): if node.text == "/home": node.tag = "path" ET.dump(root) #打印xml root.write("output.xml")
3. SAX(Simple API for XML)
而SAX是一種基於事件的流式處理模型,可以在只讀入部分XML的情況下進行處理, 比較快,占用內存少。DOM與etree一般都比SAX簡單, 但其將XML數據映射到內存中的樹,比較慢,且較耗內存;如果XML文件比較大,或要求速度快時,SAX比較適合;
利用SAX解析XML文檔牽涉到兩個部分:解析器和事件處理器。解析器負責讀取XML文檔,並向事件處理器發送事件,如元素開始跟元素結束事件;而事件處理器則負責對事件作出相應,對傳遞的XML數據進行處理
3.1 解析器:
#創建解析器 #1 xml.sax.parse( xmlfile, contenthandler[, errorhandler]) xmlfile - xml文件名 contenthandler - 必須是一個ContentHandler的對象 errorhandler - 如果指定該參數,errorhandler必須是一個SAX ErrorHandler對象 #2 xml.sax.parseString(xmlstring, contenthandler[, errorhandler]) xmlstring - xml字符串 contenthandler - 必須是一個ContentHandler的對象 errorhandler - 如果指定該參數,errorhandler必須是一個SAX ErrorHandler對象 #3 xml.sax.make_parser()
3.2 事件處理器:
class EventHandler(sax.ContentHandler): def __init__(self): pass #文檔啟動的時候調用 def startDocument(self): pass #解析器到達文檔結尾時調用 def endDocument(self): pass # 遇到XML開始標簽時調用,name是標簽的名字,attrs是標簽的屬性值字典。 def startElement(self, name, attrs): pass #遇到XML結束標簽時調用。 def endElement(self, tag): pass #標簽之間的內容處理時調用 def characters(self, content): pass
使用示例參考:

#!/usr/bin/python # -*- coding: UTF-8 -*- import xml.sax class MovieHandler( xml.sax.ContentHandler ): def __init__(self): self.CurrentData = "" self.type = "" self.format = "" self.year = "" self.rating = "" self.stars = "" self.description = "" # 元素開始事件處理 def startElement(self, tag, attributes): self.CurrentData = tag if tag == "movie": print "*****Movie*****" title = attributes["title"] print "Title:", title # 元素結束事件處理 def endElement(self, tag): if self.CurrentData == "type": print "Type:", self.type elif self.CurrentData == "format": print "Format:", self.format elif self.CurrentData == "year": print "Year:", self.year elif self.CurrentData == "rating": print "Rating:", self.rating elif self.CurrentData == "stars": print "Stars:", self.stars elif self.CurrentData == "description": print "Description:", self.description self.CurrentData = "" # 內容事件處理 def characters(self, content): if self.CurrentData == "type": self.type = content elif self.CurrentData == "format": self.format = content elif self.CurrentData == "year": self.year = content elif self.CurrentData == "rating": self.rating = content elif self.CurrentData == "stars": self.stars = content elif self.CurrentData == "description": self.description = content if ( __name__ == "__main__"): # 創建一個 XMLReader parser = xml.sax.make_parser() # turn off namepsaces parser.setFeature(xml.sax.handler.feature_namespaces, 0) # 重寫 ContextHandler Handler = MovieHandler() parser.setContentHandler( Handler ) parser.parse("movies.xml")
參考博客:
https://www.zhihu.com/question/21824329