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

找到所有的元素

  1. 是上下文元素的后代
  2. auto属性
  3. auto最高级别(在 self 和上下文元素之间没有具有属性的祖先)

因此,如果上下文节点是a则应该返回。ce

我已经在我的 php 类中实现了它:

$tempId='XDFAY69LA';
$this->setAttribute('tempId',$tempId);
$path=".//\*[@auto and not(ancestor::\*[@auto and ancestor::\*[@tempId='$tempId']])]";
$ar=$this->getElementsByXPath($path);
$this->removeAttribute('tempId');

但是我发现查询很慢,也许..,因为查询太复杂了?,有没有办法做得更好?


我写了一个测试,请看一下:

<?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";
?>
4

3 回答 3

3

使用

.//*[@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>

产生了想要的正确结果(两个元素ce

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

仅在 XPath 表达式内无法提高性能,效率低下是由于必须使用//XPath 伪运算符。

如果使用 XSLT,则可以使用 keys 获得有效的解决方案

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

于 2011-08-25T12:55:34.013 回答
1

从您的 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]
于 2011-08-25T08:40:30.563 回答
0

我的想法是简化一点。

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

“祖先::*[@tempId='$tempId']”

“不是(@tempId='$tempId')”


//编辑内容:消除冗长

于 2011-08-25T03:42:08.843 回答