【问题标题】:In DomDocument, reuse of DOMXpath, it is stable?在DomDocument中,复用DOMXpath,是否稳定?
【发布时间】:2013-11-30 17:07:45
【问题描述】:

我正在使用下面的功能,但不确定它是否总是稳定/安全... 是吗?

何时以及谁可以稳定/安全地“重用 DOMXpath 准备过程的部分内容”?

为了简化XPath query() method 的使用,我们可以采用一个函数,用静态变量记住最后一次调用,

   function DOMXpath_reuser($file) {
      static $doc=NULL;
      static $docName='';
      static $xp=NULL;
      if (!$doc)
                $doc = new DOMDocument();
      if ($file!=$docName) {
                $doc->loadHTMLFile($file);
                $xp = NULL;
      }
      if (!$xp) 
                $xp = new DOMXpath($doc);
      return $xp;  // ??RETURNED VALUES ARE ALWAYS STABLE??
   }

目前的问题类似于this other one 关于 XSLTProcessor 重用的问题。 在这两个问题中,问题都可以推广到使用 LibXML2 作为 DomDocument 实现的任何语言或框架。

还有一个相关问题:How to "refresh" DOMDocument instances of LibXML2?


说明

复用很常见(例子):

   $f = "my_XML_file.xml";
   $elements = DOMXpath_reuser($f)->query("//*[@id]");
   // use elements to get information
   $elements = DOMXpath_reuser($f)->("/html/body/div[1]");
   // use elements to get information

但是,如果您执行removeChildreplaceChild 之类的操作(示例),

   $div = DOMXpath_reuser($f)->query("/html/body/div[1]")->item(0);  //STABLE
   $div->parentNode->removeChild($div);                // CHANGES DOM
   $elements = DOMXpath_reuser($f)->query("//div[@id]"); // INSTABLE! !!

可能会发生异常情况,并且查询无法按预期工作!!

  • 何时(哪些 DOMDocument 方法会影响 XPath?)
  • 为什么我们不能使用 normalizeDocument 之类的东西来“刷新 DOM”(存在吗?)?
  • 只有一个“新的 DOMXpath($doc);”总是安全的吗?还需要重新加载 $doc 吗?

【问题讨论】:

  • “只读”的意思是“稳定”吗?为什么会这样? DOMDocument 用于读取、修改和创建 XML 文档。
  • 稳定为“按预期工作”、“没有错误”、“不出意外”。 DOMDocument 在创建或修改节点时没有属性说“DOM 映射丢失,必须刷新”:有些修改会影响 DOM,有些则不会。示例:在遍历 DOM 的循环中,setAttribute 方法没有问题,但一般replaceChild 有问题。

标签: php xpath


【解决方案1】:

DOMXpath 受 DOMDocument 上的 load*() 方法影响。加载新的 xml 或 html 后,需要重新创建 DOMXpath 实例:

$xml = '<xml/>';    
$dom = new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXpath($dom);

var_dump($xpath->document === $dom); // bool(true)

$dom->loadXml($xml);

var_dump($xpath->document === $dom); // bool(false)

在 DOMXpath_reuser() 中,您存储一个静态变量并根据文件名重新创建 xpath。如果要重用 Xpath 对象,建议扩展 DOMDocument。这样你只需要传递 $dom 变量。它适用于存储的 xml 文件以及 xml 字符串或您正在创建的文档。

以下类使用 xpath() 方法扩展 DOMDocument,该方法始终为其返回有效的 DOMXpath 实例。它也存储和注册命名空间:

class MyDOMDocument
  extends DOMDocument {

  private $_xpath = NULL;
  private $_namespaces = array();

  public function xpath() {
    // if the xpath instance is missing or not attached to the document
    if (is_null($this->_xpath) || $this->_xpath->document != $this) {
      // create a new one
      $this->_xpath = new DOMXpath($this);
      // and register the namespaces for it
      foreach ($this->_namespaces as $prefix => $namespace) {
        $this->_xpath->registerNamespace($prefix, $namespace);
      }
    }
    return $this->_xpath;
  }

  public function registerNamespaces(array $namespaces) {
    $this->_namespaces = array_merge($this->_namespaces, $namespaces);
    if (isset($this->_xpath)) {
      foreach ($namespaces as $prefix => $namespace) {
        $this->_xpath->registerNamespace($prefix, $namespace);
      }
    }
  }
}

$xml = <<<'ATOM'
  <feed xmlns="http://www.w3.org/2005/Atom">
    <title>Test</title>
  </feed>
ATOM;


$dom = new MyDOMDocument();
$dom->registerNamespaces(
  array(
    'atom' => 'http://www.w3.org/2005/Atom'
  )
);
$dom->loadXml($xml);
// created, first access
var_dump($dom->xpath()->evaluate('string(/atom:feed/atom:title)', NULL, FALSE));
$dom->loadXml($xml);
// recreated, connection was lost
var_dump($dom->xpath()->evaluate('string(/atom:feed/atom:title)', NULL, FALSE));

【讨论】:

  • 感谢您的第一个解释 (!),显示了我对 DOMXpath_reuser() 函数的更正解释:if ($file!=$docName) ... 行必须与 if ( $file!=$docName || ($xp &amp;&amp; $doc !== $xp-&gt;document) ) ... 正确,是吗? 关于第二个解释我不明白你为什么解释命名空间过程,我看不出问题的重点。
  • 我了解到您正在寻找“不稳定性”。我的错。我更改了答案以显示 DOMXpath 重用的可能解决方案,而不依赖于文件名或静态变量。
【解决方案2】:

DOMXpath 类(而不是 your another question 中的 XSLTProcessor)在构造函数中使用对给定 DOMDocument 对象的引用。 DOMXpath 根据给定的DOMDocument 创建libxml 上下文对象并将其保存到内部类数据中。除了libxml context its saves references to originalDOMDocument` 在构造函数参数中给出。

这意味着什么:

来自ThomasWeinert答案的部分样本:

var_dump($xpath->document === $dom); // bool(true)  
$dom->loadXml($xml);    
var_dump($xpath->document === $dom); // bool(false)

在加载后给出错误,因为$dom 已经持有指向新的libxml 数据的指针,但DOMXpath 在加载之前为$dom 持有libxml 上下文,并且在加载后指向真实文档的指针。

现在关于 query 的作品

如果它应该返回XPATH_NODESET(如您的情况),它会制作一个节点副本-逐个节点迭代抛出检测到的节点集(来自468行的\ext\dom\xpath.c)。 复制但以原始文档节点为父节点。这意味着您可以修改结果,但这会让您失去 XPath 和 DOMDocument 的连接。

XPath 结果提供了一个知道其来源的 parentNode 成员:

  • 对于属性值,parentNode 返回携带它们的元素。一个例子是 //foo/@attribute,其中父元素是 foo 元素。
  • 对于 text() 函数(如 //text()),它返回包含返回的文本或尾部的元素。
  • 请注意,parentNode 可能并不总是返回一个元素。例如,XPath 函数 string() 和 concat() 将构造没有来源的字符串。对他们来说,parentNode 将返回 None。

所以,

  1. 没有任何理由缓存XPath。除了xmlXPathNewContext(只分配轻量级internal struct)之外,它什么也不做。
  2. 每次修改DOMDocument(removeChild、replaceChild 等)时,您都应该重新创建XPath
  3. 我们不能使用 normalizeDocument 之类的东西来“刷新 DOM”,因为它会改变内部文档结构并使在 Xpath 构造函数中创建的 xmlXPathNewContext 无效。
  4. 只有“新 DOMXpath($doc);”总是安全的吗?是的,如果您不在 Xpath 使用之间更改 $doc。还需要重新加载 $doc - 不,因为它使之前创建的 xmlXPathNewContext 无效。

【讨论】:

    【解决方案3】:

    (这不是真正的答案,而是此处发布的 cmets 和答案以及相关问题的合并)


    这个新版本的问题DOMXpath_reuser 函数包含@ThomasWeinert 建议(用于避免外部re-load 更改DOM)和一个选项$enforceRefresh 以解决不稳定问题(如相关问题显示程序员必须检测何时)。

       function DOMXpath_reuser_v2($file, $enforceRefresh=0) {  //changed here
          static $doc=NULL;
          static $docName='';
          static $xp=NULL;
          if (!$doc)
                    $doc = new DOMDocument();
          if ( $file!=$docName || ($xp && $doc !== $xp->document) ) { // changed here
                    $doc->load($file);
                    $xp = NULL;
          } elseif ($enforceRefresh==2) {  // add this new refresh mode
                    $doc->loadXML($doc->saveXML());
                    $xp = NULL;
          }
          if (!$xp || $enforceRefresh==1)  //changed here
                    $xp = new DOMXpath($doc);
          return $xp;
       }
    

    什么时候必须使用 $enforceRefresh=1 ?

    ...也许是一个悬而未决的问题,只有一些小技巧和线索...

    • DOM 提交到 setAttribute、removeChild、replaceChild 等时
    • ...?更多案例?

    什么时候必须使用 $enforceRefresh=2 ?

    ...也许是一个悬而未决的问题,只有一些小技巧和线索...

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-03-17
      • 2015-02-28
      • 1970-01-01
      • 1970-01-01
      • 2014-10-31
      • 1970-01-01
      • 2013-04-09
      • 1970-01-01
      相关资源
      最近更新 更多