【问题标题】:Parse hierarchical XML tags解析分层 XML 标记
【发布时间】:2016-08-24 01:10:23
【问题描述】:

需要从 XML 中解析分层标签并在所需的输出中获取标签的值

输入

<doc>
<pid id="231">
    <label key="">Electronics</label>
        <desc/>
        <cid id="122">
        <label key="">TV</label>
        </cid>
        <desc/>
        <cid id="123">
        <label key="">Computers</label>
        <cid id="12433">
            <label key="">Lenovo</label>
            </cid>
            <desc/>
            <cid id="12434">
            <label key="">IBM</label>
            <desc/>
            </cid>
            <cid id="12435">
            <label key="">Mac</label>
            </cid>
            <desc/>
    </cid>
</pid>
<pid id="7764">
    <label key="">Music</label>
    <desc/>
        <cid id="1224">
        <label key="">Play</label>
        <desc/>
            <cid id="341">
            <label key="">PQR</label>
            </cid>
            <desc/>
        </cid>
        <cid id="221">
        <label key="">iTunes</label>
            <cid id="341">
            <label key="">XYZ</label>
            </cid>
            <desc/>
            <cid id="515">
            <label key="">ABC</label>
            </cid>
            <desc/>
        </cid>
</pid>
</doc>

输出

Electornics/
Electornics/TV
Electornics/Computers/Lenovo
Electornics/Computers/IBM
Electornics/Computers/Mac
Music/
Music/Play/PQR
Music/iTunes/XYZ
Music/iTunes/ABC

我尝试过的(在 Python 中

import xml.etree.ElementTree as ET
import os
import sys
import string

def perf_func(elem, func, level=0):
    func(elem,level)
    for child in elem.getchildren():
        perf_func(child, func, level+1)

def print_level(elem,level):
    print '-'*level+elem.tag

root = ET.parse('Products.xml')
perf_func(root.getroot(), print_level)

# Added find logic
root = tree.getroot()

for n in root.findall('doc')
  l = n.find('label').text
  print l

使用上面的代码,我可以获取节点及其级别(只是标签而不是它们的值)。也是所有标签的第一级。 需要一些关于如何继续以输出中提到的格式获取分层结构的建议(Perl/Python)。

【问题讨论】:

  • 看看etree find & findall 函数,它需要一个xpath表达式
  • 添加了查找逻辑(编辑了问题 - 我尝试了什么)...需要一些关于如何以输出中提到的格式获取树结构的建议

标签: python xml xml-parsing ipython


【解决方案1】:

我们将使用 3 块:按照它们出现的顺序找到所有元素,获取每个元素的深度,根据深度和顺序构建面包屑。

from lxml import etree
xml = etree.fromstring(xml_str)
elems = xml.xpath(r'//label')  #xpath expression to find all '<label ...> elements

# counts the number of parents to the root element
def get_depth(element):
    depth = 0
    parent = element.getparent()
    while parent is not None:
        depth += 1
        parent = parent.getparent()
    return depth

# build up the bread crumbs by tracking the depth
# when a new element is entered, it replaces the value in the list
# at that level and drops all values to the right
def reduce_by_depth(element_list):
    crumbs = []
    depth = 0
    elem_crumb = ['']*10
    for elem in element_list:
        depth = get_depth(elem)
        elem_crumb[depth] = elem.text
        elem_crumb[depth+1:] = ['']*(10-depth-1)
        # join all the non-empty string to get the breadcrumb
        crumbs.append('/'.join([e for e in elem_crumb if e]))
    return crumbs

reduce_by_depth(elems)

# output:
['Electronics',
 'Electronics/TV',
 'Electronics/Computers',
 'Electronics/Computers/Lenovo',
 'Electronics/Computers/IBM',
 'Electronics/Computers/Mac',
 'Music',
 'Music/Play',
 'Music/Play/PQR',
 'Music/iTunes',
 'Music/iTunes/XYZ',
 'Music/iTunes/ABC']

【讨论】:

    【解决方案2】:

    问题也可以通过using a custom iterator, similar to this answer解决

    代码必须跟踪何时添加新路径段(并非每个层次结构级别都可能有标签),以便在我们返回时可以在正确的时间删除它们。我通过存储深度和标签的元组来做到这一点。

    我还假设您对每个标签标签的父标签感兴趣,这就是为什么我检查标签元素然后返回其父标签的原因。

    如果标签不是第一个元素,代码将起作用。如果一个标签有多个标签作为直接子级,它将返回相同的父级两次。

    from lxml import etree
    
    def label_iter(element):
        path = []
        stack = []
        stack.append(iter([element]))
            while stack:
                e = next(stack[-1], None)
                if e == None:
                    stack.pop()
                    if(len(path) > 0 and len(stack) < path[-1][0]):
                        path.pop()
                else:
                    stack.append(iter(e))
                    label_tag = e.find('label') 
                    if label_tag is not None:
                        path.append((len(stack),label_tag.text))
                    if e.tag == 'label':
                        yield (e.getparent(), "/".join([label for debth, label in path]))
    
    tree = etree.fromstring(xml_str)
    root = tree.getroot()
    for tag, path in label_iter(root):
        print(path)
    

    代码有效,但我认为它可以以更少的重复和更简洁的方式完成。我不喜欢使用路径和堆栈数组进行双重簿记,并且两次查找标签似乎也不优雅。所以请随时改进它!

    【讨论】:

      【解决方案3】:

      另一个可能的解决方案是使用递归。它感觉有点像过去的黑暗时代的工具,但它允许使用调用堆栈来跟踪深度,而不必手动执行。

      from lxml import etree
      MAX_DEPTH = 20
      
      def recursive_parse(element, path = [], depth = 0):
          if depth > MAX_DEPTH:
              return
          label_tag = element.find('label')
          if label_tag is not None: #found new path segment
              path.append(label_tag.text)
              print('/'.join(path))
          for child in element.getchildren():
              recursive_parse(child, path, depth+1)
      
      tree = etree.fromstring(xml_str)
      root = tree.getroot()
      recursive_parse(root)
      

      除了打印路径之外,还可以将其存储在字典中,以便通过其路径检索元素:

      dict paths = {}
      ...
             if label_tag is not None: #found new path segment
                 path.append(label_tag.text)
                 paths['/'.join(path)] = element
      

      对我来说,这个解决方案似乎不太优雅,但它更短,可能更容易理解。

      【讨论】:

        猜你喜欢
        • 2011-12-08
        • 2014-05-22
        • 1970-01-01
        • 2014-08-18
        • 2013-11-25
        • 2010-11-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多