我正在尝试更新xml文档,以将子元素添加到由xpath标识的一组父元素中。
为此,我通过xpath确定要更改的元素,然后对这些元素运行for循环。我将这段代码称为'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
如果我把一个pdb提示符放到xpath for block中,我可以看到该元素正在更新,如果我在树上一直做'getparent',我可以看到root mods_xml文件似乎是仅为该实例更新 。
一旦我离开for循环,没有任何更新仍然存在。
例如:
原始档案:
<root>
<mods:name type="personal">
<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 block
<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 block中:
<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完成。我只是想知道是否有一种更'pythonic'的方式。
我实际上(有点)使它成功,但它是如此可怕的黑客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)
非常欢迎任何想法或评论!
答案 0 :(得分:0)
一位不在Stack Overflow上的同事帮助回答了答案。
该问题与this issue here有关。我原以为我创建的given_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