XPath - Поиск дочерних элементов, которые а) соответствуют фильтру и б) не имеют определенного предка - PullRequest
1 голос
/ 18 декабря 2009

Я пытаюсь собрать вместе выражение XPath, которое даст мне все дочерние элементы узла, которые соответствуют фильтру (например, [содержит (@class, "интересно))], но которые не имеют определенного предка например, [содержит (@class, "frame")]. Вероятно, лучше всего объяснить на примере:

    <div class="frame">
        <p class="interesting">alice</p>
        <p class="interesting">bob</p>
        <p class="interesting">carol>/p>

        <div> 
            <div>
                <h3 class="interesting">david</h3>
            </div>
        </div>

        <div class="frame">
            <p class="interesting">drevil</p>
        </div>
    </div>

Так что в этом примере я хочу иметь возможность сопоставить все "интересные" элементы, которые являются потомками первого элемента div с class = "frame". Но я не хочу, чтобы "интересные" элементы находились под вложенным элементом "frame".

В идеале у меня было бы одно выражение XPath, которое давало бы мне элементы с содержимым alice, bob, carol и david. Но не древил.

Это похоже на то, что наличие вложенной рамки исключает эту ветвь дерева из поиска.

Есть идеи? Все отзывы очень ценятся.


В ответ Роберту у меня есть этот код Python (хотя я сделаю это на стороне браузера):

from lxml import etree

from StringIO import StringIO

testxml = """
<div>
    <div class="frame">
        <p class="interesting">alice</p>
        <p class="interesting">bob</p>
        <p class="interesting">carol</p>

        <div> 
            <div>
                <h3 class="interesting">david</h3>
            </div>
        </div>

        <div class="frame">
            <p class="interesting">drevil</p>
        </div>
    </div>    
</div>
"""

xsl = """
<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="/">
        <output>
           <xsl:apply-templates select="//div[@class='frame'][1]/*"/>
        </output>
    </xsl:template>

    <xsl:template match="*">
       <xsl:apply-templates select="*"/>
    </xsl:template>

    <xsl:template match="*[@class='frame']"/>

    <xsl:template match="*[@class='interesting']">
       <xsl:copy-of select="."/>
    </xsl:template>

</xsl:stylesheet>
"""


def test_xsl():
    xslt_doc = etree.parse(StringIO(xsl))
    transform = etree.XSLT(xslt_doc)
    doc = etree.parse(StringIO(testxml))
    result = transform(doc)
    print result

if __name__=="__main__":
    test_xsl()

Это дает следующий результат:

<?xml version="1.0"?>
<output>
    <p class="interesting">alice</p>
    <p class="interesting">bob</p>
    <p class="interesting">carol</p>
    <h3 class="interesting">david</h3>
    <p class="interesting">drevil</p>
</output>

Как вы видите, древил скрывается.

Обратите внимание, что Томалак прав в том, что 2-е совпадение при * не имеет никакого эффекта (кроме удаления пробелов из вывода, что немного странно!).

Это просто косяк, хотя я, возможно, не смогу воспользоваться XSLT-подходом. Весь смысл запроса XPath в первую очередь заключался в получении ссылок на узлы в исходном HTML-документе. Если я выполню преобразование, узлы, содержащиеся в результирующем документе new , будут копиями, а не оригинальными, которые я ищу, и, следовательно, бесполезны!

Возможно, это самый глупый вопрос, но есть ли способ сохранить ссылки из узлов в преобразованном документе на узлы в оригинале?

Спасибо Томалак, Роберт и Михал за вашу помощь. Я думаю, мне просто нужно купить книгу на XSLT ...

Ответы [ 2 ]

2 голосов
/ 18 декабря 2009

Вы можете использовать селектор, ограничивающий элементы div [@ class = "frame"] предка 1

//div[@class="frame"][1]//*[@class="interesting" and count(ancestor::div[@class="frame"])=1]

это сработало:

>>> import lxml.html
>>> data = """
        <div class="frame">
            <p class="interesting">alice</p>
            <p class="interesting">bob</p>
            <p class="interesting">carol</p>

            <div> 
                <div>
                    <h3 class="interesting">david</h3>
                </div>
            </div>

            <div class="frame">
                <p class="interesting">drevil</p>
            </div>
        </div>
    """
>>> tree = lxml.html.fromstring(data)
>>> tree.xpath('//div[@class="frame"][1]//*[@class="interesting" and count(ancestor::div[@class="frame"])=1]/text()')
['alice', 'bob', 'carol', 'david']
0 голосов
/ 18 декабря 2009

Ответ mykhal - это, вероятно, лучшее, что вы можете сделать в XPath, по крайней мере, так как вы определили проблему.

Проблема в том, что он может быть очень неэффективным при использовании в больших документах со многими потенциально интересными элементами. Для каждого потенциально интересного элемента, который он находит, он должен исследовать каждый узел на своей оси предка.

В XSLT вы можете реализовать серию шаблонов, которые находят только те элементы, которые вы ищете, и которые не только посещают каждый элемент только один раз, но и не посещают элементы, которые им не нужны:

<xsl:template match="/">
    <output>
       <xsl:apply-templates select="/descendant::*[@class='frame'][1]/*"/>
    </output>
</xsl:template>

<xsl:template match="*[@class='frame']"/>

<xsl:template match="*[@class='interesting']">
   <xsl:copy-of select="."/>
</xsl:template>

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

Первый шаблон находит интересующий вас элемент-предок и применяет шаблоны к его дочерним элементам.

Второй шаблон говорит, в основном: «Если вы повторяете элементы и ударяете элемент с атрибутом класса« frame », не проверяйте его потомков». Это не позволяет преобразованию даже исследовать неинтересный элемент.

И, наконец, последний шаблон определяет, что делать, когда вы нажимаете интересный элемент - в этом случае он полностью копирует его в вывод.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...