python讀取,寫入和更新xml文件


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字符串寫入文件
寫入xml文件

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)
View Code

 

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")
View Code

 

 

 

 參考博客:

https://www.zhihu.com/question/21824329

https://zhuanlan.zhihu.com/p/85752679

https://www.cnblogs.com/hupeng1234/p/7262371.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM