Могу ли я сделать этот поиск xpath быстрее? - PullRequest
3 голосов
/ 25 августа 2011

XML:

<root><br/> <a auto='1'><br/> <b><br/> <c auto="1"><br/> <d auto="1"></d><br/> </c><br/> </b><br/> <e auto="1"><br/> <f><br/> <g auto="1"></g><br/> </f><br/> </e><br/> </a><br/> </root><br/>

Работа: найти все элементы, которые:
1, является потомком элемента контекста
2, имеют атрибут ' auto '
3, Высший уровень (без предка с автоматическим атрибутом между собой и элементом контекста)

Итак, если узел контекста равен a , c и e должны быть возвращены.

Я реализую это в своем классе php:
$tempId='XDFAY69LA';<br/> $this->setAttribute('tempId',$tempId);<br/> $path=".//*[@auto and not(ancestor::*[@auto and ancestor::*[@tempId='$tempId']])]";<br/> $ar=$this->getElementsByXPath($path);<br/> $this->removeAttribute('tempId');<br/>
Но я обнаружил, что запрос выполняется медленно, может быть .., потому что запрос слишком сложный ?, И есть ли способ сделать его лучше?

Я напишу тестирование, пожалуйста, посмотрите:


    <?php
    $xml='
      <root>
        <a auto="1" tempId="current">
          <b>
            <c auto="1">
              <d auto="1"></d>
            </c>
          </b>
          <e auto="1">
            <f>
              <g auto="1"></g>
            </f>
          </e>
        </a>
      </root> ';

    $doc=new DomDocument();
    $tempId='XDFAY69LA';
    $doc->loadXml($xml);
    $domxpath=new DOMXPath($doc);
    $a=$domxpath->query('a')->item(0);
    $path=".//*[@auto and not(ancestor::*[@auto and ancestor::*[@tempId='$tempId']])]";
    $start=microtime(true);
    for($n=0;$n<1000;$n++){ //run 1000 times
      $a->setAttribute('tempId',$tempId);
      $ar=$domxpath->query($path,$a);
      $a->removeAttribute('tempId');
      for($i=0;$i<$ar->length;$i++){
        $node=$ar->item($i);
        //echo $node->tagName . "\n";
      }
    }
    $cost=round(1000 * (microtime(true)-$start));
    echo "time cost: $cost";
    ?>

Ответы [ 3 ]

2 голосов
/ 25 августа 2011

Используйте

.//*[@auto and $tempId = ancestor::*[@auto][1]/@tempId]

При этом выбираются все элементы-потомки (узла контекста), которые имеют атрибут auto и чей первый предок, имеющий атрибут auto, также имеет атрибут tempId с тем же значением, что и у атрибута tempId узла контекста (последний хранится в переменной $tempId).

Здесь мы предполагаем, что никакие два разных элемента не имеют одинакового значения своих атрибутов tempId.

Быстрая проверка на основе XSLT :

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

 <xsl:template match="a">
   <xsl:variable name="tempId" select="@tempId"/>

     <xsl:copy-of select=
      ".//*[@auto and $tempId = ancestor::*[@auto][1]/@tempId]"/>
 </xsl:template>
</xsl:stylesheet>

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

<root>
    <a auto="1" tempId="current">
        <b>
            <c auto="1">
                <d auto="1"></d>
            </c>
        </b>
        <e auto="1">
            <f>
                <g auto="1"></g>
            </f>
        </e>
    </a>
</root>

желаемый, правильный результат (два элемента c и e) получается :

<c auto="1">
   <d auto="1"/>
</c>
<e auto="1">
   <f>
      <g auto="1"/>
   </f>
</e>

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

При использовании 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:key name="kfirstDescendents" match="*[@auto]"
  use="generate-id(ancestor::*[@auto][1])"/>

 <xsl:template match="a">
     <xsl:copy-of select=
      "key('kfirstDescendents', generate-id())"/>
 </xsl:template>
</xsl:stylesheet>

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

Если использование XSLT абсолютно исключено, можно добиться того же эффекта, что и ключи XSLT, с использованием хеш-таблиц (извините, PHP не знаю).

1 голос
/ 25 августа 2011

Начиная с вашего XPath:

 .//*[@auto and not(ancestor::*[@auto and ancestor::*[@tempId='$tempId']])]

как насчет:

 .//*[@auto and not(ancestor::*[@auto][ancestor::*[@tempId='$tempId']])]

или даже

 .//*[@auto and count(ancestor::*[@auto][ancestor::*[@tempId='$tempId']])=0]
0 голосов
/ 25 августа 2011

Моя идея немного упростить это.

$path=".//*[@auto and not(ancestor::*[@auto and not(@tempId='$tempId'))]";

"предок :: * [@ tempId = '$ tempId']"

до

"нет (@tempId = '$ tempId')"


// редактирование содержимого: устранено подробное описание

...