Я использую функцию ниже, но не уверен, что она всегда стабильна / безопасна … Не так ли?
Когда и кто стабилен / безопасен для «повторного использования частей процедур подготовки 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! !!
могут произойти события , и запросы не работают так, как ожидалось!
Класс 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, который знает их происхождение:
Так,
XPath
. Это не что-то кроме xmlXPathNewContext
(просто выделите легкую внутреннюю структуру ). DOMDocument
(removeChild, replaceChild и т. Д.), Вы должны воссоздать XPath
. xmlXPathNewContext
созданного в конструкторе Xpath
. 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; }
… возможно, открытая проблема, только маленькие подсказки и подсказки …
… возможно, открытая проблема, только маленькие подсказки и подсказки …