Запрос XPath с предикатами text потомков и потомков

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

<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)]

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

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

3 ответа

Решение

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

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

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

Переведено на простой английский: для любого текстового узла в документе, содержащего строку "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, должен получить тот же результат.

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

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

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

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

это помогает?

Другие вопросы по тегам