重写XML以将多个选择节点包装成新节点

时间:2013-10-18 13:40:58

标签: python xml xslt xpath

我正在寻找更好的解决方案来执行以下操作。给定XML树,例如

<root>
  <x>
    <p class="a"> </p>
    <p class="b"> </p>  <!-- Not yet wrapped, so these two <p> nodes can be wrapped, like below. -->
    <p class="b"> </p>
    <p class="a"> </p>  <!-- Not yet wrapped, so these two <p> nodes can be wrapped. -->
    <p class="a"> </p>
  </x>
  <y>
    <p class="a"> </p>
    <wrap class="b">    <!-- The <p> nodes are wrapped in a <wrap> and the attribute has moved. -->
      <p> </p>
      <p> </p>
    </wrap>
    <p class="a"> </p>
  </y>
</root>

我想选择具有相同类属性的所有相邻<p>节点,并将它们包装在同一类的<wrap>元素中;但是,如果它们已经被包裹起来,请不要包装它们。

我目前的做法是选择所有可以包装的节点:

candidates = xml.xpath("//*[not(self::wrap)]/p[@class]")

然后挑选任何候选人并将所有有效的兄弟姐妹积累到一个列表中:

if len(candidates) == 0 :
    return
candidate = candidates[0] # Pick any one of the candidates.
siblings = get_siblings(candidate) # Gather all of the candidate's matching sibling elements into a list.

构建兄弟列表很简单:给定候选<p>元素,迭代匹配的所有getprevious()元素(即也是<p class="b">元素)直到找到第一个元素兄弟姐妹。然后将所有getnext()元素收集到一个列表中,生成一组完整的匹配元素。

获得该列表后,我创建一个新的<wrap>元素,复制class属性,然后将所有兄弟元素添加到新元素中。完成后,我添加了新元素(所有<p>兄弟姐妹作为其子代),原来的第一个兄弟姐妹曾经是:

parent = candidate.getparent() # Get parent element of the candidate.
index = parent.index(candidate) # Get the index of the candidate.

wrap = lxml.etree.Element("wrap") # Create a new <wrap> element...
wrap.attrib["class"] = candidate.attrib["class"] # ...and copy the class attribute.
for s in siblings : # Iterate over all sibling elements in the list, and
    wrap.append(s) # add the sibling to the new <wrap> element, and
    del s.attrib["class"] # remove the class attribute from the sibling (because it's in the <wrap> element.
parent.insert(index, wrap) # Once all siblings are moved, add the new <wrap> element where the siblings used to be.

问题

环顾四周似乎有更好的解决方案,而不是手动实现这样的重写,例如使用XSLT? (我之前从未使用过它,所以我不确定XSLT是否打算解决这些问题。)那么:什么是“正确”的方法呢?是否有更正式的基于XML的工具用于此类重写/转换,或者是一种手动实现,就像上面常用的方法一样?

1 个答案:

答案 0 :(得分:0)

你使用的是(c)ElementTree还是lxml?它们具有以下不同的行为。当一个节点从一个父节点“移动”到另一个父节点时,ElementTree会创建一个副本,而lxml会重新创建该节点。这种行为可能对你有用......

import xml.etree.ElementTree as et
from lxml import etree as lxet

e1 = et.Element('e1')
e2 = et.Element('e2')
e3 = et.SubElement(e1, 'e3')
e2.append(e3)

et.dump(e1)
<e1><e3 /></e1>
et.dump(e2)
<e2><e3 /></e2>

lxe1 = lxet.Element('e1')
lxe2 = lxet.Element('e2')
lxe3 = lxet.SubElement(lxe1, 'e3')
lxe2.append(lxe3)

lxet.dump(lxe1)
<e1/>
lxet.dump(lxe2)
<e2>
  <e3/>
</e2>