PHP SimpleXML xpath在返回数据时不保留名称空间

PHP SimpleXML xpath在返回数据时不保留名称空间,php,xpath,simplexml,Php,Xpath,Simplexml,我试图注册一个名称空间,但每次使用xpath返回的值时,我都必须一次又一次地注册相同的名称空间 <?php $xml= <<<XML <?xml version="1.0" encoding="UTF-8"?> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <response> <extension> &

我试图注册一个名称空间,但每次使用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;
请注意,我必须在循环中再次注册名称空间,如果我没有注册,则会出现以下错误:

//PHP警告:SimpleXMLElement::xpath:未定义的命名空间前缀 在


您知道如何在xpath函数返回的simplexml对象中保留已注册的名称空间吗。

是的,您的示例是正确的,不再次注册xpath名称空间将创建一个如下警告,然后是另一个导致空结果的警告:

警告:SimpleXMLElement::xpath:未定义的命名空间前缀

警告:SimpleXMLElement::xpath:XmlXpathVal:计算失败

评论中给出的解释并不遥远,但是它们并不能提供一个很好的解释来帮助回答您的问题

首先,文件不正确。从技术上讲,它不仅适用于下一个::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中的数据。它将始终创建新元素节点,然后错误地指出不支持对象/数组

避免这种情况的一种方法是存储在DOM中,而不是存储在SimpleXMLElement中,DOM可以通过DOM\u import\u simplexml访问

因此,如果您创建一个DOMXpath,您可以向它注册名称空间。如果将实例存储在所有者文档中,则可以通过以下方式从任何SimpleXMLElement访问xpath对象:

要使其工作,需要循环引用。我在中概述了这一点,一个易于访问的封装变体可能看起来像:

/**
 * 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;
    }

    ...
该类构造函数负责确保DOMXPath实例可用,并根据在构造函数中传递的SimpleXMLElement设置私有属性

静态创建者功能允许以后轻松访问:

    /**
     * @param SimpleXMLElement $xml
     *
     * @return SimpleXpath
     */
    public static function of(SimpleXMLElement $xml)
    {
        $self = new self($xml);
        return $self;
    }
SimpleExpath现在在实例化时始终具有xpath对象和simplexml对象。因此,它只需要包装DOMXpath拥有的所有方法,并将返回的节点转换回simplexml即可实现此兼容。下面是一个示例,介绍如何将DOMNodeList转换为原始类的SimpleXMLElement数组,这是任何SimpleXMLElement::xpath调用的行为:

有了这些实现,剩下的就是从SimpleXMLElement进行扩展,并将其与新创建的SimpleExpath类连接起来:

在引擎盖下进行此修改后,如果您使用该子类,它将透明地与您的示例一起工作:

/** @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));

}
然后,此示例将无错误运行,请参见此处:


完整的代码也在一个要点中:

是的,您的示例是对的,不再次注册xpath名称空间将创建一个类似于以下的警告,然后是另一个导致空结果的警告:

警告:SimpleXMLElement::xpath:未定义的命名空间前缀

警告:SimpleXMLElement::xpath:XmlXpathVal:计算失败

评论中给出的解释并不遥远,但是它们并不能提供一个很好的解释来帮助回答您的问题

首先,文件不正确。从技术上讲,它不仅适用于下一个::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中的数据。它将始终创建新元素节点,然后错误地指出不支持对象/数组

避免这种情况的一种方法是存储在DOM中,而不是存储在SimpleXMLElement中,DOM可以通过DOM\u import\u simplexml访问

因此,如果您创建一个DOMXpath,您可以向它注册名称空间。如果将实例存储在所有者文档中,则可以通过以下方式从任何SimpleXMLElement访问xpath对象:

要使其工作,需要循环引用。我勾勒出了t his in和易于访问的封装变体可能看起来像:

/**
 * 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;
    }

    ...
该类构造函数负责确保DOMXPath实例可用,并根据在构造函数中传递的SimpleXMLElement设置私有属性

静态创建者功能允许以后轻松访问:

    /**
     * @param SimpleXMLElement $xml
     *
     * @return SimpleXpath
     */
    public static function of(SimpleXMLElement $xml)
    {
        $self = new self($xml);
        return $self;
    }
SimpleExpath现在在实例化时始终具有xpath对象和simplexml对象。因此,它只需要包装DOMXpath拥有的所有方法,并将返回的节点转换回simplexml即可实现此兼容。下面是一个示例,介绍如何将DOMNodeList转换为原始类的SimpleXMLElement数组,这是任何SimpleXMLElement::xpath调用的行为:

有了这些实现,剩下的就是从SimpleXMLElement进行扩展,并将其与新创建的SimpleExpath类连接起来:

在引擎盖下进行此修改后,如果您使用该子类,它将透明地与您的示例一起工作:

/** @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));

}
然后,此示例将无错误运行,请参见此处:


完整的代码也在一个要点中:

好吧,因为registerXPathNamespace是SimpleXMLElement的一个方法,并且每次都在循环中操作一个新元素,这看起来很自然。顺便说一句,手册用“为下一个XPath查询创建前缀/ns上下文”明确描述了此方法,4年前的第一条也是唯一一条用户评论也指出,“看起来在使用XPath时,每个节点都必须使用registerXPathNamespace。”感谢@CBroe,我看到了这条评论,但从他的负面声誉来看,我认为他错了。@Abdullah我建议不要太注意php.net上的评论投票。他们可能完全是胡说八道。不好的建议通常会有很深的负面分数,但好的建议通常也会有负面分数。好吧,因为registerXPathNamespace是SimpleXMLElement的一种方法,并且每次都在循环中操作一个新元素,这似乎很自然。顺便说一句,手册用“为下一个XPath查询创建前缀/ns上下文”明确描述了此方法,4年前的第一条也是唯一一条用户评论也指出,“看起来在使用XPath时,每个节点都必须使用registerXPathNamespace。”感谢@CBroe,我看到了这条评论,但从他的负面声誉来看,我认为他错了。@Abdullah我建议不要太注意php.net上的评论投票。他们可能完全是胡说八道。不好的建议通常会有很深的负面分数,但好的建议通常也会有负面分数。在中,这里描述的技巧也以稍微不同的方式进行了概述。在中,这里描述的技巧也以稍微不同的方式进行了概述。
/** @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));

}