Python 15 标准库-xml模块

Python 15 标准库-xml模块

旧博客Python系列

XML模块

XML是一种标记语言,出现时间比Json早,所以在很多领域还是相当于标准的地位.用来制作网页的HTML语言也是标记语言的一种,HTML与XML的结合XHTML在2000年成为互联网规范,但后来由于HTML5的发展,XHTML已经废弃,合并到HTML5规范内.
标记语言采用标记(标签)作为基础的单元,所有的语法都通过标签实现.有两种,一种是自闭合标签,一种是非自闭合标签.标记语言之前学过HTML,到前端的时候再详细记录.
XML语言的本质就是数据的嵌套关系,通过标签的相互包含可以建立起数据的组织结构(就像浏览器建立DOM模型一样),XML的数据关系本质上和python的字典非常类似.

以下提到标签,元素,节点,都是指的同一个概念,即XML文件中的一个标记.

XML模块

import xml
print(xml.__all__)
# ['dom', 'parsers', 'sax', 'etree']

可以看到有四个模块,这是代表了python里解析XML的四种实现方式,这里有一篇文章介绍了XML模块的四种实现.DOM是DOM模型,今后学HTML和JS时候会遇到,采用DOM模型处理XML,里边还包含很多的库和功能.parsers和sax也是两种API,但是对于单纯的XML数据处理来说,etree模块轻量级有C实现,也比较简便,因此普遍采用etree方式进行处理.先通过实际运用来学习模块使用:

1 导入XML模块并且打开(解析)XML文件
import xml.etree.ElementTree as ET
tree = ET.parse('xmldata.xml') #解析XML文件,建立树结构
root = tree.getroot()
print(root.tag)

ET.parse方法用来解析一个XML文件,解析之后会生成一个XML的数据结构,就是一个树结构.
tree对象就代表了整个XML数据结构.
tree对象的getroot()方法用来获取根节点.
每一个节点都会有标签名和其他属性
用.tag属性表示每个节点的标签名

2 对树上各个节点的了解和取节点属性
# 遍历根节点中的内容:
for i in tree:
    print(i)

结果得到了一堆对象的内存地址,看原文件的第一层标签下有5个标签,猜测这五个东西应该是根节点的5个子节点,于是同样,试验一下这几个节点应该和根节点一样至少有.tag属性.

# 显示根节点的子节点的tag名称
for node in root:
    print(i.tag)

拿到了节点的tag名称.那么标签内部的其他属性如何获得,只要用.attrib属性即可返回标签内各个属性与值的字典,.text属性返回两个标记中的内容文本.

# 取得其他属性和标签内容
for node in root:
    for child_node in node:
        print(child_node.attrib)
        print(child_node.text)
3 高级操作

拿到特定的标签:

# 拿到XML文件里所有的某一个标签的属性
for node in root.iter(""year""):
    print(node.text,node.tag)

这里要注意的是root.iter('tagname')方法,是取到所有的节点里与tagname匹配的标签.由于用在根节点上,取到的就是这个文件内所有的标签

修改数据:

# 先取到节点,然后更改tag的attrib及text属性
for node in root.iter(""year""):
    new_year = int(node.text)+1  
    node.text = str(new_year)  #更新text属性
    node.set(""updated"",""yes"")  #set方法更新attrib

完成上边的代码以后,是修改了内存中的XML数据结构,但是还没有写进文件,要把tree对象写进文件.

# 写入文件
tree.write('xmldata.xml') #可以覆盖原文件,也可以写一个新文件.

删除数据:

# 想把排名低于50的国家连带下边数据删除,所以需要先找到所有的country标签.
for node in root.findall('country'):
    print(node.tag)
    rank = int(node.find('rank').text)
    if rank >= 50:
        root.remove(node)

findall应该就是如果当前节点有多于一个同名的子节点,就可以取得所有同名节点,和iter类似.find就是用来找唯一的子节点.然后在定位到需要删除的节点,用remove(node)来删除.

创建标签并插入给某个标签作为子标签:

# 在wordpress里经常遇到表格,写了个生成HTML表格的程序:
import xml.etree.ElementTree as ET
root_node = ET.Element('table')
root_node.set('border','1')
def addline(a,b):
    new_line = ET.SubElement(root_node,'tr')
    new_td1 = ET.SubElement(new_line,'td')
    new_td1.text = a
    new_td2 = ET.SubElement(new_line,'td')
    new_td2.text = b
    print('成功增加一行,内容为 {}|{}'.format(a,b))
    return

def final(name):
    file = ET.ElementTree(root_node)
    file.write(name)

def input_content():
    while True:
        a = input('输入方法:')
        if a == 'q':
            break
        b = input('输入描述:')
        if b == 'q':
            break
        addline(a,b)
    file_name = input('请输入文件名:')
    final(file_name)

input_content()

前边在整理模块和方法的时候经常用到表格,今天学了XML处理,发现可以写一个简单生成HTML表格的程序,输入每一行两列的内容后,选择文件名,即可生成简易的HTML表格.

XML Etree 总结

XML的核心操作就是先解析xml生成一个tree对象(可以视为整个xml文件),然后从tree对象取得root节点.之后通过root节点的各种方法和属性,来定位所需要操作的节点,进行修改删除.最后再将修改过的tree写入文件.处理XML的思想和今后HTML及JavaScript的思想非常类似.
处理多个节点时,也可以先将要处理的节点放入一个列表,以后来寻找和进行操作就非常方便了,不用总是遍历.

Etree的XML Etree 的官方文档,还有这里有一篇文章可供参考:

操作XML(和其他标记语言)的主要逻辑是: 1 建立树结构并取得根节点---> 2 定位要操作的节点 ---> 3 增删改 ----> 4 保存

1 建立树结构并取得根节点

一旦获得树结构并且找到了root节点,剩下的节点都是嵌套在root节点内,只要通过各种方法取得要操作的节点就可以了.

tree = ET.parse('country_data.xml') 解析XML文件,返回XML数据文件对象,是一个xml.etree.ElementTree.ElementTree类对象(后边称为ETree类).这个类的方法在后边总结.
root = tree.getroot() 获得根节点对象,子节点是嵌套的,可以用root[index].attrib这种形式来操作.根节点和其他的节点都是xml.etree.ElementTree.Element类(后边称为Element类),这个类的方法在后边总结
root = ET.fromstring(country_data_as_string) 直接从一个符合XML结构的字符串生成root节点.
2 定位要修改的节点

定位节点的方法主要来自于Element类和ETree类支持的方法,此外因为节点是嵌套的,还可以直接用索引来定位,索引按照XML文件里的顺序,从0开始.比如 root[0][2][3] 即表示根节点的第一个子节点的第三个子节点的第四个子节点,如果对文档结构了解,可以采用该方法.

Element类的方法:
Element类代表了XML里边的节点=元素=标签对象,因此Element类既有属性用于修改相关数据,又有一些用于定位节点的方法.Element的find(tagname)相关方法均只能对子元素生效,不能递归生效.可以递归生效的是iter(tagname)方法.

Element.tag 返回节点的tag名,字符串类型
Element.text 返回该节点从起始标签到第一个子标签或者结束标签之间的内容.如果没有就是None
Element.tail 返回该节点的结束标签到下一个标签之间的内容,如果没有就是None,就是紧跟在结束标签后的数据尾巴.
Element.attrib 标签内的属性,以一个字典的形式返回,用字典方法增删改查.
Element.get(key, default=None) 元素本身的get方法,与操作Element.attrib字典一样.
Element.clear() 重置当前节点,清除所有子节点,删除所有标签属性,将text和tail属性都设置为None
Element.items() 返回元素的所有标签属性字典键值对构成的列表.
Element.keys() 返回元素所有标签属性字典的键
Element.set(key, value) 设置标签属性,传入key value键值对
Element.append(subelement) 增加新节点,重要功能.追加subelement成为当前元素的最后一个子元素.subelement必须为一个element对象.
Element.extend(subelements) 接受一个序列形式的节点元素,将其中的每个节点都添加为子节点
Element.find(match, namespaces=None) match可以是路径(这里的路径不是指文件系统目录,而是XML应用的XPath expressions,用来表示一个树里的某个节点=元素)或者tag名,返回第一个符合match的Element类对象=节点.这个方法仅对当前节点的子节点有效,不递归查找.
Element.findall(match, namespaces=None) match可以是路径名或者tag名,返回一个列表,包含所有符合match的Element类对象,按照其在XML文档中出现的顺序.这个方法仅对当前节点的子节点有效,不递归查找
Element.findtext(match, default=None, namespaces=None) match可以是路径名或者tag名,返回第一个匹配的元素的text内容,如果该元素没有text内容则返回空字符串.如果找不到元素则返回default参数的值.只能对子元素查找,不递归查找.
Element.insert(index, subelement) 节点都是嵌套的,在指定的索引处插入新节点,如果subelement不是有效的节点对象,返回TypeError
Element.iter(tag=None) 返回一个迭代器,如果tag为None或者*,表示从当前节点开始生成一个包含自身和所有下级节点的迭代器.如果tag指定了内容,则只生成的迭代器其中的元素的tag名必须与参数相等.这个方法很常用,经常用来获得某一类tag来集中操作.
Element.iterfind(match, namespaces=None) 从对象节点开始查找所有子节点然后放入一个生成器.match依然可以是tag名称或者路径.这个也只能对子元素生效,不能递归查找
Element.remove(subelement) 删除对象下边的一个子节点,subelement是一个节点对象,不是tag标签或者text内容.所以这个方法必须定位到元素才能进行删除.
Element.itertext() 返回一个text生成器,包含当前节点和所有子节点的text内容.这个是递归的,全部下级节点都在.

ETree类的方法:
ETree类的find方法与Element类相同,只不过都是基于根节点,而Element类的方法是基于调用方法的节点.ETree由于是整个数据结构的对象,因此还支持写入到文件和从文件中读出,以及重置根节点等方法.

ETree._setroot(element) 重置根节点:用参数element对象当成新的根节点,等于抛弃所有当前树结构,重新建立树结构.尽量少用.
Etree.find(match, namespaces=None) 与Element类的find方法相同,这个方法一定是查找根节点的子元素,只对子元素生效,无法递归查找
Etree.findall(match, namespaces=None) 与Element类的find方法相同,这个方法也开始于根节点,只对子元素生效,无法递归查找.
Etree.findtext(match, default=None, namespaces=None) 与Element类的方法一样,从根节点开始,不递归
Etree.getroot() 返回树结构的根节点,是一个Element类对象
Etree.iter(tag=None) 和Element的iter()方法相同,只不过是从根节点开始.
Etree.iterfind(match, namespaces=None) 和Element类对象的方法相同,返回生成器,不递归查找
Etree.parse(source, parser=None) 从source=xml文件装入一个树,取代原来的Etree对象的内容,等于抛弃原来的全部数据.注意,ET模块的parse()方法和这个方法并不相同.这个方法返回装入的xml文件的根节点对象,而ET.parse()返回的是ETree类对象.
write(file, encoding=""us-ascii"", xml_declaration=None, default_namespace=None, method=""xml"", *, short_empty_elements=True) 将树结构写入文件中,file为文件名或文件对象,encoding对于我们来说应该选择utf-8.xml_declaration表示是否在第一行声明XML文件格式,可以选False为关闭,True为添加声明,None则仅当不选用ascii,utf-8和unicode的时候才声明.method可以选择'html','xml'或者'text.最后的short_empty_elements设置为True表示如果一个标签之内没有内容,将自动把该标签设置为自闭合标签,False则不会.默认为True.如果编辑HTML的时候,应该设置为False
3 增删改

通过ETree和Element的find以及iter等方法,需要的话再加上对于节点属性的逻辑判断等要素,最终可以定位到需要操作的节点,进行增删改操作:

增加节点:
增加节点的方法主要用到的是Element.append和Element.extend方法.这两个方法不再详述.关键是这两个方法的参数需要是ELement对象,这个对象如何生成呢,其实很简单.
如果导入的是xml.etree.ElementTree as ET的话,已经知道了ET下边有ElementTree类(前边说的ETree类)和Element类.采用ET.Element(tagname)来实例化一个ELement对象.采用ET.ElementTree(element)来以一个节点作为根节点并实例化一个XML树结构对象.
此外ET有一个方法,ET.Subelement(parent, tagname, attrib={}, **extras)可以直接给一个父元素添加一个子标签,并且返回这个子标签.这样给节点添加子节点有两个办法:
1 定位节点--->用ET.Subelement(parent, tagname)直接添加子节点并返回子节点
2 ET.Element(tagname)实例化一个新节点--->ET.Element.append(new_node)来添加节点
此外如果是新建XML文件,则在建立好自己的结构之后,用ET.ElementTree(root_node)以根节点为参数来建立XML树结构对象,然后将这个对象写进文件即可.

删除节点:
定位到要删除的节点的父节点,然后用remove删除即可,删除一个标签则里边的所有嵌套标签和数据一并被删除.

修改节点:
修改节点就是使用ET.Element对象的各种方法和属性来修改内容.标签属性用字典方式修改,text内容直接修改属性.

ET的方法
ET的方法主要是解析文件生成树对象,以及有一个简便的Subelement方法给节点添加子节点.
总结一下ET本身的方法:

xml.etree.ElementTree.dump(elem) elem可以是树对象,也可以是一个节点对象,这个命令是向stdout写出xml数据,应该只在debug的时候使用.
xml.etree.ElementTree.fromstring(text) 解析字符串生成树对象
xml.etree.ElementTree.fromstringlist(sequence, parser=None) 从一个字符串列表里解析XML数据,生成树对象
xml.etree.ElementTree.iselement(element) 测试element是否是树结构里的一个元素.
xml.etree.ElementTree.parse(source, parser=None) 最常用,解析一个XML文件生成树对象
xml.etree.ElementTree.SubElement(parent, tag, attrib={}, **extra) 给一个父节点添加tag名称的子节点,可以指定属性,这个方法返回新添加的子节点对象
xml.etree.ElementTree.dump(elem) elem可以是树对象,也可以是一个节点对象,这个命令是向stdout写出xml数据,应该只在debug的时候使用.

经过学习之后,理清了XML文件操作的思路:
1 拿到XML解析,分为两个对象(树对象和节点对象),一个ET方法来掌握.
2 节点对象;节点对象支持修改属性和内容,支持查找子节点和用标签名递归定位;
3 树对象是抽象的整个树,不支持修改属性和内容,但一样支持查找和定位,此外还支持写入文件和设置根节点.
4 ET的方法主要是为了建立树对象,此外ET有一个简便办法可以添加子节点.

XML的操作乍看起来与python日常进行的操作有很大区别,因为python的数据结构通常使用的是序列类以及字典(哈希表),而XML是树形结构的数据,解析不太一样.但其实计算机在表达式求值的时候也是用的树形结构求值,操作系统也有很多资源管理采用树结构.
在write()方法的时候已经看到了有保存HTML文件的参数,其实XML的操作思想和HTML是一样的,到了HTML的时候,JavaScript是通过DOM模型来对HTML进行操作,和XML树结构是一样的东西.XML的标准库内也有采用DOM模型的实现.

LICENSED UNDER CC BY-NC-SA 4.0
Comment