Запрос XPath с предикатами-потомками и потомками()

Я хотел бы построить запрос XPath, который вернет элемент "div" или "table", если у него есть потомок, содержащий текст "abc". Одно из предостережений состоит в том, что у него не может быть потомков div или таблиц.

<div>
  <table>
    <form>
      <div>
        <span>
          <p>abcdefg</p>
        </span>
      </div>
      <table>
        <span>
          <p>123456</p>
        </span>
      </table>
    </form>
  </table>
</div>

Таким образом, единственным правильным результатом этого запроса будет:

/div/table/form/div 

Моя лучшая попытка выглядит примерно так:

//div[contains(//text(), "abc") and not(descendant::div or descendant::table)] | //table[contains(//text(), "abc") and not(descendant::div or descendant::table)]

но не возвращает правильный результат.

Спасибо за вашу помощь.

Ответы

Ответ 1

Что-то другое::)

//text()[contains(.,'abc')]/ancestor::*[self::div or self::table][1]

Кажется намного короче других решений, не так ли?:)

Переведен на простой английский. Для любого текста node в документе, который содержит строку "abc", выберите своего первого предка, который является либо div, либо table.

Это более эффективно, так как требуется только одно полное сканирование дерева документа (а не любое другое), а обход ancestor::* очень дешев по сравнению с descendent:: (дерево).

Чтобы убедиться, что это решение действительно работает:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="/">
  <xsl:copy-of select=
  "//text()[contains(.,'abc')]/ancestor::*[self::div or self::table][1] "/>
 </xsl:template>
</xsl:stylesheet>

, когда это преобразование выполняется на предоставленном XML-документе:

<div>
  <table>
    <form>
      <div>
        <span>
          <p>abcdefg</p>
        </span>
      </div>
      <table>
        <span>
          <p>123456</p>
        </span>
      </table>
    </form>
  </table>
</div>

требуется, правильный результат получается:

<div>
   <span>
      <p>abcdefg</p>
   </span>
</div>

Примечание. Нет необходимости использовать XSLT - любой хост XPath 1.0, такой как DOM, должен получить тот же результат.

Ответ 2

вы можете попробовать:

//div[
  descendant::text()[contains(., "abc")] 
  and not(descendant::div or descendant::table)
] | 
//table[
  descendant::text()[contains(., "abc")] 
  and not(descendant::div or descendant::table)
]

Помогает ли это?

Ответ 3

//*[self::div|self::table] 
   [descendant::text()[contains(.,"abc")]]  
   [not(descendant::div|descendant::table)]

Проблема с contains(//text(), "abc") заключается в том, что функции, литые node, устанавливают первый node.