【问题标题】:Get maximal level of embedding of element in recursively nested XML在递归嵌套的 XML 中获得最高级别的元素嵌入
【发布时间】:2019-10-10 15:17:05
【问题描述】:

对于任意递归嵌套的 XML 中的每个元素,我需要找到它的最大嵌入级别。

例如对于这个 XML

<chorus>
    <l>Alright now lose it <ah>aah <i>aah <ah>a<ah>a</ah>h</ah> aah</i> aah</ah></l>
    <l>Just lose it aah aah aah aah aah</l>
    <l>Go crazy aah aah aah aah aah</l>
    <l>Oh baby <ah>aah aah</ah>, oh baby baby <ah>aah aah</ah></l>
</chorus>

输出应如下所示:{"chorus": 0, "l": 0, "ah": 2, "i": 0}

不幸的是,解决方案仅限于使用xml.etree.ElementTree

我尝试了几个小时不同的方法,但我无法理解它。

【问题讨论】:

  • @mzjn &lt;chorus&gt;&lt;l&gt; 本身没有嵌入,但 &lt;ah&gt; 本身嵌入了两次。这就是我所说的“最大嵌入水平”。抱歉,如果不清楚。

标签: python python-3.x xml xml-parsing elementtree


【解决方案1】:

您可以使用this example from the docs 的修改版本:

尝试使用键的元素名称(标签)将maxDepthdepth 更改为字典...

Python

from xml.etree.ElementTree import XMLParser


class MaxDepth:  # The target object of the parser
    maxDepth = {}
    depth = {}

    def start(self, tag, attrib):  # Called for each opening tag.
        try:
            self.depth[tag] += 1
        except KeyError:
            self.depth[tag] = 0
            self.maxDepth[tag] = 0
        if self.depth[tag] > self.maxDepth[tag]:
            self.maxDepth[tag] = self.depth[tag]

    def end(self, tag):  # Called for each closing tag.
        self.depth[tag] -= 1

    def data(self, data):
        pass  # We do not need to do anything with data.

    def close(self):  # Called when all data has been parsed.
        return self.maxDepth


target = MaxDepth()
parser = XMLParser(target=target)
exampleXml = """
<chorus>
    <l>Alright now lose it <ah>aah <i>aah <ah>a<ah>a</ah>h</ah> aah</i> aah</ah></l>
    <l>Just lose it aah aah aah aah aah</l>
    <l>Go crazy aah aah aah aah aah</l>
    <l>Oh baby <ah>aah aah</ah>, oh baby baby <ah>aah aah</ah></l>
</chorus>"""
parser.feed(exampleXml)
print(parser.close())

输出

{'chorus': 0, 'l': 0, 'ah': 2, 'i': 0}

编辑的 Python(其中 chorus 已经是 ElementTree.Element 对象)

import xml.etree.ElementTree as ET
from xml.etree.ElementTree import XMLParser


class MaxDepth:  # The target object of the parser
    maxDepth = {}
    depth = {}

    def start(self, tag, attrib):  # Called for each opening tag.
        try:
            self.depth[tag] += 1
        except KeyError:
            self.depth[tag] = 0
            self.maxDepth[tag] = 0
        if self.depth[tag] > self.maxDepth[tag]:
            self.maxDepth[tag] = self.depth[tag]

    def end(self, tag):  # Called for each closing tag.
        self.depth[tag] -= 1

    def data(self, data):
        pass  # We do not need to do anything with data.

    def close(self):  # Called when all data has been parsed.
        return self.maxDepth


exampleXml = """
<chorus>
    <l>Alright now lose it <ah>aah <i>aah <ah>a<ah>a</ah>h</ah> aah</i> aah</ah></l>
    <l>Just lose it aah aah aah aah aah</l>
    <l>Go crazy aah aah aah aah aah</l>
    <l>Oh baby <ah>aah aah</ah>, oh baby baby <ah>aah aah</ah></l>
</chorus>"""

chorus_element = ET.fromstring(exampleXml)

target = MaxDepth()
parser = XMLParser(target=target)
parser.feed(ET.tostring(chorus_element))
print(parser.close())

【讨论】:

  • 哇,这完美!谢谢!但是我不确定,如果&lt;chorus&gt; 已经是解析的xml.etree.ElementTree.Element 对象,我该如何整合它?
  • @DanielMacDaniel - 由于feed() 需要编码数据,您应该能够使用tostring() 序列化ElementTree.Element (&lt;chorus&gt;) 并在feed() 中使用它。请查看我的更新。
【解决方案2】:

我认为这是函数 str.find(string, begining, end) 的一个很好的用例:https://www.tutorialspoint.com/python/string_find.htm

所以如果我对 之间的每个元素理解正确,我们将查看该元素的最大嵌入深度,所以:

  • 我们寻找元素,我们保存找到它的位置
  • 然后我们寻找结束匹配元素,保存位置
  • 再次查找该元素,但从我们第一次找到它的位置开始
  • 如果元素第二次出现的位置在结束元素第一次出现之前找到,我们将深度加 1,我们将开始寻找第二次出现的位置
  • 如果元素第二次出现的位置是在闭合元素第一次出现之后找到的,则它们没有被嵌入,所以我们不更新深度,我们将在闭合元素的位置之后寻找下一个结束元素
  • 重复直到在子字符串中找不到元素为止
  • 重复每个不同的元素

我认为它是完全可优化的,但经过几次测试后它可以工作:

import re

string = ("<chorus>"
          "<l>Alright now lose it <ah>aah <i>aah <ah>a<ah>a</ah>h</ah> aah</i> aah</ah></l>"
          "<l>Just lose it aah aah aah aah aah</l>"
          "<l>Go crazy aah aah aah aah aah</l>"
          "<l>Oh baby <ah>aah aah</ah>, oh baby baby <ah>aah aah</ah></l>"
          "</chorus>")

# Start looking for the different nodes
regex = re.compile('\<[a-z]+\>')
# create a set of the nodes and closing nodes so we can iterate trough them
nodes = set(regex.findall(string))
closing_nodes = [node.replace('<', '</') for node in nodes]


depth = {node: '0' for node in nodes}

for node, closing_node in zip(nodes, closing_nodes):
    pos_node = string.find(node) + len(node)
    pos_closing_node = 0
    node_depth = 0
    max_node_depth = 0
    # we keep looking until we do not find our node (then str.find(node) returns -1)
    while pos_node >= 0:
        pos_closing_node = string.find(closing_node, pos_closing_node)
        pos_node = string.find(node, pos_node)
        # if we didnt find the node at all of if we found an occurence of a closing node before the node, we reduce depth by 1
        # and we will be looking for the next closing node next time, so we add the lengh of the closing node to the starting position of our search
        if pos_closing_node < pos_node or pos_node == -1:
            node_depth -= 1
            pos_closing_node = pos_closing_node + len(closing_node)
        else:
            node_depth += 1
            pos_node = pos_node + len(node)
        # we want the max depth so we take the max of the depth values
        max_node_depth = max(max_node_depth, node_depth)
    depth[node] = max_node_depth

print(depth)

【讨论】:

  • 感谢您的工作和建议,但不幸的是,在我的情况下,str.find 方法不是很合适。我的程序使用 xml.etree.ElementTree 解析大型 XML 文件,并找到每个元素的最大嵌入深度只是一个副产品。例如Beautifulsoup 有一个很好的方法find_parents(),它可以简化很多,但是因为我将这个函数添加到更大的代码库中,所以我坚持使用xml.etree.ElementTree,就像我写的那样。
猜你喜欢
  • 1970-01-01
  • 2013-03-24
  • 2013-06-26
  • 2020-09-07
  • 2013-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-12
相关资源
最近更新 更多