【问题标题】:python xpath parsing of xml avoiding <lb/>python xpath解析xml避免<lb/>
【发布时间】:2021-05-25 09:34:27
【问题描述】:

我正在使用 xpath 来解析一个 xml 文件

from lxml import etree

example='''<div n="0001" type="car" xml:id="_3a327f0002">
                <p xml:id="_3a327f0003">
                1. A car is
                    <p xml:id="_3a327f0004"> - big, yellow and red;</p>
                    <p xml:id="_3a327f0005"> - has a big motor;</p>
                    <p xml:id="_3a327f0006"> - and also has <lb/>
                      big seats.
                    </p>
                </p>
                </div>'''

我想用下面的方式序列化上面的XML文件:

{"_3a327f0003": "1. A car is",
 "_3a327f0004":"- big, yellow and red;"
 "_3a327f0005":"- has a big motor;"
"_3a327f0006":"- and also has big seats"

基本上提取文本并构建一个字典,其中每个文本都属于他的xml:id。我的代码如下:

parser = etree.XMLParser(resolve_entities=False, strip_cdata=False, recover=True, ns_clean=True)

XML_tree = etree.fromstring(example.encode() , parser=parser)
all_paras = XML_tree.xpath('.//p[@xml:id]')

list_of_paragraphs = []
for para in all_paras:
    mydict = {}
    mydict['text'] = para.text
    for att in para.attrib:
        mykey=att
        if 'id' in mykey:
            mykey='xmlid'
        mydict[mykey] = para.attrib[att]
    list_of_paragraphs.append(mydict)

PDM_XML_serializer(example)

它的工作原理是,如果我有一个像这样的节点:

<p xml:id="_3a327f0006"> - and also has <lb/>
                        big seats.
                      </p>

它不会从

中提取部分

我应该如何修改:

XML_tree.xpath('.//p[@xml:id]')

为了从

获取所有文本?

编辑: 可以使用 para.itertext() 但第一个节点也会返回其他节点的所有文本。

【问题讨论】:

  • 尝试使用para.text_content()而不是para.text
  • AttributeError: 'lxml.etree._Element' 对象没有属性 'text_content'
  • 你试过text_content吗?
  • 是的......但没有运气:-(
  • 实际上在执行 print(dir(para)) 时,我得到了不包括 text_content 的方法列表

标签: python xml xpath lxml


【解决方案1】:

使用xml.etree.ElementTree

import xml.etree.ElementTree as ET

xml = '''<div n="0001" type="car" xml:id="_3a327f0002">
                <p xml:id="_3a327f0003">
                1. A car is
                    <p xml:id="_3a327f0004"> - big, yellow and red;</p>
                    <p xml:id="_3a327f0005"> - has a big motor;</p>
                    <p xml:id="_3a327f0006"> - and also has <lb/>
                      big seats.
                    </p>
                </p>
                </div>'''


def _get_element_txt(element):
    txt = element.text
    children = list(element)
    if children:
        txt += children[0].tail.strip()
    return txt


root = ET.fromstring(xml)
data = {p.attrib['{http://www.w3.org/XML/1998/namespace}id']: _get_element_txt(p)
        for p in root.findall('.//p/p')}
for k, v in data.items():
    print(f'{k} --> {v}')

输出

_3a327f0004 -->  - big, yellow and red;
_3a327f0005 -->  - has a big motor;
_3a327f0006 -->  - and also has big seats.

【讨论】:

    【解决方案2】:

    使用lxml.etree 解析列表/字典理解中all_paras 中的所有元素。由于您的 XML 使用特殊的 xml 前缀并且 lxml 尚不支持解析属性中的命名空间前缀(请参阅 @mzjn 的答案 here),因此下面使用 next + iter 的解决方法来检索属性值。

    此外,为了检索节点之间的所有文本值,xpath("text()")str.strip.join 一起使用以清理空格和换行符并连接在一起。

    from lxml import etree
    
    example='''<div n="0001" type="car" xml:id="_3a327f0002">
                    <p xml:id="_3a327f0003">
                    1. A car is
                        <p xml:id="_3a327f0004"> - big, yellow and red;</p>
                        <p xml:id="_3a327f0005"> - has a big motor;</p>
                        <p xml:id="_3a327f0006"> - and also has <lb/>
                          big seats.
                        </p>
                    </p>
                    </div>'''
                    
    XML_tree = etree.fromstring(example)
    all_paras = XML_tree.xpath('.//p[@xml:id]')
    
    output = {
        next(iter(t.attrib.values())):" ".join(i.strip() 
            for i in t.xpath("text()")).strip()
        for t in all_paras
    }
    
    output
    # {
    #  '_3a327f0003': '1. A car is', 
    #  '_3a327f0004': '- big, yellow and red;',
    #  '_3a327f0005': '- has a big motor;',
    #  '_3a327f0006': '- and also has big seats.'
    # }
    

    【讨论】:

    • 嗨@Parfait,你能重新看一下吗,我犯了一个错误,我忘记了我想要的输出中缺少一行,因为我还想要字典中的键/值对与键“_3a327f0003”,谢谢
    • all_paras中查看遍历所有&lt;p&gt;的更新。
    【解决方案3】:

    您可以使用 lxml itertext() 来获取 p 元素的文本内容:

    mydict['text'] = ''.join(para.itertext())
    

    请参阅this question 以获得更通用的解决方案。

    【讨论】:

    • 这是一个半解决方案,因为当嵌套 p 标签时,最外层的标签也会包含内层标签的文本。
    【解决方案4】:

    这会根据您的示例修改 xpath 以排除“A car is”文本。它还使用 xpath 函数 stringnormalize-spacepara 节点评估为字符串并加入其文本节点,并清理文本以匹配您的示例。

    from lxml import etree
    
    example='''<div n="0001" type="car" xml:id="_3a327f0002">
                    <p xml:id="_3a327f0003">
                    1. A car is
                        <p xml:id="_3a327f0004"> - big, yellow and red;</p>
                        <p xml:id="_3a327f0005"> - has a big motor;</p>
                        <p xml:id="_3a327f0006"> - and also has <lb/>
                          big seats.
                        </p>
                    </p>
                    </div>'''
    
    parser = etree.XMLParser(resolve_entities=False, strip_cdata=False, recover=True, ns_clean=True)
    
    XML_tree = etree.fromstring(example.encode() , parser=parser)
    all_paras = XML_tree.xpath('./p/p[@xml:id]')
    
    list_of_paragraphs = []
    for para in all_paras:
        mydict = {}
        mydict['text'] = para.xpath('normalize-space(string(.))')
        for att in para.attrib:
            mykey=att
            if 'id' in mykey:
                mykey='xmlid'
            mydict[mykey] = para.attrib[att]
        list_of_paragraphs.append(mydict)
    
    PDM_XML_serializer(example)
    

    【讨论】:

      【解决方案5】:

      如果这些标签对你来说只是噪音,你可以在阅读 xml 之前删除它们

      XML_tree = etree.fromstring(example.replace('<lb/>', '').encode() , parser=parser)
      

      【讨论】:

        猜你喜欢
        • 2011-11-06
        • 1970-01-01
        • 2015-12-15
        • 2014-05-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-01
        • 1970-01-01
        相关资源
        最近更新 更多