lxml xpath/findall和upd

2024-09-27 22:34:24 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在尝试更新一个xml文档,将一个子元素添加到xpath标识的一组父元素中。在

为此,我通过xpath标识要更改的元素,然后对这些元素运行for循环。我将这段代码称为“xpath for block”

我可以:

  • 成功识别需要更新的元素
  • 在“xpath for block”中更新元素
  • 在“xpath for block”中更新根节点

我不能:

  • 一旦离开'xpath for block'(编辑除了循环中的最后一个元素之外)就保留更新

代码:

from lxml import etree as ET

def _modify_mods_content(mods_content):
    """Massage the MODS content before auto-populating page

    Args:
        mods_content (str): content of MODS datastream as a string

    Returns:
        updated_content (str): updated MODS content
    """
    nsmap = {'mods': NAMESPACES['mods']}

    mods_xml = ET.fromstring(mods_content)

    # Create empty given-name element
    given_name_tag = ET.QName(NAMESPACES['mods'], 'namePart')
    given_name = ET.Element(given_name_tag)
    given_name.attrib['type'] = 'given'


    # xpath to find every personal name that does not have a child 
    # given name element
    no_given_names_xpath = '//mods:name[@type="personal"][not(mods:namePart[@type="given"])]'


    for element in mods_xml.xpath(no_given_names_xpath, namespaces=nsmap):
        element.insert(0, given_name)

    new_string = ET.tostring(mods_xml)
    return new_string

如果我在xpath for block中输入pdb提示符,我可以看到元素正在更新,如果我在树上一直执行“getparent”,我可以看到根mods_xml文档似乎只在该实例中更新了。在

一旦我离开for循环,所有更新都不会持久。在

例如:

原始文件:

^{pr2}$

第一次在xpath for块中

<root>
  <mods:name type="personal">
    <mods:namePart type="given"></mods:namePart>
    <mods:namePart type="family">Smith</mods:namePart>
  </mods:name>
  <mods:name type="personal">
    <mods:namePart type="family">Jones</mods:namePart>
  </mods:name>
</root>

第二次在xpath for块中:

<root>
  <mods:name type="personal">
    <mods:namePart type="family">Smith</mods:namePart>
  </mods:name>
  <mods:name type="personal">
    <mods:namePart type="given"></mods:namePart>
    <mods:namePart type="family">Jones</mods:namePart>
  </mods:name>
</root>

编辑:以下示例最初没有插入元素。这些示例是从一个更长更复杂的文档中简化而来的。我一定错过了最后的更新是在离开块之后被持久化的。在

一旦我离开街区

<root>
  <mods:name type="personal">
    <mods:namePart type="family">Smith</mods:namePart>
  </mods:name>
  <mods:name type="personal">
    <mods:namePart type="given"></mods:namePart>
    <mods:namePart type="family">Jones</mods:namePart>
  </mods:name>
</root>

我意识到这可以用XSLT实现。我只想知道有没有更“Python”的方法。在

我实际上(有点)让它工作了,但这是一个可怕的黑客,a)我不想做,b)只有当所有元素都在同一深度(幸运的是,在这种情况下,它们恰好是这样)才有效:

    while mods_xml.xpath(no_given_names_xpath, namespaces=nsmap):
        elements = mods_xml.xpath(no_given_names_xpath, namespaces=nsmap)
        replacement_element = elements[0]
        replacement_element.insert(0, given_name)
        parent = elements[0].getparent()
        parent.replace(elements[0], replacement_element)
        new_xml_string = ET.tostring(parent, pretty_print=True)
        mods_xml = ET.fromstring(new_xml_string)

欢迎有任何想法或意见!在


Tags: namemods元素fortyperootxmlelement
1条回答
网友
1楼 · 发布于 2024-09-27 22:34:24

一位不在堆栈溢出上的同事给出了有用的答案。在

该问题与this issue here有关。我原以为我创建的给定的\u name元素是一个空白元素,我可以在循环中每次添加到父文档中(就像我在进行字符串连接一样)。在

事实上,lxml将元素视为一个不同的对象,并且只是在文件中移动它(上面的示例被简化了,所以我一定错过了插入的最后一个位置)。在

解决方案是每次使用一个小的私有函数来生成一个新元素。在

更正代码:

def _modify_mods_content(mods_content):
    """Massage the MODS content before auto-populating xml_ora

    Args:
        mods_content (str): content of MODS datastream as a string

    Returns:
        updated_content (str): updated MODS content
    """
    nsmap = {'mods': NAMESPACES['mods']}
    try:
        mods_xml = ET.fromstring(mods_content)

        def __create_empty_given_name_element():
            """Create empty given-name element

            Returns:
                given_name (ET.element): empty element for processing
            """
            given_name_tag = ET.QName(NAMESPACES['mods'], 'namePart')
            given_name = ET.Element(given_name_tag)
            given_name.attrib['type'] = 'given'
            given_name.text = ''
            given_name.tail = '\n      '
            return given_name

        # xpath to find every personal name that does not have a child given name element
        no_given_names_xpath = '//mods:name[@type="personal"][not(mods:namePart[@type="given"])]'

        for element in mods_xml.xpath(no_given_names_xpath, namespaces=nsmap):
            given_name_element = __create_empty_given_name_element()
            element.insert(0, given_name_element)

        return_text = ET.tostring(mods_xml)   
    except Exception as _:  # pylint: disable=W0703
        # Something went wrong, but try to let a human fix it
        # by returning original MODS
        return mods_content
    return return_text

相关问题 更多 >

    热门问题