为了账号安全,请及时绑定邮箱和手机立即绑定

使用元素树删除xml节点的所有内容和子元素

使用元素树删除xml节点的所有内容和子元素

猛跑小猪 2022-12-20 14:03:48
我有一个 XML 文件,想删除具有给定属性=值的节点内的所有内容,但一直无法使元素树.remove()方法起作用。我得到一个list.remove(x): x not in list错误。如果我有一个包含多个段落和列表元素的 div,v1-9,deleted我希望能够删除整个 div 及其所有内容的属性。import xml.etree.ElementTree as ET#get target filetree = ET.parse('tested.htm')#pull into element treeroot = tree.getroot()#confirm outputprint(root)#define xlmns tagsMadCap = {'MadCap': 'http://www.madcapsoftware.com/Schemas/MadCap.xsd'}i=1j=6# specify state            state = "state.deleted-in-vers"            # specify version            vers = "version-number.v{}-{}".format(i,j)            # combine to get conditional string might need to double up b/c of order mattering here???            search = ".//*[@MadCap:conditions='{},{}']".format(vers,state)            #get matching elements            for elem in root.findall(search, MadCap):                print('---PARENT---')                print(elem)                print('attributes:', elem.attrib)                print('text:', elem.text)                elem.text = " "                elem.attrib = {}                for child in elem.iter():                    print('-child element-')                    print(child)                    elem.remove(child)            print('==========')为简单起见,我在上面省略了 i 和 j 上的循环。这是目标 xml 的片段,因此您可以看到如何使用这些属性。
查看完整描述

1 回答

?
慕神8447489

TA贡献1780条经验 获得超1个赞

我发现使用lxml更容易完成任务,因为更容易删除元素。


试试下面的代码:


from lxml import etree as et


def remove_element(el):

    parent = el.getparent()

    if el.tail.strip():

        prev = el.getprevious()

        if prev is not None:

            prev.tail = (prev.tail or '') + el.tail

        else:

            parent.text = (parent.text or '') + el.tail

    parent.remove(el)


# Read source XML

parser = et.XMLParser(remove_blank_text=True)

tree = et.parse('Input.xml', parser)

root = tree.getroot()

# Replace the below namespace with your proper one

ns = {'mc': 'http://dummy.com'}

# Processing

for it in root.findall('.//*[@mc:conditions]', ns):

    attr = it.attrib

    attrTxt = ', '.join([ f'{key}: {value}'

        for key, value in attr.items() ])

    print(f'Elem.: {et.QName(it).localname:6}: {attrTxt}')

    delFlag = False

    cond = attr.get('{http://dummy.com}conditions')

    if cond:

        dct = { k: v for k, v in (x.split('.')

            for x in cond.split(',')) }

        vn = dct.get('version-number')

        st = dct.get('state')

        if vn == 'v1-6' and st.startswith('deleted'):

            delFlag = True

        print(f"    {vn}, {st:15}  {'Delete' if delFlag else 'Keep'}")

        if delFlag:

            remove_element(it)

# Print the result

print(et.tostring(tree, method='xml',

    encoding='unicode', pretty_print=True))

当然,在目标版本中添加将此树保存到输出文件。


为了使用单个根元素正确格式化 XML,我将您的内容封装在:


<main xmlns:MadCap="http://dummy.com">

   ...

</main>

编辑

在我以前的解决方案中,我曾经it.getparent().remove(it)删除有问题的元素。但后来我发现了一个缺陷,如果源 XML 包含“混合内容”,即被删除元素之后的“尾部”文本也被删除(但它不应该),这个缺陷就会变得可见。

为了防止它,我添加了remove_element函数以删除元素本身并调用它而不是以前的it.getparent().remove(it)

评论中问题后的解释

attrTxt的来源是attr字典的内容(当前元素的属性)。这个片段实际上打印了这本没有大括号的字典。它仅用于跟踪,无处可寻。

另一方面,dct扮演着更重要的角色。它的来源是cond,包含(当前元素的)条件属性的内容,例如state.new-in-vers,version-number.v1-6

这段代码:

  • 在逗号上拆分内容。

  • 将上述每个部分拆分为一个点。

  • 从这些对创建字典。

然后vn收到版本号 ( v1-6 ) 和st - 状态 ( new-in-vers )。这是嵌入这里的重要情报。由于这两个片段可能以不同的顺序出现,因此您无法创建任何匹配所有可能情况的XPath表达式。但是如果你检查上面的变量,这个元素是否应该被删除就变得很明显了。


查看完整回答
反对 回复 2022-12-20
  • 1 回答
  • 0 关注
  • 80 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信