Разбор XML с помощью Python и minidom

Я использую Python (minidom) для синтаксического анализа XML-файла, который распечатывает иерархическую структуру, которая выглядит примерно так (здесь используется отступ, чтобы показать важные иерархические отношения):

My Document
Overview
    Basic Features
    About This Software
        Platforms Supported

Вместо этого программа несколько раз выполняет итерацию по узлам и производит следующие, распечатывая повторяющиеся узлы. (Глядя на список узлов на каждой итерации, становится очевидным, почему он это делает, но я не могу найти способ получить список узлов, который я ищу.)

My Document
Overview
Basic Features
About This Software
Platforms Supported
Basic Features
About This Software
Platforms Supported
Platforms Supported

Вот исходный XML-файл:

<?xml version="1.0" encoding="UTF-8"?>
<DOCMAP>
    <Topic Target="ALL">
        <Title>My Document</Title>
    </Topic>
    <Topic Target="ALL">
        <Title>Overview</Title>
        <Topic Target="ALL">
            <Title>Basic Features</Title>
        </Topic>
        <Topic Target="ALL">
            <Title>About This Software</Title>
            <Topic Target="ALL">
                <Title>Platforms Supported</Title>
            </Topic>
        </Topic>
    </Topic>
</DOCMAP>

Вот программа Python:

import xml.dom.minidom
from xml.dom.minidom import Node

dom = xml.dom.minidom.parse("test.xml")
Topic=dom.getElementsByTagName('Topic')
i = 0
for node in Topic:
    alist=node.getElementsByTagName('Title')
    for a in alist:
        Title= a.firstChild.data
        print Title

Я мог бы решить проблему, не вкладывая элементы «Тема», изменив имена тем нижнего уровня на что-то вроде «SubTopic1» и «SubTopic2». Но я хочу воспользоваться преимуществами встроенного иерархического структурирования XML без необходимости использования разных имен элементов; кажется, что я должен иметь возможность вкладывать элементы «Тема» и что должен быть какой-то способ узнать, на каком уровне «Тема» я сейчас смотрю.

Я без особого успеха пробовал несколько различных функций XPath.


person hWorks    schedule 20.10.2009    source источник
comment
Если вам нужен вывод первого, вы можете просто распечатать текст из каждого элемента - я не понимаю, как структурирование влияет на желаемый результат   -  person mmmmmm    schedule 21.10.2009


Ответы (5)


getElementsByTagName является рекурсивным, вы получите всех потомков с совпадающим tagName. Поскольку ваши темы содержат другие темы, у которых также есть заголовки, вызов будет получать более низкие заголовки много раз.

Если вы хотите запросить только все соответствующие прямые дочерние элементы, и у вас нет XPath, вы можете написать простой фильтр, например:

def getChildrenByTagName(node, tagName):
    for child in node.childNodes:
        if child.nodeType==child.ELEMENT_NODE and (tagName=='*' or child.tagName==tagName):
            yield child

for topic in document.getElementsByTagName('Topic'):
    title= list(getChildrenByTagName('Title'))[0]         # or just get(...).next()
    print title.firstChild.data
person bobince    schedule 20.10.2009
comment
Спасибо за попытку. Это не сработало, но дало мне несколько идей. Следующие работы (та же общая идея; FWIW, nodeType - ELEMENT_NODE): импортировать xml.dom.minidom из xml.dom.minidom import Node dom = xml.dom.minidom.parse (docmap.xml) def getChildrenByTitle (node) : для дочернего элемента в node.childNodes: если child.localName == 'Title': вывести дочерний элемент Topic = dom.getElementsByTagName ('Topic') для узла в Topic: alist = getChildrenByTitle (node) для списка в списке: # Title = a .firstChild.data Title = a.childNodes [0] .nodeValue печать Заголовок - person hWorks; 21.10.2009

Следующие работы:

import xml.dom.minidom
from xml.dom.minidom import Node

dom = xml.dom.minidom.parse("docmap.xml")

def getChildrenByTitle(node):
    for child in node.childNodes:
        if child.localName=='Title':
            yield child

Topic=dom.getElementsByTagName('Topic')
for node in Topic:
    alist=getChildrenByTitle(node)
    for a in alist:
        Title= a.childNodes[0].nodeValue
        print Title
person hWorks    schedule 21.10.2009
comment
Я бы вызвал функцию getTitle (или get_title), чтобы она возвращала не все непосредственные дочерние элементы Title, а только первый (так как в любом случае должен быть только один заголовок для каждого дочернего элемента). - person Martin v. Löwis; 21.10.2009
comment
Может, это то, чего я не понимаю. Мне нужны титулы всех непосредственных потомков. Может быть, лучше было бы getTitlesOfChildren. - person hWorks; 21.10.2009

Я думаю это может помочь

import os
import sys
import subprocess
import base64,xml.dom.minidom
from xml.dom.minidom import Node
f = open("file.xml",'r')
data = f.read()
i = 0
doc = xml.dom.minidom.parseString(data)
for topic in doc.getElementsByTagName('Topic'):
   title= doc.getElementsByTagName('Title')[i].firstChild.nodeValue
   print title
   i +=1

Вывод:

My Document
Overview
Basic Features
About This Software
Platforms Supported
person 0x3bfc    schedule 28.01.2014

Вы можете использовать следующий генератор для просмотра списка и получения заголовков с уровнями отступа:

def f(elem, level=-1):
    if elem.nodeName == "Title":
        yield elem.childNodes[0].nodeValue, level
    elif elem.nodeType == elem.ELEMENT_NODE:
        for child in elem.childNodes:
            for e, l in f(child, level + 1):
                yield e, l

Если вы протестируете это со своим файлом:

import xml.dom.minidom as minidom
doc = minidom.parse("test.xml")
list(f(doc))

вы получите список со следующими кортежами:

(u'My Document', 1), 
(u'Overview', 1), 
(u'Basic Features', 2), 
(u'About This Software', 2), 
(u'Platforms Supported', 3)

Конечно, это всего лишь основная идея, требующая точной настройки. Если вам просто нужны пробелы в начале, вы можете закодировать это прямо в генераторе, хотя с уровнем у вас будет больше гибкости. Вы также можете определить первый уровень автоматически (здесь просто плохая работа по инициализации уровня на -1 ...).

person RedGlyph    schedule 21.10.2009
comment
Именно то, что я пытался сделать весь день, прежде чем наткнулся на генераторы. Большое спасибо. - person hWorks; 22.10.2009

Рекурсивная функция:

import xml.dom.minidom

def traverseTree(document, depth=0):
  tag = document.tagName
  for child in document.childNodes:
    if child.nodeType == child.TEXT_NODE:
      if document.tagName == 'Title':
        print depth*'    ', child.data
    if child.nodeType == xml.dom.Node.ELEMENT_NODE:
      traverseTree(child, depth+1)

filename = 'sample.xml'
dom = xml.dom.minidom.parse(filename)
traverseTree(dom.documentElement)

Ваш xml:

<?xml version="1.0" encoding="UTF-8"?>
<DOCMAP>
    <Topic Target="ALL">
        <Title>My Document</Title>
    </Topic>
    <Topic Target="ALL">
        <Title>Overview</Title>
        <Topic Target="ALL">
            <Title>Basic Features</Title>
        </Topic>
        <Topic Target="ALL">
            <Title>About This Software</Title>
            <Topic Target="ALL">
                <Title>Platforms Supported</Title>
            </Topic>
        </Topic>
    </Topic>
</DOCMAP>

Ваш желаемый результат:

 $ python parse_sample.py 
      My Document
      Overview
          Basic Features
          About This Software
              Platforms Supported
person imesias    schedule 10.01.2013