2022 / 10 / 19 说明:本文仍为未完成状态,且后续很长一段时间内将不会进一步完善此文。请谨慎阅读这篇文章。
因学长所用的某个针对yolov5的修改版训练网络 针对宽高不等的图片无法作为数据正常送入网络进行训练 ,而先期使用labelimg2标注工具所截取的图片往往都不是正方形,所以我要对之前处理的图片进行padding处理使之成为正方形。同时padding操作改变了图片的坐标系位置以及图片宽高属性,因此要对标签(XML文件)进行更新操作,而这涉及到对XML文件的读写,在网上查询相关资料后,我成功实现了预期目标。
01 XML文件的结构
XML文件作为一种被设计为传输、存储结构化数据信息的文件结构,其语法简洁清晰,没有其它的预定义标签,以一个简单的XML文档为例:
testfile.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?xml version="1.0" encoding="UTF-8" ?> <addressbook > <person gender = "male" > <name > Jack</name > <id > 01</id > <tel > 10000</tel > <address > <province > Jiangsu</province > <city > Nanking</city > </address > </person > <person gender = "female" > <name > Maria</name > <id > 02</id > <tel > 10001</tel > <address > <province > Anhui</province > <city > Hefei</city > </address > </person > </addressbook >
其中第一行必须 是XML声明 ,在文件的前面不能有其它元素或者注释,它定义 XML 的版本和所使用的编码方式,第二行开始是一个根元素 ,在这个例子中,根元素是<addressbook>
...</addressbook>
。接下来的<person>
标签是根元素的子元素 ,而对应的,根元素为所有子元素的父元素。所有元素都有对应的子元素,都可以拥有文本内容和属性 ,例如<person>
标签的属性名gender
,其对应的属性值由双引号"
括起来。
其它需要注意的内容诸如标签名大小写敏感、注释格式等可以参考其它网络文章:
XML 树结构 (w3school.com.cn)
XML文件结构和基本语法 - konglingbin - 博客园 (cnblogs.com)
XML - 廖雪峰的官方网站 (liaoxuefeng.com)
02 DOM解析XML文件
文件对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展置标语言的标准编程接口。一个 DOM 的解析器在解析一个XML文档时,一次性读取整个文档,把文档中所有元素保存在内存中的一个树结构里,之后你可以利用DOM 提供的不同的函数来读取或修改文档的内容和结构,也可以把修改过的内容写入xml文件。python中用xml.dom.minidom来解析xml文件。
参考了几篇网络教程,废话不多说,直接上手开干!
读取
Python代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import xml.dom.minidom as minidomimport osfilepath = os.path.abspath("testfile.xml" ) print ("读取文件路径:" , filepath)objectTree = minidom.parse(filepath) rootElement = objectTree.documentElement print ("变量类型:" , type (rootElement))print ("根元素名:" , rootElement.nodeName)persons = rootElement.getElementsByTagName("person" ) print ("变量类型:" , type (persons))Jack = persons[0 ] print ("节点列表中某一个元素的类型:" , type (Jack))print ("Jack性别:" , Jack.getAttribute("gender" ))print ("Jack性别:" , Jack.getAttributeNode("gender" ).nodeValue)tel = Jack.getElementsByTagName("tel" ) print ("tel:" , tel[0 ].childNodes[0 ].data)print (type (Jack.childNodes[0 ]))print (type (Jack.childNodes[1 ]))print (Jack.childNodes[1 ])print (type (Jack.childNodes[1 ].childNodes[0 ]))print (Jack.childNodes[1 ].childNodes[0 ].data)print (Jack.childNodes[1 ].childNodes[0 ].nodeValue)print ("\n遍历各个元素:" )for person in persons: name = person.getElementsByTagName("name" )[0 ].childNodes[0 ].data gender = person.getAttribute("gender" ) id_number = person.getElementsByTagName("id" )[0 ].childNodes[0 ].nodeValue tel = person.getElementsByTagName("tel" )[0 ].childNodes[0 ].nodeValue address = person.getElementsByTagName("address" )[0 ] province = address.childNodes[1 ].childNodes[0 ].data city = address.getElementsByTagName("city" )[0 ].childNodes[0 ].data print ("姓名: %s\t性别: %s\tid: %s\t电话: %s\t地址: %s省%s市" % (name, gender, id_number, tel, province, city))
控制台输出 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 读取文件路径: C:\Users\*****\Desktop\xmltest\testfile.xml 变量类型: <class 'xml.dom.minidom.Element' > 根元素名: addressbook 变量类型: <class 'xml.dom.minicompat.NodeList' > 节点列表中某一个元素的类型: <class 'xml.dom.minidom.Element' > Jack性别: male Jack性别: male tel: 10000 <class 'xml.dom.minidom.Text' > <class 'xml.dom.minidom.Element' > <DOM Element: name at 0x21cca71a700> <class 'xml.dom.minidom.Text' > Jack Jack 遍历各个元素: 姓名: Jack 性别: male id : 01 电话: 10000 地址: Jiangsu省Nanking市 姓名: Maria 性别: female id : 02 电话: 10001 地址: Anhui省Hefei市
警告:在笔者所用的环境下,.childNodes
方法会将子元素与子元素之间生成的空格与换行当作空白文本节点并入节点列表,以至于得不到预期的获取元素的效果。个人认为还是最好用.getElementsByTagName
获取元素节点列表再通过索引获得元素。
写入/更新(追加、删除)
对于第一种情况,你需要调用xml.dom.minidom.Document()
新建一个DOM对象。而在其他情况下,你只需要建立、删除或者更新元素节点或者文本内容节点的值,并挂在到对应的父元素上即可,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 import xml.dom.minidom as minidomimport osfilepath = os.path.abspath("testfile.xml" ) objectTree = minidom.parse(filepath) rootElement = objectTree.documentElement person = objectTree.createElement("person" ) person.setAttribute("gender" , "male" ) name = objectTree.createElement("name" ) name_text = objectTree.createTextNode("Xiaoming" ) name.appendChild(name_text) id_number = objectTree.createElement("id" ) id_text = objectTree.createTextNode("03" ) id_number.appendChild(id_text) tel = objectTree.createElement("tel" ) tel_text = objectTree.createTextNode("10002" ) tel.appendChild(tel_text) address = objectTree.createElement("address" ) province = objectTree.createElement("province" ) province_text = objectTree.createTextNode("Hubei" ) city = objectTree.createElement("city" ) city_text = objectTree.createTextNode("Wuhan" ) address.appendChild(province) address.appendChild(city) province.appendChild(province_text) city.appendChild(city_text) person.appendChild(name) person.appendChild(id_number) person.appendChild(tel) person.appendChild(address) rootElement.appendChild(person) persons = rootElement.getElementsByTagName("person" ) print ("\n遍历各个元素:" )for p in persons: name = p.getElementsByTagName("name" )[0 ].childNodes[0 ].data gender = p.getAttribute("gender" ) id_number = p.getElementsByTagName("id" )[0 ].childNodes[0 ].nodeValue tel = p.getElementsByTagName("tel" )[0 ].childNodes[0 ].nodeValue address = p.getElementsByTagName("address" )[0 ] province = address.getElementsByTagName("province" )[0 ].childNodes[0 ].data city = address.getElementsByTagName("city" )[0 ].childNodes[0 ].data print ("姓名: %s\t性别: %s\tid: %s\t电话: %s\t地址: %s省%s市" % (name, gender, id_number, tel, province, city)) with open ('modified.xml' , 'w' ) as f: objectTree.writexml(f, addindent=' ' , encoding='utf-8' , newl=’\n’)
根据笔者测试,其能生成对应的元素,逻辑结构虽然没有发生错误:
控制台输出 1 2 3 4 遍历各个元素: 姓名: Jack 性别: male id : 01 电话: 10000 地址: Jiangsu省Nanking市 姓名: Maria 性别: female id : 02 电话: 10001 地址: Anhui省Hefei市 姓名: Xiaoming 性别: male id : 03 电话: 10002 地址: Hubei省Wuhan市
但仍存在其问题,由于读取XML文件时,元素之间的换行符与空格被当作文本内容(在DOM下同样被视作元素),从而.writexml
参数中的换行、缩进同样对其生效,导致新加的元素格式正常,而原来排版工整的元素出现了格式问题:
换行问题
正常效果
03 SAX解析XML文件
SAX,全称为Simple API for XML,它并非W3C官方所指定的一种标准,但是凭着其独特的事件处理模型,支持它的XML解析器也不在少数。与DOM相比,它的优势在于边解析边进行操作,不用像DOM一样将XML文件的整个对象树映射进内存中,占用资源较少。但缺陷在于需要用户自己动手重写回调函数(handler)以实现指定的操作,且根据我自己搜索到的资料,似乎SAX对于修改文件的数据——特别是指定位置的数据比较困难,中文资料可供参考的不多,而且大多是给的例子一模一样……
SAX包括readers、handlers以及input sources三个部分。readers负责读取sources里的内容,并在遇到标签开始/结束/内容时向handler发送对应的事件。而handler负责处理对应的事件,相关代码可以在.\Python\Python[版本号]\Lib\xml\sax\handler.py
中看到。
SAX定义了四种handler,即content handler、DTD handler、error handler以及entity handler。笔者所学尚浅,只需要对xml文件中的内容进行处理即可。因此我们只需要重写content handler的类方法以执行我们需要的操作。
重写回调函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 class XmlHandler (xml.sax.ContentHandler): def __init__ (self ): self.currentData = "" self.gender = "" self.name = "" self.id = "" self.province = "" self.city = "" def startElement (self, tag, attribute ): self.currentData = tag if tag == "person" : print ("******" ) self.gender = attribute["gender" ] if self.gender == "male" : print ("男性:" ) else : print ("女性:" ) else : pass def endElement (self, tag ): if tag == "name" : print ("姓名:" , self.name) elif tag == "id" : print ("id:" , self.id ) elif tag == "province" : print ("省份:" , self.province) elif tag == "city" : print ("市区:" , self.city) else : pass self.currentData = "" def characters (self, content ): if self.currentData == "name" : self.name = content elif self.currentData == "id" : self.id = content elif self.currentData == "province" : self.province = content elif self.currentData == "city" : self.city = content else : pass
然后我们来实战验证一下我们的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import xml.saxclass XmlHandler (xml.sax.ContentHandler): ... ... ... parser = xml.sax.make_parser() parser.setFeature(xml.sax.handler.feature_namespaces, 0 ) handler = XmlHandler() parser.setContentHandler(handler) parser.parse("testfile.xml" )
效果展示:
控制台输出 1 2 3 4 5 6 7 8 9 10 11 12 ****** 男性: 姓名: Jack id : 01省份: Jiangsu 市区: Nanking ****** 女性: 姓名: Maria id : 02省份: Anhui 市区: Hefei
04 ElementTree解析XML文件
施工中。。。