设为首页 收藏本站
查看: 802|回复: 0

[经验分享] Python:minidom模块 用于解析XML

[复制链接]

尚未签到

发表于 2018-8-6 07:08:28 | 显示全部楼层 |阅读模式
  下面是片段分类的一个示例文--catalog.xml
  <?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?>
  <catalog>
  <maxid>4</maxid>

  <item>  <caption>Python</caption>

  <item>  <caption>测试</caption>
  </item>
  </item>

  <item>  <caption>Zope</caption>
  </item>
  </catalog>
  分类是树状结构,显示出来可能为:
  Python
  测试
  Zope
  先简单介绍一下XML的知识,如果你已经知道了可以跳过去。
  1. XML文档的编码
  此XML文档的编码为utf-8,因此你看到的“测试”其实是UTF-8编码。在XML文档的处理中都是使用UTF-8编码进行的,因此,如果你不写明encoding的话,都是认为文件是UTF-8编码的。在Python中,好象只支持几种编码,象我们常用的GB2312码就不支持,因此建议大家在处理XML时使用UTF-8编码。
  2. XML文档的结构
  XML文档有XML头信息和XML信息体。头信息如:
  <?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?>
  它表明了此XML文档所用的版本,编码方式。有些复杂的还有一些文档类型的定义(DOCTYPE),用于定义此XML文档所用的DTD或Schema和一些实体的定义。这里并没有用到,而且我也不是专家,就不再细说了。
  XML信息体是由树状元素组成。每个XML文档都有一个文档元素,也就是树的根元素,所有其它的元素和内容都包含在根元素中。
  3. DOM
  DOM是Document Object Model的简称,它是以对象树来表示一个XML文档的方法,使用它的好处就是你可以非常灵活的在对象中进行遍历。
  4. 元素和结点
  元素就是标记,它是成对出现的。XML文档就是由元素组成的,但元素与元素之间可以有文本,元素的内容也是文本。在minidom中有许多的结点,元素也属于结点的一种,它不是叶子结点,即它存在子结点;还存在一些叶子结点,如文本结点,它下面不再有子结点。
  象catalog.xml中,文档元素是catalog,它下面有两种元素:maxid和item。maxid用来表示当前最大的item的id 值。每一个item都有一个id属性,id属性是唯一的,在 NewEdit 中用来生成每个分类所对应的代码片段的XML文档名,因此不能重复,而且它是一个递增的值。item元素有一个caption子元素,用来表示此分类项的名称,它还可以包含item元素。这样,就定义了一个树状XML结构,下面让我们看一看如果把它们读出来。
  一、得到dom对象
  >>> import xml.dom.minidom
  >>> dom = xml.dom.minidom.parse('d:/catalog.xml')
  这样我们得到了一个dom对象,它的第一个元素应该是catalog。
  二、得到文档元素对象
  >>> root = dom.documentElement
  这样我们得到了根元素(catalog)。
  三、结点属性
  每一个结点都有它的nodeName,nodeValue,nodeType属性。nodeName为结点名字。
  >>> root.nodeName
  u'catalog'
  nodeValue是结点的值,只对文本结点有效。nodeType是结点的类型,现在有以下几种:
  'ATTRIBUTE_NODE'
  'CDATA_SECTION_NODE'
  'COMMENT_NODE'
  'DOCUMENT_FRAGMENT_NODE'
  'DOCUMENT_NODE'
  'DOCUMENT_TYPE_NODE'
  'ELEMENT_NODE'
  'ENTITY_NODE'
  'ENTITY_REFERENCE_NODE'
  'NOTATION_NODE'
  'PROCESSING_INSTRUCTION_NODE'
  'TEXT_NODE'
  这些结点通过名字很好理解。catalog是ELEMENT_NODE类型。
  >>> root.nodeType
  1
  >>> root.ELEMENT_NODE
  1
  四、子元素、子结点的访问
  访问子元素、子结点的方法很多,对于知道元素名字的子元素,可以使用getElementsByTagName方法,如读取maxid子元素:
  >>> root.getElementsByTagName_r('maxid')
  [<DOM Element: maxid at 0xb6d0a8>]
  这样返回一个列表,由于我们的例子中maxid只有一项,因此列表也只有一项。
  如果想得到某个元素下的所有子结点(包括元素),可以使用childNodes属性:
  >>> root.childNodes
  [<DOM Text node &quot;\n &quot;>, <DOM Element: maxid at 0xb6d0a8>, <DOM Text node &quot;\n &quot;>, <DOM Element: item at 0xb6d918>, <DOM Text node &quot;\n &quot;>, <DOM Element: item at 0xb6de40>, <DOM Text node &quot;\n &quot;>, <DOM Element: item at 0xb6dfa8>, <DOM Text node &quot;\n&quot;>]
  可以看出所有两个标记间的内容都被视为文本结点。象每行后面的回车,都被看到文本结点。从上面的结果我们可以看出每个结点的类型,本例中有文本结点和元素结点;结点的名字(元素结点);结点的值(文本结点)。每个结点都是一个对象,不同的结点对象有不同的属性和方法,更详细的要参见文档。由于本例比较简单,只涉及文本结点和元素结点。
  getElementsByTagName可以搜索当前元素的所有子元素,包括所有层次的子元素。childNodes只保存了当前元素的第一层子结点。
  这样我们可以遍历childNodes来访问每一个结点,判断它的nodeType来得到不同的内容。如,打印出所有元素的名字:
  >>> for node in root.childNodes:
  if node.nodeType == node.ELEMENT_NODE:
  print node.nodeName
  maxid
  item
  item
  对于文本结点,想得到它的文本内容可以使用: .data属性。
  对于简单的元素,如:<caption>Python</caption>,我们可以编写这样一个函数来得到它的内容(这里为Python)。
  def getTagText(root, tag):
  node = root.getElementsByTagName_r(tag)[0]
  rc = &quot;&quot;
  for node in node.childNodes:
  if node.nodeType in ( node.TEXT_NODE, node.CDATA_SECTION_NODE):
  rc = rc + node.data
  return rc
  这个函数只处理找到的第一个符合的子元素。它会将符合的第一个子元素中的所有文本结点拼在一起。当nodeType为文本类结点时,node.data为文本的内容。如果我们考查一下元素caption,我们可能看到:
  [<DOM Text node &quot;Python&quot;>]
  说明caption元素只有一个文本结点。
  如果一个元素有属性,那么可以使用getAttribute方法,如:
  >>> itemlist = root.getElementsByTagName_r('item')
  >>> item = itemlist[0]
  >>> item.getAttribute('id')
  u'1'
  这样就得到了第一个item元素的属性值。
  下面让我们简单地小结一下如何使用minidom来读取XML中的信息
  1. 导入xml.dom.minidom模块,生成dom对象
  2. 得到文档对象(根对象)
  3. 通过getElementsByTagName_r()方法和childNodes属性(还有其它一些方法和属性)找到要处理的元素
  4. 取得元素下文本结点的内容
  二.写入.
  下面我来演示一下如何从无到有生成象catalog.xml一样的XML文件。
  一、生成dom对象
  >>> import xml.dom.minidom
  >>> impl = xml.dom.minidom.getDOMImplementation()
  >>> dom = impl.createDocument(None, 'catalog', None)
  这样就生成了一个空的dom对象。其中catalog为文档元素名,即根元素名。
  二、显示生成的XML内容
  每一个dom结点对象(包括dom对象本身)都有输出XML内容的方法,如:toxml(), toprettyxml()
  toxml()输出紧凑格式的XML文本,如:
  <catalog><item>test</item><item>test</item></catalog>
  toprettyxml()输出美化后的XML文本,如:
  <catalog>
  <item>
  test
  </item>
  <item>
  test
  </item>
  </catalog>
  可以看出,它是将每个结点后面都加入了回车符,并且自动处理缩近。但对于每一个元素,如果元素只有文本内容,则我希望元素的tag与文本是在一起的,如:
  <item>test</item>
  而不想是分开的格式,但minidom本身是不支持这样的处理。关于如何实现形如:
  <catalog>
  <item>test</item>
  <item>test</item>
  </catalog>
  这样的XML格式,后面我们再说。
  三、生成各种结点对象
  dom对象拥有各种生成结点的方法,下面列出文本结点,CDATA结点和元素结点的生成过程。
  1. 文本结点的生成
  >>> text=dom.createTextNode('test')
  test
  要注意的是,在生成结点时,minidom并不对文本字符进行检查,象文本中如果出现了'<','&'之类的字符,应该转换为相应的实体符号'&lt;','&amp;'才可以,这里没有做这个处理。
  2. CDATA结点的生成
  >>> data = dom.createCDATASection('aaaaaa\nbbbbbb')
  >>> data.toxml()
  '<![CDATA[aaaaaa\nbbbbbb]]>'
  CDATA是用于包括大块文本,同时可以不用转换'<','&'字符的标记,它是用<![CDATA[文本]]>来包括的。但文本中不可以有&quot;]]>&quot;这样的串存在。生成结点时minidom不作这些检查,只有当你输出时才有可能发现有错。
  3. 元素结点的生成
  >>> item = dom.createElement_x_x_x_x_x('caption')
  >>> item.toxml()
  '<caption/>'
  对于象元素这样的结点,生成的元素结点其实是一个空元素,即不包含任何文本,如果要包含文本或其它的元素,我们需要使用a() 或insertBefore()之类的方法将子结点加就到元素结点中。如将上面生成的text结点加入到caption元素结点中:
  >>> item.a(text)
  <DOM Text node &quot;test&quot;>
  >>> item.toxml()
  '<caption>test</caption>'
  使用元素对象的setAttribute()方法可以向元素中加入属性,如:
  >>> item.setAttribute('id', 'idvalue')
  >>> item.toxml()

  '<caption>
  四、生成dom对象树
  我们有了dom对象,又知道了如何生成各种结点,包括叶子结点(不包含其它结点的结点,如文本结点)和非叶子结点(包含其它结点的结点,如元素结点)的生成,然后就需要利用结点对象本身的a()或insertBefore()方法将各个结点根据在树中的位置连起来,串成一棵树。最后要串到文档结点上,即根结点上。如一个完整的示例为:
  >>> import xml.dom.minidom
  >>> impl = xml.dom.minidom.getDOMImplementation()
  >>> dom = impl.createDocument(None, 'catalog', None)
  >>> root = dom.documentElement
  >>> item = dom.createElement_x_x_x_x_x('item')
  >>> text = dom.createTextNode('test')
  >>> item.a(text)
  <DOM Text node &quot;test&quot;>
  >>> root.a(item)
  <DOM Element: item at 0xb9cf80>
  >>> print root.toxml()
  <catalog><item>test</item></catalog>
  五、简单生成元素结点的函数
  下面是我写的一个小函数,用于简单的生成类似于:
  <caption>test</caption>
  或形如:
  <item><![CDATA[test]]></item>
  的元素结点
  1 def makeEasyTag(dom, tagname, value, type='text'):
  2 tag = dom.createElement_x_x_x_x_x(tagname)
  3 if value.find(']]>') > -1:
  4 type = 'text'
  5 if type == 'text':
  6 value = value.replace('&', '&amp;')
  7 value = value.replace('<', '&lt;')
  8 text = dom.createTextNode(value)
  9 elif type == 'cdata':
  10 text = dom.createCDATASection(value)
  11 tag.a(text)
  12 return tag
  参数说明:

  • dom为dom对象
  • tagname为要生成元素的名字,如'item'
  • value为其文本内容,可以为多行
  • type为文本结点的格式,'text'为一般Text结点,'cdata'为CDATA结点
  函数处理说明:

  • 首先创建元素结点
  • 查找文本内容是否有']]>',如果找到,则此文本结点只可以是Text结点
  • 如果结点类型为'text',则对文本内容中的'<'替换为'&lt;','&'替换为'&amp;',再生成文本结点
  • 如果结点类型为'cdata',则生成CDATA结点
  • 将生成的文本结点追加到元素结点上
  因此这个小函数可以自动地处理字符转化及避免CDATA结点中出现']]>'串。
  上面生成'item'结点的语句可以改为:
  >>> item = makeEasyTag(dom, 'item', 'test')
  >>> item.toxml()
  '<item>test</item>'
  六、写入到XML文件中
  dom对象树已经生成好了,我们可以调用dom的writexml()方法来将内容写入文件中。writexml()方法语法格式为:
  writexml(writer, indent, addindent, newl, encoding)


  • writer是文件对象
  • indent是每个tag前填充的字符,如:' ',则表示每个tag前有两个空格
  • addindent是每个子结点的缩近字符
  • newl是每个tag后填充的字符,如:'\n',则表示每个tag后面有一个回车
  • encoding是生成的XML信息头中的encoding属性值,在输出时minidom并不真正进行编码的处理,如果你保存的文本内容中有汉字,则需要自已进行编码转换。
  writexml方法是除了writer参数必须要有外,其余可以省略。下面给出一个文本内容有汉字的示例:
  1 >>> import xml.dom.minidom
  2 >>> impl = xml.dom.minidom.getDOMImplementation()
  3 >>> dom = impl.createDocument(None, 'catalog', None)
  4 >>> root = dom.documentElement
  5 >>> text = unicode('汉字示例', 'cp936')
  6 >>> item = makeEasyTag(dom, 'item', text)
  7 >>> root.a(item)
  8 <DOM Element: item at 0xb9ceb8>
  9 >>> root.toxml()
  10 u'<catalog><item>\u6c49\u5b57\u793a\u4f8b</item></catalog>'
  11 >>> f=file('d:/test.xml', 'w')
  12 >>> import codecs
  13 >>> writer = codecs.lookup('utf-8')[3](f)
  14 >>> dom.writexml(writer, encoding='utf-8')
  15 >>> writer.close()
  5行因为XML处理时内部使用Unicode编码,因此象汉字首先要转成Unicode,如果你不做这一步minicode并不检查,并且保存时可能不会出错。但读取时可能会出错。
  12-13行 生成UTF-8编码的写入流对象,这样在保存时会自动将Unicode转换成UTF-8编码。
  这样写XML文件就完成了。
  三.美化.
  对于dom对象的writexml()方法,虽然可以控制一些格式上的输出,但结果并不让人满意。比如我想实现:
  <catalog>
  <item>test</item>
  <item>test</item>
  </catalog>
  而不是:
  <catalog>
  <item>
  test
  </item>
  <item>
  test
  </item>
  </catalog>
  如果是象下面的输出结果我无法区分原来文本中是否带有空白,而上一种结果则不存在这一问题。好在我在wxPython自带的XML资源编辑器(xred)发现了美化的代码。代码如下:
  1 def Indent(dom, node, indent = 0):
  2 # Copy child list because it will change soon
  3 children = node.childNodes[:]
  4 # Main node doesn't need to be indented
  5 if indent:
  6 text = dom.createTextNode('\n' + '\t' * indent)
  7 node.parentNode.insertBefore(text, node)
  8 if children:
  9 # Append newline after last child, except for text nodes
  10 if children[-1].nodeType == node.ELEMENT_NODE:
  11 text = dom.createTextNode('\n' + '\t' * indent)
  12 node.a(text)
  13 # Indent children which are elements
  14 for n in children:
  15 if n.nodeType == node.ELEMENT_NODE:
  16 Indent(dom, n, indent + 1)
  参数说明:
  dom为dom对象
  node为要处理的元素结点
  indent指明缩近的层数
  函数说明:
  Indent是一个递归函数,当一个结点有子元素时进行递归处理。主要是解决子元素的换行和缩近的处理。这里缩近是写死的,每一级缩近使用一个制表符。如果你愿意可以改为你想要的内容。就是把函数中的'\t'换替一下。或干脆写成一个全局变量,或参数以后改起来可能要容易的多。不过在 NewEdit 中,这样的处理足够了,就没有做这些工作。
  Indent基本的想法就是递归遍历所有子结点,在所有需要加入回车和缩近的地方插入相应的文本结点。这样再使用writexml()输出时就是缩近好了的。具体程序不再细说,直接用就行了。
  但这里要注意的是:
  Indent()要修改原dom对象,因此在调用它之前最好先复制一个临时dom对象,使用完毕后再清除这个临时dom对象即可。下面是详细的调用过程:
  1 domcopy = dom.cloneNode(True)
  2 Indent(domcopy, domcopy.documentElement)
  3 f = file(xmlfile, 'wb')
  4 writer = codecs.lookup('utf-8')[3](f)
  5 domcopy.writexml(writer, encoding = 'utf-8')
  6 domcopy.unlink()
  1行 克隆一个dom对象
  2行 进行缩近处理
  3-4行 进行UTF-8编码处理
  5行 生成XML文件
  6行 清除dom对象的内容
  经过这番处理之后,你的XML文档应该好看多了。
  去除多余的行:
  from xml.dom inport minidom
  def fixed_writexml(self, writer, indent=&quot;&quot;, addindent=&quot;&quot;, newl=&quot;&quot;):
  # indent = current indentation
  # addindent = indentation to add to higher levels
  # newl = newline string
  writer.write(indent+&quot;<&quot; + self.tagName)
  attrs = self._get_attributes()
  a_names = attrs.keys()
  a_names.sort()
  for a_name in a_names:
  writer.write(&quot; %s=\&quot;&quot; % a_name)
  minidom._write_data(writer, attrs[a_name].value)
  writer.write(&quot;\&quot;&quot;)
  if self.childNodes:
  if len(self.childNodes) == 1 \
  and self.childNodes[0].nodeType == minidom.Node.TEXT_NODE:
  writer.write(&quot;>&quot;)
  self.childNodes[0].writexml(writer, &quot;&quot;, &quot;&quot;, &quot;&quot;)
  writer.write(&quot;</%s>%s&quot; % (self.tagName, newl))
  return
  writer.write(&quot;>%s&quot;%(newl))
  for node in self.childNodes:
  if node.nodeType is not minidom.Node.TEXT_NODE:
  node.writexml(writer,indent+addindent,addindent,newl)
  writer.write(&quot;%s</%s>%s&quot; % (indent,self.tagName,newl))
  else:
  writer.write(&quot;/>%s&quot;%(newl))
  # replace minidom's function with ours
  minidom.Element.writexml = fixed_writexml

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-547254-1-1.html 上篇帖子: python动态获取对象的属性和方法 下篇帖子: 把python文件编译成exe文件
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表