PHP SimpleXML xpath 在 returns 数据时不保留名称空间
PHP SimpleXML xpath does not keep the namespaces when returns data
我正在尝试注册一个命名空间,但每次我使用 xpath 的返回值时,我都必须一次又一次地注册相同的命名空间。
<?php
$xml= <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<response>
<extension>
<xyz:form xmlns:xyz="urn:company">
<xyz:formErrorData>
<xyz:field name="field">
<xyz:error>REQUIRED</xyz:error>
<xyz:value>username</xyz:value>
</xyz:field>
</xyz:formErrorData>
</xyz:form>
</extension>
</response>
</epp>
XML;
解析器:
$xmlObject = simplexml_load_string(trim($xml), NULL, NULL);
$xmlObject->registerXPathNamespace('ns','urn:company');
$fields = $xmlObject->xpath("//ns:field");
foreach($fields as $field){
//PHP Warning: SimpleXMLElement::xpath(): Undefined namespace prefix in
//$errors = $field->xpath("//ns:error");
// I have to register the same namespace again so it works
$field->registerXPathNamespace('ns','urn:company');
$errors = $field->xpath("//ns:error"); // no issue
var_dump((string)current($errors));
}
?>
请注意,我必须在循环内再次注册命名空间,否则我将收到以下错误:
//PHP Warning: SimpleXMLElement::xpath(): Undefined namespace prefix
in...
您是否知道如何在 xpath 函数返回的 simplexml 对象中保留已注册的命名空间。
是的,您的示例是正确的,不再次注册 xpath 命名空间会产生如下所示的警告,然后是另一个导致空结果的警告:
Warning: SimpleXMLElement::xpath(): Undefined namespace prefix
Warning: SimpleXMLElement::xpath(): xmlXPathEval: evaluation failed
评论中给出的解释相差无几,但是它们没有提供有助于回答您的问题的良好解释。
首先文档不正确。从技术上讲,它不仅适用于下一个 ::xpath()
调用:
$xmlObject->registerXPathNamespace('ns', 'urn:company');
$fields = $xmlObject->xpath("//ns:field");
$fields = $xmlObject->xpath("//ns:field");
$fields = $xmlObject->xpath("//ns:field");
$fields = $xmlObject->xpath("//ns:field");
这并没有给出警告,尽管它不仅是下一个,而且还有另外三个调用。所以评论中的描述可能更贴切,说这与对象有关。
一个解决方案是从 SimpleXMLElement 扩展并干扰命名空间注册,以便在执行 xpath 查询时,所有结果元素也可以注册命名空间前缀。但这将是很多工作,并且当您访问结果的更多子项时将不起作用。
此外,您不能分配数组或对象来将数据存储在 SimpleXMLElement 中,它总是会创建新的元素节点,然后出现不支持对象/数组的错误。
避免这种情况的一种方法是不存储在 SimpleXMLElement 内,而是存储在 DOM 内,可通过 dom_import_simplexml
.
因此,如果您创建一个 DOMXpath,您可以使用它注册命名空间。如果将实例存储在所有者文档中,则可以通过以下方式从任何 SimpleXMLElement 访问 xpath 对象:
dom_import_simplexml($xml)->ownerDocument-> /** your named field here **/
要使其正常工作,需要循环引用。我在 The SimpleXMLElement Magic Wonder World in PHP 中概述了这一点,并且易于访问的封装变体可能如下所示:
/**
* Class SimpleXpath
*
* DOMXpath wrapper for SimpleXMLElement
*
* Allows assignment of one DOMXPath instance to the document of a SimpleXMLElement so that all nodes of that
* SimpleXMLElement have access to it.
*
* @link
*/
class SimpleXpath
{
/**
* @var DOMXPath
*/
private $xpath;
/**
* @var SimpleXMLElement
*/
private $xml;
...
/**
* @param SimpleXMLElement $xml
*/
public function __construct(SimpleXMLElement $xml)
{
$doc = dom_import_simplexml($xml)->ownerDocument;
if (!isset($doc->xpath)) {
$doc->xpath = new DOMXPath($doc);
$doc->circref = $doc;
}
$this->xpath = $doc->xpath;
$this->xml = $xml;
}
...
此 class 构造函数负责确保 DOMXPath 实例可用,并根据 SimpleXMLElement[ 设置私有属性=74=] 传入ctor.
静态创建者函数方便以后访问:
/**
* @param SimpleXMLElement $xml
*
* @return SimpleXpath
*/
public static function of(SimpleXMLElement $xml)
{
$self = new self($xml);
return $self;
}
SimpleXpath 现在在实例化时始终具有 xpath 对象和 simplexml 对象。所以它只需要包装所有方法 DOMXpath 并将返回的节点转换回 simplexml 以具有此兼容性。这是一个关于如何将 DOMNodeList 转换为原始 class 的 SimpleXMLElements 数组的示例任何 SimpleXMLElement::xpath()
调用的行为:
...
/**
* Evaluates the given XPath expression
*
* @param string $expression The XPath expression to execute.
* @param DOMNode $contextnode [optional] <The optional contextnode
*
* @return array
*/
public function query($expression, SimpleXMLElement $contextnode = null)
{
return $this->back($this->xpath->query($expression, dom_import_simplexml($contextnode)));
}
/**
* back to SimpleXML (if applicable)
*
* @param $mixed
*
* @return array
*/
public function back($mixed)
{
if (!$mixed instanceof DOMNodeList) {
return $mixed; // technically not possible with std. SimpleXMLElement
}
$result = [];
$class = get_class($this->xml);
foreach ($mixed as $node) {
$result[] = simplexml_import_dom($node, $class);
}
return $result;
}
...
实际注册 xpath 名称空间更直接,因为它有效 1:1:
...
/**
* Registers the namespace with the DOMXPath object
*
* @param string $prefix The prefix.
* @param string $namespaceURI The URI of the namespace.
*
* @return bool true on success or false on failure.
*/
public function registerNamespace($prefix, $namespaceURI)
{
return $this->xpath->registerNamespace($prefix, $namespaceURI);
}
...
有了这些实现,剩下的就是从 SimpleXMLElement 扩展并将其与新创建的 SimpleXpath class:
/**
* Class SimpleXpathXMLElement
*/
class SimpleXpathXMLElement extends SimpleXMLElement
{
/**
* Creates a prefix/ns context for the next XPath query
*
* @param string $prefix The namespace prefix to use in the XPath query for the namespace given in ns.
* @param string $ns The namespace to use for the XPath query. This must match a namespace in use by the XML
* document or the XPath query using prefix will not return any results.
*
* @return bool TRUE on success or FALSE on failure.
*/
public function registerXPathNamespace($prefix, $ns)
{
return SimpleXpath::of($this)->registerNamespace($prefix, $ns);
}
/**
* Runs XPath query on XML data
*
* @param string $path An XPath path
*
* @return SimpleXMLElement[] an array of SimpleXMLElement objects or FALSE in case of an error.
*/
public function xpath($path)
{
return SimpleXpath::of($this)->query($path, $this);
}
}
在后台进行此修改后,如果您使用该子 class:
,它会透明地与您的示例一起工作
/** @var SimpleXpathXMLElement $xmlObject */
$xmlObject = simplexml_load_string($buffer, 'SimpleXpathXMLElement');
$xmlObject->registerXPathNamespace('ns', 'urn:company');
$fields = $xmlObject->xpath("//ns:field");
foreach ($fields as $field) {
$errors = $field->xpath("//ns:error"); // no issue
var_dump((string)current($errors));
}
此示例随后运行无错误,请参见此处:https://eval.in/398767
完整代码也在要点中:https://gist.github.com/hakre/1d9e555ac1ebb1fc4ea8
我正在尝试注册一个命名空间,但每次我使用 xpath 的返回值时,我都必须一次又一次地注册相同的命名空间。
<?php
$xml= <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<response>
<extension>
<xyz:form xmlns:xyz="urn:company">
<xyz:formErrorData>
<xyz:field name="field">
<xyz:error>REQUIRED</xyz:error>
<xyz:value>username</xyz:value>
</xyz:field>
</xyz:formErrorData>
</xyz:form>
</extension>
</response>
</epp>
XML;
解析器:
$xmlObject = simplexml_load_string(trim($xml), NULL, NULL);
$xmlObject->registerXPathNamespace('ns','urn:company');
$fields = $xmlObject->xpath("//ns:field");
foreach($fields as $field){
//PHP Warning: SimpleXMLElement::xpath(): Undefined namespace prefix in
//$errors = $field->xpath("//ns:error");
// I have to register the same namespace again so it works
$field->registerXPathNamespace('ns','urn:company');
$errors = $field->xpath("//ns:error"); // no issue
var_dump((string)current($errors));
}
?>
请注意,我必须在循环内再次注册命名空间,否则我将收到以下错误:
//PHP Warning: SimpleXMLElement::xpath(): Undefined namespace prefix in...
您是否知道如何在 xpath 函数返回的 simplexml 对象中保留已注册的命名空间。
是的,您的示例是正确的,不再次注册 xpath 命名空间会产生如下所示的警告,然后是另一个导致空结果的警告:
Warning: SimpleXMLElement::xpath(): Undefined namespace prefix
Warning: SimpleXMLElement::xpath(): xmlXPathEval: evaluation failed
评论中给出的解释相差无几,但是它们没有提供有助于回答您的问题的良好解释。
首先文档不正确。从技术上讲,它不仅适用于下一个 ::xpath()
调用:
$xmlObject->registerXPathNamespace('ns', 'urn:company');
$fields = $xmlObject->xpath("//ns:field");
$fields = $xmlObject->xpath("//ns:field");
$fields = $xmlObject->xpath("//ns:field");
$fields = $xmlObject->xpath("//ns:field");
这并没有给出警告,尽管它不仅是下一个,而且还有另外三个调用。所以评论中的描述可能更贴切,说这与对象有关。
一个解决方案是从 SimpleXMLElement 扩展并干扰命名空间注册,以便在执行 xpath 查询时,所有结果元素也可以注册命名空间前缀。但这将是很多工作,并且当您访问结果的更多子项时将不起作用。
此外,您不能分配数组或对象来将数据存储在 SimpleXMLElement 中,它总是会创建新的元素节点,然后出现不支持对象/数组的错误。
避免这种情况的一种方法是不存储在 SimpleXMLElement 内,而是存储在 DOM 内,可通过 dom_import_simplexml
.
因此,如果您创建一个 DOMXpath,您可以使用它注册命名空间。如果将实例存储在所有者文档中,则可以通过以下方式从任何 SimpleXMLElement 访问 xpath 对象:
dom_import_simplexml($xml)->ownerDocument-> /** your named field here **/
要使其正常工作,需要循环引用。我在 The SimpleXMLElement Magic Wonder World in PHP 中概述了这一点,并且易于访问的封装变体可能如下所示:
/**
* Class SimpleXpath
*
* DOMXpath wrapper for SimpleXMLElement
*
* Allows assignment of one DOMXPath instance to the document of a SimpleXMLElement so that all nodes of that
* SimpleXMLElement have access to it.
*
* @link
*/
class SimpleXpath
{
/**
* @var DOMXPath
*/
private $xpath;
/**
* @var SimpleXMLElement
*/
private $xml;
...
/**
* @param SimpleXMLElement $xml
*/
public function __construct(SimpleXMLElement $xml)
{
$doc = dom_import_simplexml($xml)->ownerDocument;
if (!isset($doc->xpath)) {
$doc->xpath = new DOMXPath($doc);
$doc->circref = $doc;
}
$this->xpath = $doc->xpath;
$this->xml = $xml;
}
...
此 class 构造函数负责确保 DOMXPath 实例可用,并根据 SimpleXMLElement[ 设置私有属性=74=] 传入ctor.
静态创建者函数方便以后访问:
/**
* @param SimpleXMLElement $xml
*
* @return SimpleXpath
*/
public static function of(SimpleXMLElement $xml)
{
$self = new self($xml);
return $self;
}
SimpleXpath 现在在实例化时始终具有 xpath 对象和 simplexml 对象。所以它只需要包装所有方法 DOMXpath 并将返回的节点转换回 simplexml 以具有此兼容性。这是一个关于如何将 DOMNodeList 转换为原始 class 的 SimpleXMLElements 数组的示例任何 SimpleXMLElement::xpath()
调用的行为:
...
/**
* Evaluates the given XPath expression
*
* @param string $expression The XPath expression to execute.
* @param DOMNode $contextnode [optional] <The optional contextnode
*
* @return array
*/
public function query($expression, SimpleXMLElement $contextnode = null)
{
return $this->back($this->xpath->query($expression, dom_import_simplexml($contextnode)));
}
/**
* back to SimpleXML (if applicable)
*
* @param $mixed
*
* @return array
*/
public function back($mixed)
{
if (!$mixed instanceof DOMNodeList) {
return $mixed; // technically not possible with std. SimpleXMLElement
}
$result = [];
$class = get_class($this->xml);
foreach ($mixed as $node) {
$result[] = simplexml_import_dom($node, $class);
}
return $result;
}
...
实际注册 xpath 名称空间更直接,因为它有效 1:1:
...
/**
* Registers the namespace with the DOMXPath object
*
* @param string $prefix The prefix.
* @param string $namespaceURI The URI of the namespace.
*
* @return bool true on success or false on failure.
*/
public function registerNamespace($prefix, $namespaceURI)
{
return $this->xpath->registerNamespace($prefix, $namespaceURI);
}
...
有了这些实现,剩下的就是从 SimpleXMLElement 扩展并将其与新创建的 SimpleXpath class:
/**
* Class SimpleXpathXMLElement
*/
class SimpleXpathXMLElement extends SimpleXMLElement
{
/**
* Creates a prefix/ns context for the next XPath query
*
* @param string $prefix The namespace prefix to use in the XPath query for the namespace given in ns.
* @param string $ns The namespace to use for the XPath query. This must match a namespace in use by the XML
* document or the XPath query using prefix will not return any results.
*
* @return bool TRUE on success or FALSE on failure.
*/
public function registerXPathNamespace($prefix, $ns)
{
return SimpleXpath::of($this)->registerNamespace($prefix, $ns);
}
/**
* Runs XPath query on XML data
*
* @param string $path An XPath path
*
* @return SimpleXMLElement[] an array of SimpleXMLElement objects or FALSE in case of an error.
*/
public function xpath($path)
{
return SimpleXpath::of($this)->query($path, $this);
}
}
在后台进行此修改后,如果您使用该子 class:
,它会透明地与您的示例一起工作/** @var SimpleXpathXMLElement $xmlObject */
$xmlObject = simplexml_load_string($buffer, 'SimpleXpathXMLElement');
$xmlObject->registerXPathNamespace('ns', 'urn:company');
$fields = $xmlObject->xpath("//ns:field");
foreach ($fields as $field) {
$errors = $field->xpath("//ns:error"); // no issue
var_dump((string)current($errors));
}
此示例随后运行无错误,请参见此处:https://eval.in/398767
完整代码也在要点中:https://gist.github.com/hakre/1d9e555ac1ebb1fc4ea8