Условное удаление элемента из дерева документов XML - PullRequest
0 голосов
/ 12 ноября 2010

Моя задача - выполнить незначительный рефакторинг некоторых элементов дерева XML в python 3, а именно заменить следующую структуру:

<span class="nobr">
 <a href="http://www.google.com/">
  http://www.google.com/
  <sup>
   <img align="absmiddle" alt="" border="0" class="rendericon" height="7" src="http://jira.atlassian.com/icon.gif" width="7"/>
  </sup>
 </a>
</span>

На:

<span class="nobr">
 <a href="http://www.google.com/">
  http://www.google.com/
 </a>
</span>

Т.е. - удалить элемент supесли вся структура точно соответствует приведенной в первом примереМне нужно хранить XML-документ во время процесса, поэтому сопоставление с регулярным выражением не представляется возможным.

У меня уже есть код, который работает для моих целей:

doc = self.__refactor_links(doc)
...
def __refactor_links(self, node):
    """Recursively seeks for links to refactor them"""
    for span in node.childNodes:
        replace = False
        if isinstance(span, xml.dom.minidom.Element):
            if span.tagName == "span" and span.getAttribute("class") == "nobr":
                if span.childNodes.length == 1:
                    a = span.childNodes.item(0)
                    if isinstance(a, xml.dom.minidom.Element):
                        if a.tagName == "a" and a.getAttribute("href"):
                            if a.childNodes.length == 2:
                                aurl = a.childNodes.item(0)
                                if isinstance(aurl, xml.dom.minidom.Text):
                                    sup = a.childNodes.item(1)
                                    if isinstance(sup, xml.dom.minidom.Element):
                                        if sup.tagName == "sup":
                                            if sup.childNodes.length == 1:
                                                img = sup.childNodes.item(0)
                                                if isinstance(img, xml.dom.minidom.Element):
                                                    if img.tagName == "img" and img.getAttribute("class") == "rendericon":
                                                        replace = True
            else:
                self.__refactor_links(span)
        if replace:
            a.removeChild(sup)
    return node

Этот код не выполняетсявсе теги рекурсивно - если он соответствует чему-то похожему на структуру, которую ищет - даже если он потерпит неудачу, он не будет продолжать искать структуру внутри этих элементов, но в моем случае я не должен это делать (хотя этобыло бы неплохо иметь его, но стоимость добавления нескольких других: self .__ refactor_links (tag) убивает это в моих глазах).

Если какое-либо условие не выполняется, удаление не должно происходить.Есть ли более чистый способ определения набора условий, избегая огромного набора «если»?Некоторая пользовательская структура данных может использоваться для хранения условий, например ('sup', ('img', (...))), но я понятия не имею, как она должна обрабатываться.Если у вас есть предложения или примеры в Python - помогите.

Спасибо.

Ответы [ 4 ]

1 голос
/ 12 ноября 2010

Я плохо разбираюсь в xml, но вы не можете использовать поиск / поиск по узлам

>>> from xml.dom.minidom import parse, parseString
>>> dom = parseString(x)
>>> k = dom.getElementsByTagName('sup')
>>> for l in k:
...     p = l.parentNode
...     p.removeChild(l)
... 
<DOM Element: sup at 0x100587d40>
>>> 
>>> print dom.toxml()
<?xml version="1.0" ?><span class="nobr">
 <a href="http://www.google.com/">
  http://www.google.com/

 </a>
</span>
>>> 
1 голос
/ 12 ноября 2010

Это наиболее определенная задача для выражения XPath, в вашем случае, вероятно, в сочетании с lxml .

Вероятно, XPath - это нечто вроде:

//span[@class="nobr"]/a[@href]/sup[img/@class="rendericon"]

Сопоставьте свое дерево с этим выражением XPath и удалите все соответствующие элементы.Нет необходимости в бесконечном числе конструкций или рекурсии.

0 голосов
/ 12 ноября 2010

Легко выполнить, используя lxml и XSLT:

>>> from lxml import etree
>>> from StringIO import StringIO
>>> # create the stylesheet
>>> xslt = StringIO("""
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <!-- this is the standard identity transform -->
  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>
  <!-- this replaces the specific node you're looking to replace -->
  <xsl:template match="span[a[@href='http://www.google.com' and 
                    sup[img[
                      @align='absmiddle' and
                      @border='0' and
                      @class='rendericon' and
                      @height='7' and
                      @src='http://jira.atlassian.com/icon.gif' and
                      @width='7']]]]">
    <span class="nobr">
      <a href="http://www.google.com/">http://www.google.com/</a>
    </span>
  </xsl:template>
</xsl:stylesheet>""")
>>> # create a transform function from the XSLT stylesheet
>>> transform = etree.XSLT(etree.parse(xslt))
>>> # here's a sample source XML instance for testing
>>> source = StringIO("""
<test>
  <span class="nobr">
   <a href="http://www.google.com/">
    http://www.google.com/
    <sup>
     <img align="absmiddle" alt="" border="0" class="rendericon" height="7" src="http://jira.atlassian.com/icon.gif" width="7"/>
    </sup>
   </a>
  </span>
</test>""")
>>> # parse the source, transform it to an XSLT result tree, and print the result
>>> print etree.tostring(transform(etree.parse(source)))
<test>
  <span class="nobr"><a href="http://www.google.com/">http://www.google.com/</a></span>
</test>

Редактировать:

Я должен отметить, что ни один из ответов - ни мой, ни МэттХи, конечно, не опубликованный пример OP - делайте то, о чем просил OP, то есть заменять только элементы, структура которых точно соответствует

<span class="nobr">
  <a href="http://www.google.com/">
    http://www.google.com/
  <sup>
    <img align="absmiddle" alt="" border="0" class="rendericon" height="7" src="http://jira.atlassian.com/icon.gif" width="7"/>
  </sup>
  </a>
</span>

Например, все эти примеры заменятsup, если img имеет атрибут style, или если у sup есть другой дочерний элемент, кроме img.

Можно построить выражение XPath, которое будет гораздо более строгим в отношении соответствия.Например, вместо использования

span[a]

, который соответствует любому span хотя бы с одним a дочерним элементом, вы можете использовать

span[count(@*)=0 and count(*)=1 and a]

, который соответствует любому span, который имеетбез атрибута и точно один дочерний элемент, где этот дочерний элемент является a.Вы можете сойти с ума с этим в своем стремлении к точности, например:

span[count(@*) = 1 and
     @class='nobr' and
     count(*) = 1 and 
     a[count(@*) = 1 and
       @href='http://www.google.com' and 
       count(*) = 1 and
       sup[count(@*) = 0 and
           count(*) = 1 and 
           img[count(*) = 0 and
               count(@*) = 7 and
               @align='absmiddle' and
               @alt='' and
               @border='0' and
               @class='rendericon' and
               @height='7' and
               @src='http://jira.atlassian.com/icon.gif' and
               @width='7']]]]

, который на каждом шаге сопоставления гарантирует, что сопоставляемый элемент содержит только точно указанные атрибуты и элементы и не более.(И это все еще не подтверждает, что они не содержат текст, комментарии или инструкции по обработке - если вы действительно серьезно относитесь к точности, используйте count(node()) везде, где используется count(*).)

0 голосов
/ 12 ноября 2010

Вот быстрая вещь с lxml. Настоятельно рекомендуем xpath.

>>> from lxml import etree
>>> doc = etree.XML("""<span class="nobr">
...  <a href="http://www.google.com/">
...   http://www.google.com/
...   <sup>
...    <img align="absmiddle" alt="" border="0" class="rendericon" height="7" src="http://jira.atlassian.com/icon.gif" width="7"/>
...   </sup>
...  </a>
... </span>""")
>>> for a in doc.xpath('//span[@class="nobr"]/a[@href="http://www.google.com/"]'):
...     for sub in list(a):
...         a.remove(sub)
...
>>> print etree.tostring(doc,pretty_print=True)
<span class="nobr">
 <a href="http://www.google.com/">
  http://www.google.com/
  </a>
</span>
...