XPath для выбора элементов между элементами с разными родителями - PullRequest
2 голосов
/ 19 октября 2011

С учетом XML-документа, подобного следующему:

<r>
  <a/><b/><c/>
  <d>
    <d1/>
    <d2>
      <d2a/>
      <d2b/>
      <d2c/>
    </d2>
  </d>
  <e/>
</r>

И с учетом критериев "Начните с b, остановитесь на d2b" Существует ли выражение XPath, которое можно выбрать либо:

В идеале :

<c/><d><d1/><d2><d2a/></d2></d>

Разумно :

<c/>

Я знаю, что с критериями "начать с«а» и заканчивается на «е» «Я могу использовать выражение //*[preceding-sibling::a][following-sibling::e];Мне интересно, есть ли способ сделать какое-то странное пересечение осей предка и предшествующих братьев и сестер, чтобы найти общего предка, когда начальный и конечный элементы не гарантированно имеют одного и того же родителя.

Ответы [ 2 ]

4 голосов
/ 19 октября 2011

XPath (как 1.0, так и 2.0) - это язык запросов для документов XML. Как таковой он не может изменять узлы и структуру любого XML-документа .

Требуемый результат можно получить с помощью преобразования XSLT (I. XSLT 1.0, используемый ниже):

<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:param name="pStart" select="/*/b"/>
 <xsl:param name="pEnd" select="/*/d/d2/d2b"/>

  <xsl:variable name="vFollowingStart" select=
  "$pStart/following::* | $pStart/descendant::*"/>

 <xsl:variable name="vPrecedingEnd" select=
  "$pEnd/preceding::* | $pEnd/ancestor::*"/>

 <xsl:template match="node()|@*" name="identity">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="*">
  <xsl:choose>
      <xsl:when test=
      "count(.|$vFollowingStart) = count($vFollowingStart)
      and
       count(.|$vPrecedingEnd) = count($vPrecedingEnd)
      ">
       <xsl:call-template name="identity"/>
      </xsl:when>
      <xsl:otherwise>
       <xsl:apply-templates/>
      </xsl:otherwise>
  </xsl:choose>
 </xsl:template>
</xsl:stylesheet>

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

<r>
  <a/><b/><c/>
  <d>
    <d1/>
    <d2>
      <d2a/>
      <d2b/>
      <d2c/>
    </d2>
  </d>
  <e/>
</r>

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

<c/>
<d>
   <d1/>
   <d2>
      <d2a/>
   </d2>
</d>

Объяснение

  1. Правило идентификации копирует каждый соответствующий узел "как есть".

  2. Существует один переопределяющий шаблон, соответствующий любому элементу.

  3. Внутри этого шаблона выполняются два теста: принадлежит ли текущий узел к набору всех элементов «после начала», и , принадлежит ли текущий узел к набору всех элементов, предшествующих конец". Если это так, текущий узел передается в шаблон идентификации (копируется), в противном случае он игнорируется (удаляется).


II. Решение XSLT 2.0

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

 <xsl:param name="pStart" select="/*/b"/>
 <xsl:param name="pEnd" select="/*/d/d2/d2b"/>

  <xsl:variable name="vFollowingStart" select=
  "$pStart/following::* | $pStart/descendant::*"/>

 <xsl:variable name="vPrecedingEnd" select=
  "$pEnd/preceding::* | $pEnd/ancestor::*"/>

 <xsl:variable name="vWanted" select=
  "$vFollowingStart intersect $vPrecedingEnd"/>

 <xsl:template match="node()|@*" name="identity">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="*[not(. intersect $vWanted)]">
  <xsl:apply-templates/>
 </xsl:template>
</xsl:stylesheet>

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

Объяснение : Использование оператора XPath 2.0 intersect.


III. Решение XPath 1.0, выбирающее только узлы без изменения документа:

Для удобства чтения я предоставляю XSLT-преобразование, которое выводит результат выбора нужных узлов. С этой же целью подвыражения определяются как переменные:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:param name="pStart" select="/*/b"/>
 <xsl:param name="pEnd" select="/*/d/d2/d2b"/>

 <xsl:template match="node()|@*">
  <xsl:variable name="vFollowingStart" select=
  "$pStart/following::* | $pStart/descendant::*"/>

 <xsl:variable name="vPrecedingEnd" select=
  "$pEnd/preceding::* | $pEnd/ancestor::*"/>

  <xsl:copy-of select=
   "$vFollowingStart
      [count(.|$vPrecedingEnd)
      =
       count($vPrecedingEnd)
      ]
   "/>
 </xsl:template>
</xsl:stylesheet>

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

<c/>
<d>

   <d1/>

   <d2>

      <d2a/>

      <d2b/>

      <d2c/>

   </d2>

</d>
<d1/>
<d2>

   <d2a/>

   <d2b/>

   <d2c/>

</d2>
<d2a/>

Объяснение : Здесь я использую формулу Кейса (от @Michael Kay) для пересечения двух наборов узлов $ns1 и $ns2:

$ns1[count(.|$ns2) = count($ns2)]

IV. Наконец, решение Xpath 2.0 (соответствующее решению XPath 1.0):

Я снова использую преобразование XSLT (2.0) для копирования результатов в вывод:

<xsl:stylesheet version="2.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:xs="http://www.w3.org/2001/XMLSchema">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>

     <xsl:param name="pStart" select="/*/b"/>
     <xsl:param name="pEnd" select="/*/d/d2/d2b"/>

     <xsl:template match="node()|@*">
      <xsl:variable name="vFollowingStart" select=
      "$pStart/following::* | $pStart/descendant::*"/>

     <xsl:variable name="vPrecedingEnd" select=
      "$pEnd/preceding::* | $pEnd/ancestor::*"/>

      <xsl:sequence select=
       "$vFollowingStart intersect $vPrecedingEnd"/>
     </xsl:template>
</xsl:stylesheet>

Получаются те же результаты (в точности нужные узлы), что и в решении XPath 1.0 :

<c/>
<d>
        <d1/>
        <d2>
               <d2a/>
               <d2b/>
               <d2c/>
        </d2>
    </d>
<d1/>
<d2>
            <d2a/>
            <d2b/>
            <d2c/>
</d2>
<d2a/>

ОБНОВЛЕНИЕ : Вот решение XPath 1.0 для «разумного» вопроса. Опять же, это выражается в виде модуля таблицы стилей XSLT, в котором для лучшей читаемости подвыражения определяются как отдельные переменные:

<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:param name="pStart" select="/*/*/b"/>
 <xsl:param name="pEnd" select="/*/*/d/d2/d2b"/>

  <xsl:variable name="vFollowingStart" select=
  "$pStart/following::* | $pStart/descendant::*"/>

  <xsl:variable name="vcommonAncestor" select=
  "$pStart/ancestor::*
    [count(.|$pEnd/ancestor::*)
    =
     count($pEnd/ancestor::*)
    ][1]
    "/>
 <xsl:variable name="vEndHighestAncestor" select=
  "$vcommonAncestor/*
       [count($pEnd | descendant::*)
       =
        count(descendant::*)
       ]"/>

  <xsl:variable name="vPrecedingEnd" select=
  "$vEndHighestAncestor/preceding::*
  |
   $vEndHighestAncestor/ancestor::*"/>


 <xsl:template match="node()|@*" name="identity">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="/">
  <xsl:copy-of select=
  "//*[count(.|$vFollowingStart) = count($vFollowingStart)
      and
       count(.|$vPrecedingEnd) = count($vPrecedingEnd)
      ]
  "/>
 </xsl:template>
</xsl:stylesheet>

Когда это преобразование применяется к следующему документу XML (аналогично предоставленному, но обернуто в еще один верхний элемент, и два дочерних элемента (g и h) добавляются к c - чтобы было интереснее:

<t>
<r>
  <a/><b/><c><g/><h/></c>
  <d>
    <d1/>
    <d2>
      <d2a/>
      <d2b/>
      <d2c/>
    </d2>
  </d>
  <e/>
</r>
</t>

желаемый, правильный набор узлов выбран и скопирован на выход :

<c>
   <g/>
   <h/>
</c>
<g/>
<h/>

Объяснение : Это почти то же самое, что и раньше, но мы принимаем за $pEnd его самого высокого предка - который является непосредственным потомком общего предка $pStart и $pEnd.

2 голосов
/ 20 октября 2011

Для вашей «разумной» цели: учитывая критерии «Начать с b, остановиться на d2b», вы можете использовать следующий XPath:

//b/following-sibling::*[following::d2b]

Поскольку ось following:: исключает потомков, это будетвыберите только следующие братья и сестры от b до того, который является предком (или самим собой) d2b.

(я предполагаю, что в документе есть только один элемент <b>, как вам показалосьвзять на себя.)

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