09.XML处理之etree模块

本主题主要说明python的xml处理标准模块xml.etree的使用。xml.etree模块包含4个子模块,其中cElementTree是ElementTree的别名,已经不推荐使用。本主题主要包含内容:
  1. ElementInclude模块使用
  2. ElementPath模块使用
  3. ElementTree模块使用

一、etree模块帮助

import xml.etree
help(xml.etree)
Help on package xml.etree in xml:

NAME
    xml.etree

DESCRIPTION
    # $Id: __init__.py 3375 2008-02-13 08:05:08Z fredrik $
    # elementtree package

PACKAGE CONTENTS
    ElementInclude
    ElementPath
    ElementTree
    cElementTree

FILE
    /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/xml/etree/__init__.py

提供四个模块:
  |- ElementInclude
  |- ElementPath
  |- ElementTree
  |- cElementTree

二、ElementInclude模块

ElementInclude模块主要提供xml中xinclude展开使用。

提供了几个异常类:
FatalIncludeError
  |-builtins.SyntaxError
  |-builtins.Exception
  |-builtins.BaseException
  |-builtins.object
提供了两个函数:
FUNCTIONS
  |-default_loader(href, parse, encoding=None)
  |-include(elem, loader=None)

提供了三个常量:
DATA
  |-XINCLUDE = '{http://www.w3.org/2001/XInclude}'
  |-XINCLUDE_FALLBACK = '{http://www.w3.org/2001/XInclude}fallback'
  |-XINCLUDE_INCLUDE = '{http://www.w3.org/2001/XInclude}include'

2.1. 使用xinclude语法的xml文件

1. webpages.xml文件

<?xml version="1.0"?>
<webpage>
<body>Hello world!</body>
    <xi:include href="footer.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/>
</webpage>

2. footer.xml文件

<?xml version="1.0"?>
<footer>Mage Education, 2019</footer>

2.2. 使用default_loader加载xml文件

函数default_loader加载任何文本文件,不过按照xml加载就返回Element对象,非xml就返回str字符串。

函数原型如下:
default_loader(href, parse, encoding=None)
  |- href:用来指定加载的xml文件
  |- parse:指定解析的类型:xml与非xml,xml就返回xml.etree.ElementTree.Element类型,否则字符串。
  |- encoding:对非xml有用,用来指定编码格式,一般就是utf-8了。

import xml.etree.ElementInclude

# 非xml解析,直接返回字符串
result = xml.etree.ElementInclude.default_loader(
    href='codes/webpages.xml',
    parse='text',
    encoding='utf-8')
print(':', type(result))
print(result)
print('------------------')

# 作为xml解析返回xml.etree.ElementTree.Element对象。
result = xml.etree.ElementInclude.default_loader(
    href='codes/webpages.xml',
    parse='xml',
    encoding='utf-8')

print(':', type(result))
print(result)
: <class 'str'>
<?xml version="1.0"?>
<webpage>
<body>Hello world!</body>
   <xi:include href="codes/footer.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/>
</webpage>
------------------
: <class 'xml.etree.ElementTree.Element'>
<Element 'webpage' at 0x105a32728>

2.3. 使用include函数扩展xinclude

函数include用来展开xml中的xinclude指令。

函数原型如下:
include(elem, loader=None)
  |- elem:要扩展xinclude的元素。
  |- loader:只加载扩展xml文件的加载器,默认是default_loader函数加载器。

注意
  经过include函数处理的元素,如果包含include指令,则会展开成xml的文件。 可以仔细观察西面例子的输出。

import xml.etree.ElementInclude

# 由于版本变化,默认的常量值,可以根据已有的文档修改。
xml.etree.ElementInclude.XINCLUDE_INCLUDE='{http://www.w3.org/2003/XInclude}include'

# 作为xml解析返回xml.etree.ElementTree.Element对象。
result = xml.etree.ElementInclude.default_loader(
    href='codes/webpages.xml',
    parse='xml',
    encoding='utf-8')

print('xinclude扩展前输出')
for ele in result:
    print(type(ele),ele)

xml.etree.ElementInclude.include(result, loader=None)

print('xinclude扩展后输出')
for ele in result:
    print(type(ele),ele)

xinclude扩展前输出
<class 'xml.etree.ElementTree.Element'> <Element 'body' at 0x105a56e58>
<class 'xml.etree.ElementTree.Element'> <Element '{http://www.w3.org/2003/XInclude}include' at 0x105a4dae8>
xinclude扩展后输出
<class 'xml.etree.ElementTree.Element'> <Element 'body' at 0x105a56e58>
<class 'xml.etree.ElementTree.Element'> <Element 'footer' at 0x105a32728>

三、ElementPath模块

提供XPath的支持。
支持XPath的函数如下:
FUNCTIONS
  |- find(elem, path, namespaces=None)
  |- findall(elem, path, namespaces=None)
  |- findtext(elem, path, default=None, namespaces=None)
  |- get_parent_map(context)
  |- iterfind(elem, path, namespaces=None)
  |- prepare_child(next, token)
  |- prepare_descendant(next, token)
  |- prepare_parent(next, token)
  |- prepare_predicate(next, token)
  |- prepare_self(next, token)
  |- prepare_star(next, token)
  |- xpath_tokenizer(pattern, namespaces=None)

其中提供的常量数据有:
  |- ops = {'': <function prepare_child>, '': <function prepare_star>, '.'...
  |- xpath_tokenizer_re = re.compile('('[^']
'|\"[^\"]*\"|::|//?|\.....

注意:
  其中prepare_XXX函数就是ops的操作列表,操作会被iterfind使用,被用来解析path的每个部分,一般不直接使用。下面也不介绍。
  实际ElementTree中的path操作方式也是使用这里的find系列函数。

下面是xpath支持的语法:

xpath语法 说明
tag 选择所有tag子元素
* 选择一级子元素,比如*/egg选在孙子元素egg
. 表示当前元素
// 所有子元素,比如.//egg选在任何级别节点上的egg元素。
.. 上级父元素
[@attrib] 具有属性attrib的元素
[@attrib='value'] 属性值等于value的元素
[tag] 所有具有tag子元素的元素
[tag='text'] 选择子节点是tag,同时内容为text的元素
[position] 选在给定位置的元素(1表示第一个), last()表示最后一个,last()-1从最后开始计数.

3.1. findall函数

findall函数返回一个列表,原型实现如下:


def findall(elem, path, namespaces=None):
    return list(iterfind(elem, path, namespaces))

findall本质是调用iterfind函数实现。
  
参数说明:
  elem:被搜索的元素。
  path:查找路径。
  namespaces=None:指定path的命名空间。

import xml.etree.ElementPath
import xml.etree.ElementInclude

print(xml.etree.ElementPath.ops)
print(xml.etree.ElementPath.xpath_tokenizer_re)

root = xml.etree.ElementInclude.default_loader('codes/books.xml', 'xml')
eles = xml.etree.ElementPath.findall(root, 'book')
print('findall结果:----------')
print(eles)

{'': <function prepare_child at 0x105a53378>, '*': <function prepare_star at 0x105a53400>, '.': <function prepare_self at 0x105a53488>, '..': <function prepare_parent at 0x105a53598>, '//': <function prepare_descendant at 0x105a53510>, '[': <function prepare_predicate at 0x105a53620>}
re.compile('(\'[^\']*\'|\\"[^\\"]*\\"|::|//?|\\.\\.|\\(\\)|[/.*:\\[\\]\\(\\)@=])|((?:\\{[^}]+\\})?[^/\\[\\]\\(\\)@=\\s]+)|\\s+')
findall结果:----------
[<Element 'book' at 0x105a4d9f8>, <Element 'book' at 0x105a62048>, <Element 'book' at 0x105a62228>]

3.2. find函数

find函数返回查找的第一个元素,函数原型实现如下:


def find(elem, path, namespaces=None):
    return next(iterfind(elem, path, namespaces), None)

从find的实现代码中可以看出,实际find每次返回都是iterfind返回的迭代器中的下一个元素,因为iterfind每次返回都是全新的查找的结果,所以find每次返回都是查找列表中的第一个。

import xml.etree.ElementPath
import xml.etree.ElementInclude

root = xml.etree.ElementInclude.default_loader('codes/books.xml', 'xml')
ele = xml.etree.ElementPath.find(root, 'book')
if ele:
    print(ele.attrib)



{'category': 'Python'}

3.3. findtext函数

findtext是查找满足path条件的text内容,函数原型如下:


def findtext(elem, path, default=None, namespaces=None):
    try:
        elem = next(iterfind(elem, path, namespaces))
        return elem.text or ""
    except StopIteration:
        return default

注意:返回的不是查找的元素,而是元素的text文本。
  
参数说明:
  default:就是找到的元素没有文本内容的时候,使用该值替代。

import xml.etree.ElementPath
import xml.etree.ElementInclude


root = xml.etree.ElementInclude.default_loader('codes/note.xml', 'xml')
ele = xml.etree.ElementPath.findtext(root, 'to', '缺省值')
print(ele)

Tove

3.4. iterfind函数

iterfind返回一个迭代器类型,实际本质是一个生成器(class 'generator'),该函数的原型实现如下:


def iterfind(elem, path, namespaces=None):

import xml.etree.ElementPath
import xml.etree.ElementInclude

root = xml.etree.ElementInclude.default_loader('codes/books.xml', 'xml')
eles = xml.etree.ElementPath.iterfind(root, 'book')
print(eles)
print(type(eles))
for e in eles:
    print(e)

<generator object prepare_child.<locals>.select at 0x105a487d8>
<class 'generator'>
<Element 'book' at 0x105a62b88>
<Element 'book' at 0x105a629a8>
<Element 'book' at 0x105a72048>

四、ElementTree模块

ElementTree模块提供xml的dom解析实现,该模块的类包含:
  builtins.SyntaxError(builtins.Exception)
     ParseError
  builtins.object
   Element:xml的基本单元:元素
   ElementTree:xml元素构成的树状数据结构
   QName:Quality Name:根元素
   TreeBuilder:xml树构建器
   XMLParser:xml解析器
   XMLPullParser:xml非阻塞解析器
  
同时提供一组快捷函数:
  Comment(text=None)
  PI = ProcessingInstruction(target, text=None)
  ProcessingInstruction(target, text=None)
  SubElement(...)
  XML(text, parser=None)
  XMLID(text, parser=None)
  dump(elem)
  fromstring = XML(text, parser=None)
  fromstringlist(sequence, parser=None)
  iselement(element)
  iterparse(source, events=None, parser=None)
  parse(source, parser=None)
  register_namespace(prefix, uri)
  tostring(element, encoding=None, method=None, *, short_empty_elements=True)
  tostringlist(element, encoding=None, method=None, *, short_empty_elements=True)

4.1. TreeBuilder与XMLParser阻塞解析

TreeBuilder负责构建Element对象树,XMLParser负责解析xml内容。

TreeBuilder提供基本的树的构建功能,我们只要返回root Element元素即可得到Element树。

# coding = utf-8
from xml.etree.ElementTree import TreeBuilder
from xml.etree.ElementTree import XMLParser


class MyBuilder(TreeBuilder):
    is_root = True
    root_element = None

    def start(self, tag, attrs):
        elem = super().start(tag, attrs)
        if self.is_root:
            self.root_element = elem
            self.is_root = False
        return elem


builder = MyBuilder()
parser = XMLParser(target=builder)
fd = open('codes/books.xml', 'r')
xml_data = fd.read()
parser.feed(xml_data)

root = builder.root_element
for item in root.getchildren():
    print(item.tag, ':', item.attrib)
    for it in item.getchildren():
        print('\t|-', it, ':', it.tag,':', it.text)

book : {'category': 'Python'}
    |- <Element 'title' at 0x105a62e58> : title : 网络爬虫开发
    |- <Element 'author' at 0x105a623b8> : author : 蜘蛛精
    |- <Element 'year' at 0x105a620e8> : year : 2018
    |- <Element 'price' at 0x105a621d8> : price : 66.50
    |- <Element 'publisher' at 0x105a62048> : publisher : 清华大学出版社
book : {'category': '系统运维'}
    |- <Element 'title' at 0x105a62a98> : title : K8S运维指南r
    |- <Element 'author' at 0x105a62728> : author : 马哥教育
    |- <Element 'year' at 0x105a624a8> : year : 2018
    |- <Element 'price' at 0x105a62408> : price : 99.00
    |- <Element 'publisher' at 0x105a62458> : publisher : 机械版社
book : {'category': '区块链'}
    |- <Element 'title' at 0x105a625e8> : title : 以太坊智能合约开发
    |- <Element 'author' at 0x105a624f8> : author : 钱多多
    |- <Element 'year' at 0x105a62598> : year : 2019
    |- <Element 'price' at 0x105a62548> : price : 88.95
    |- <Element 'publisher' at 0x105a723b8> : publisher : 邮电出版社

4.2. XMLPullParser 非阻塞解析

XMLPullParser与XMLParser的区别是非阻塞,阻塞的特点在指定回调事件,非阻塞的特点是产生事件列表。
XMLPullParser的read_events(self)返回的是一个数据生成器,也是迭代器。

其中第一个节点就是root节点,可以直接遍历。

# coding = utf-8
from xml.etree.ElementTree import XMLPullParser

events = ("start", "end", "start-ns", "end-ns")
parser = XMLPullParser(events=events)
fd = open('codes/books.xml', 'r')
xml_data = fd.read()
parser.feed(xml_data)
# 转换成列表操作
re_events = list(parser.read_events())
# 构造xml的root
root_element = re_events[0][1]


# 从根节点偏离element树
def list_tree(element, depth):
    print('\t' * depth, element.tag, ":", element.text if element.text.strip() != '' else '')
    children_elements = element.getchildren()
    if children_elements:
        for e_ in children_elements:
            list_tree(e_, depth+1)


list_tree(root_element, 0)

 books : 
     book : 
         title : 网络爬虫开发
         author : 蜘蛛精
         year : 2018
         price : 66.50
         publisher : 清华大学出版社
     book : 
         title : K8S运维指南r
         author : 马哥教育
         year : 2018
         price : 99.00
         publisher : 机械版社
     book : 
         title : 以太坊智能合约开发
         author : 钱多多
         year : 2019
         price : 88.95
         publisher : 邮电出版社

4.3. Element对象与ElementTree对象

  ElementTree实际是Element的封装,从上面的XMLParser与XMLPullParser可以看出,已经实现基本的Element树结构。
  ElementTree与XMLParser、XMLPullParser返回的Element都是Element树结构。但ElementTree提供更加快捷的XMLParser、XMLPullParser的解析功能与xml加载功能,同时提供xml保存功能。

Element对象就是每个标签的封装,提供如下基本数据封装。
  | -attrib
  |  类型是字典,用来封装元素的所有属性。
  |
  | -tag
  |  类型字符串,用来封装元素的标签名。
  |
  | -tail
  |   类型字符串,标签结束后的文本,也可以是None。
  |
  | -text
  | 类型字符串,开始标签后的文本,也可以是None。

构造器:
  | __init__(self, /, *args, **kwargs)

同时提供了一组对封装数据的操作函数:
  |
  | -makeelement(self, tag, attrib, /)
  |
  | -append(self, subelement, /)
  |
  | -insert(self, index, subelement, /)
  |
  | -remove(self, subelement, /)
  |
  | -set(self, key, value, /)
  |
  | -clear(self, /)
  |
  | -extend(self, elements, /)
  |
  | -find(self, /, path, namespaces=None)
  |
  | -findall(self, /, path, namespaces=None)
  |
  | -findtext(self, /, path, default=None, namespaces=None)
  |
  | -get(self, /, key, default=None)
  |
  | -getchildren(self, /)
  |
  | -getiterator(...)
  |  iter($self, /, tag=None)
  |  --
  |
  | -items(self, /)
  |
  | -iter(self, /, tag=None)
  |
  | -iterfind(self, /, path, namespaces=None)
  |
  | -itertext(self, /)
  |
  | -keys(self, /)

  | - __init__(self, element=None, file=None)
  | - find(self, path, namespaces=None)
  | - findall(self, path, namespaces=None)
  | - findtext(self, path, default=None, namespaces=None)
  | - getiterator(self, tag=None)
  | - getroot(self)
  | - iter(self, tag=None)
  | - iterfind(self, path, namespaces=None)
  | - parse(self, source, parser=None)
  | - write(self, file_or_filename, encoding=None, xml_declaration=None, default_namespace=None, method=None, *, short_empty_elements=True)
  | - write_c14n(self, file)
  
注意:
  c14n说明:W3C推出了C14n标准用于XML数据的规范化。
  目前还没有c14n真正的实现。

# coding = utf-8
from xml.etree.ElementTree import ElementTree
from xml.etree.ElementTree import Element

tree = ElementTree()
tree.parse('codes/books.xml')

root_element = tree.getroot()


# 从根节点偏离element树
def list_tree(element, depth):
    print('\t' * depth, element.tag, ":", element.text if element.text.strip() != '' else '')
    children_elements = element.getchildren()
    if children_elements:
        for e_ in children_elements:
            list_tree(e_, depth+1)


list_tree(root_element, 0)

 books : 
     book : 
         title : 网络爬虫开发
         author : 蜘蛛精
         year : 2018
         price : 66.50
         publisher : 清华大学出版社
     book : 
         title : K8S运维指南r
         author : 马哥教育
         year : 2018
         price : 99.00
         publisher : 机械版社
     book : 
         title : 以太坊智能合约开发
         author : 钱多多
         year : 2019
         price : 88.95
         publisher : 邮电出版社

ElementTree还可以通过构造器封装root元素,提供更加方便的操作。

五、cElementTree模块

cElementTree模块是xml.etree.ElementTree模块的别名,目前已经不推荐使用。


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容