В DomDocument, повторное использование DOMXpath, он стабилен?

Я использую функцию ниже, но не уверен, что она всегда стабильна / безопасна … Не так ли?

Когда и кто стабилен / безопасен для «повторного использования частей процедур подготовки DOMXpath»?

Чтобы упростить использование метода XPath query (), мы можем использовать функцию, которая запоминает последние вызовы со статическими переменными,

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?? } 

Настоящий вопрос аналогичен этому другому вопросу о повторном использовании XSLTProcessor. В обоих вопросах проблема может быть обобщена для любого языка или структуры, которые используют LibXML2 в качестве реализации DomDocument.

Есть еще один связанный с этим вопрос: как «обновить» экземпляры DOMDocument 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 

Но если вы делаете что-то вроде removeChild , replaceChild и т. Д. (Пример),

  $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?

Класс DOMXpath (вместо XSLTProcessor в другом вопросе ) использует ссылку на данный объект DOMDocument в contructor. DOMXpath создает libxml контекста libxml на основе данного DOMDocument и сохраняет его во внутренние данные класса. Помимо контекста libxml он s saves references to original DOMDocument, заданный в аргументах contructor.

Что это значит:

Часть образца от ThomasWeinert ответ:

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

дает false после загрузки, поскольку $dom уже содержит указатель на новые данные libxml но DOMXpath содержит контекст libxml для $dom перед загрузкой и указатель на реальный документ после загрузки.

Теперь о выполнении query

Если он должен возвратить XPATH_NODESET (как в вашем случае), то сделайте узел node-node узлом, итерационным набором обнаруженных узлов ( \ext\dom\xpath.c из строки 468). Скопируйте, но с исходным узлом документа в качестве родителя . Это означает, что вы можете изменить результат, но это ушло с вашего XPath и DOMDocument соединения.

Результаты XPath предоставляют родительскийNode memeber, который знает их происхождение:

  • для значений атрибута parentNode возвращает элемент, который их переносит. Примером является // foo / @ attribute, где родителем будет foo Element.
  • для функции text () (как в // text ()), он возвращает элемент, содержащий текст или хвост, который был возвращен.
  • обратите внимание, что parentNode может не всегда возвращать элемент. Например, функции XPath string () и concat () будут строить строки, которые не имеют происхождения. Для них parentNode вернет None.

Так,

  1. Нет никаких причин для кэширования XPath . Это не что-то кроме xmlXPathNewContext (просто выделите легкую внутреннюю структуру ).
  2. Каждый раз, когда вы изменяете свой DOMDocument (removeChild, replaceChild и т. Д.), Вы должны воссоздать XPath .
  3. Мы не можем использовать что-то вроде normalizeDocument для «обновления DOM» из-за изменения структуры внутреннего документа и аннулирования xmlXPathNewContext созданного в конструкторе Xpath .
  4. Только «новый DOMXpath ($ doc)»; всегда безопасно? Да, если вы не меняете $ doc между использованием Xpath . Нужно перезагрузить $ doc также – нет, из-за этого недействителен ранее созданный xmlXPathNewContext .

На DOMXpath влияют методы load * () в DOMDocument. После загрузки нового 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 или документом, который вы создаете.

Следующий класс расширяет DOMDocument с помощью метода xpath (), который всегда возвращает для него действительный экземпляр 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 содержит предложение @ThomasWeinert (во избежание изменений DOM при внешней $enforceRefresh ) и параметр $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?

… возможно, открытая проблема, только маленькие подсказки и подсказки …

  • когда DOM подвергался несогласованности индексов и т. д. См. этот вопрос / решение .
  • …? больше случаев?