文档对象模型
xml.dom 模块对于 Python 程序员来说,可能是使用 XML 文档时功能最强大的工具。不幸的是,XML-SIG 提供的文档目前来说还比较少。W3C 语言无关的 DOM 规范填补了这方面的部分空白。但 Python 程序员最好有一个特定于 Python 语言的 DOM 的快速入门指南。本文旨在提供这样一个指南。在 上一篇专栏文章 中,某些样本中使用了样本 quotations.dtd 文件,并且这些文件可以与本文中的代码样本档案文件一起使用。
有必要了解 DOM 的确切含义。这方面,正式解释非常好:
"文档对象模型"是平台无关和语言无关的接口,它允许程序和脚本动态访问和更新文档的内容、结构和样式。可以进一步处理文档,而处理的结果也可以合并到已显示的页面中。(万维网联盟 DOM 工作组)
DOM 将 XML 文档转换成树 -- 或森林 -- 表示。万维网联盟 (W3C) 规范给出了一个 HTML 表的 DOM 版本作为例子。
如上图所示,DOM 从一个更加抽象的角度定义了一组可以遍历、修剪、改组、输出和操作树的方法,而这种方法要比 XML 文档的线性表示更为便利。
将 HTML 转换成 XML
有效的 HTML 几乎就是有效的 XML,但又不完全相同。这里有两个主要的差异,XML 标记是区分大小写的,并且所有 XML 标记都需要一个显式的结束符号(作为结束标记,而这对于某些 HTML 标记是可选的;例如: )。使用 xml.dom 的一个简单示例就是使用 HtmlBuilder() 类将 HTML 转换成 XML。
try_dom1.py
"""Convert a valid HTML document to XML
USAGE: python try_dom1.py < infile.html > outfile.xml
"""
import
sys
from
xml.dom
import
core
from
xml.dom.html_builder
import
HtmlBuilder
# Construct an HtmlBuilder object and feed the data to it
b = HtmlBuilder()
b.feed(sys.stdin.read())
# Get the newly-constructed document object
doc = b.document
# Output it as XML
print
doc.toxml()
HtmlBuilder() 类很容易实现它继承的部分基本 xml.dom.builder 模板的功能,它的源码值得研究。然而,即使我们自己实现了模板功能,DOM 程序的轮廓还是相似的。在一般情况下,我们将用一些方法构建一个 DOM 实例,然后对该实例进行操作。DOM 实例的 .toxml() 方法是一种生成 DOM 实例的字符串表示的简单方法(在以上的情况中,只要在生成后将它打印出来)。
将 Python 对象转换成 XML
Python 程序员可以通过将任意 Python 对象导出为 XML 实例来实现相当多的功能和通用性。这就允许我们以习惯的方式来处理 Python 对象,并且可以选择最终是否使用实例属性作为生成 XML 中的标记。只需要几行(从 building.py 示例派生出),我们就可以将 Python"原生"对象转换成 DOM 对象,并对包含对象的那些属性执行递归处理。
try_dom2.py
"""Build a DOM instance from scratch, write it to XML
USAGE: python try_dom2.py > outfile.xml
"""
import
types
from
xml.dom
import
core
from
xml.dom.builder
import
Builder
# Recursive function to build DOM instance from Python instance
defobject_convert
(builder, inst):
# Put entire object inside an elem w/ same name as the class.
builder.startElement(inst.__class__.__name__)
for
attr
in
inst.__dict__.keys():
if
attr[0] ==
'_':
# Skip internal attributes
continue
value = getattr(inst, attr)
if
type(value) == types.InstanceType:
# Recursively process subobjects
object_convert(builder, value)
else
:
# Convert anything else to string, put it in an element
builder.startElement(attr)
builder.text(str(value))
builder.endElement(attr)
builder.endElement(inst.__class__.__name__)
if
__name__ ==
'__main__':
# Create container classes
classquotations
:
pass
classquotation
:
pass
# Create an instance, fill it with hierarchy of attributes
inst = quotations()
inst.title =
"Quotations file (not quotations.dtd conformant)"
inst.quot1 = quot1 = quotation()
quot1.text =
"""'"is not a quine" is not a quine' is a quine"""
quot1.source =
"Joshua Shagam, kuro5hin.org"
inst.quot2 = quot2 = quotation()
quot2.text =
"Python is not a democracy. Voting doesn't help. "+\
"Crying may..."
quot2.source =
"Guido van Rossum, comp.lang.python"
# Create the DOM Builder
builder = Builder()
object_convert(builder, inst)
print
builder.document.toxml()
函数 object_convert() 有一些限制。例如,不可能用以上的过程生成符合 XML 文档的 quotations.dtd:#PCDATA 文本不能直接放到 quotation 类中,而只能放到类的属性中(如 .text )。一个简单的变通方法就是让 object_convert() 以特殊方式处理一个带有名称的属性,例如 .PCDATA 。可以用各种方法使对 DOM 的转换变得更巧妙,但该方法的妙处在于我们可以从整个 Python 对象开始,以简明的方式将它们转换成 XML 文档。
还应值得注意的是在生成的 XML 文档中,处于同一个级别的元素没有什么明显的顺序关系。例如,在作者的系统中使用特定版本的 Python,源码中定义的第二个 quotation 在输出中却第一个出现。但这种顺序关系在不同的版本和系统之间会改变。Python 对象的属性并不是按固定顺序排列的,因此这种特性就具有意义。对于与数据库系统相关的数据,我们希望它们具有这种特性,但是对于标记为 XML 的文章却显然不希望具有这种特性(除非我们想要更新 William Burroughs 的 "cut-up" 方法)。
将 XML 文档转换成 Python 对象
从 XML 文档生成 Python 对象就像其逆向过程一样简单。在多数情况下,用 xml.dom 方法就可以了。但在某些情况下,最好使用与处理所有"类属"Python 对象相同的技术来处理从 XML 文档生成的对象。例如,在以下的代码中,函数 pyobj_printer() 也许是已经用来处理任意 Python 对象的函数。
try_dom3.py
"""Read in a DOM instance, convert it to a Python object
"""
from
xml.dom.utils
import
FileReader
classPyObject
:
pass
defpyobj_printer
(py_obj, level=0):
"""Return a "deep" string description of a Python object"""
from
string
import
join, split
import
types
descript =
''
for
membname
in
dir(py_obj):
member = getattr(py_obj,membname)
if
type(member) == types.InstanceType:
descript = descript + (
' '*level) +
'{'+membname+
'}\n'
descript = descript + pyobj_printer(member, level+3)
elif
type(member) == types.ListType:
descript = descript + (
' '*level) +
'['+membname+
']\n'
for
i
in
range(len(member)):
descript = descript+(
' '*level)+str(i+1)+
': '+ \
pyobj_printer(member[i],level+3)
else
:
descript = descript + membname+
'='
descript = descript + join(split(str(member)[:50]))+
'...\n'
return
descript
defpyobj_from_dom
(dom_node):
"""Converts a DOM tree to a "native" Python object"""
py_obj = PyObject()
py_obj.PCDATA =
''
for
node
in
dom_node.get_childNodes():
if
node.name ==
'#text':
py_obj.PCDATA = py_obj.PCDATA + node.value
elif
hasattr(py_obj, node.name):
getattr(py_obj, node.name).append(pyobj_from_dom(node))
else
:
setattr(py_obj, node.name, [pyobj_from_dom(node)])
return
py_obj
# Main test
dom_obj = FileReader(
"quotes.xml").document
py_obj = pyobj_from_dom(dom_obj)
if
__name__ ==
"__main__":
print
pyobj_printer(py_obj)
这里的关注焦点应该是函数 pyobj_from_dom() ,特别是起实际作用的 xml.dom 方法 .get_childNodes() 。在 pyobj_from_dom() 中,我们直接抽取标记之间的所有文本,将它放到保留属性 .PCDATA 中。对于任何遇到的嵌套标记,我们创建一个新属性,其名称与标记匹配,并将一个列表分配给该属性,这样就可以潜在地包含在在父代块中多次出现的标记。当然,使用列表要维护在 XML 文档中遇到的标记的顺序。
除了使用旧的 pyobj_printer() 类属函数(或者,更复杂和健壮的函数)之外,我们可以使用正常的属性记号来访问 py_obj 的元素。
Python 交互式会话
>>>
from
try_dom3
import
*
>>> py_obj.quotations[0].quotation[3].source[0].PCDATA
'Guido van Rossum, '
重新安排 DOM 树
DOM 的一大优点是它可以让程序员以非线性方式对 XML 文档进行操作。由相匹配的开/关标记括起的每一块都只是 DOM 树中的一个"节点"。当以类似于列表的方式维护节点以保留顺序信息时,则顺序并没有什么特殊之处,也并非不可改变。我们可以轻易地剪下某个节点,嫁接到 DOM 树的另一个位置(如果 DTD 允许,甚至嫁接到另一层上)。或者添加新的节点、删除现有节点,等等。
try_dom4.py
"""Manipulate the arrangement of nodes in a DOM object
"""
from
try_dom3
import
*
#-- Var 'doc' will hold the single <quotations> "trunk"
doc = dom_obj.get_childNodes()[0]
#-- Pull off all the nodes into a Python list
# (each node is a <quotation> block, or a whitespace text node)
nodes = []
while
1:
try
: node = doc.removeChild(doc.get_childNodes()[0])
except
:
break
nodes.append(node)
#-- Reverse the order of the quotations using a list method
# (we could also perform more complicated operations on the list:
# delete elements, add new ones, sort on complex criteria, etc.)
nodes.reverse()
#-- Fill 'doc' back up with our rearranged nodes
for
node
in
nodes:
# if second arg is None, insert is to end of list
doc.insertBefore(node, None)
#-- Output the manipulated DOM
print
dom_obj.toxml()
如果我们将 XML 文档只看作一个文本文件,或者使用一个面向序列的模块(如 xmllib 或 xml.sax),那么在以上几行中执行对 quotation 节点的重新安排操作将引出一个值得考虑的问题。然而如果使用 DOM,则问题就如同对 Python 列表执行的任何其它操作一样简单。
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。