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

Abd*_*lah 0 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>
             <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;
Run Code Online (Sandbox Code Playgroud)

解析器:

         $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));

        }

?>
Run Code Online (Sandbox Code Playgroud)

请注意,我不得不在循环内再次注册名称空间,如果不这样做,我将得到以下错误:

// PHP警告:SimpleXMLElement :: xpath():...中的未定义名称空间前缀

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

hak*_*kre 5

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

警告:SimpleXMLElement :: xpath():未定义的名称空间前缀

警告:SimpleXMLElement :: xpath():xmlXPathEval:评估失败

注释中给出的解释距离不太远,但是它们没有提供可以帮助您回答问题的良好解释。

首先,文档不正确。从技术上讲,它不仅用于下一次::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");
Run Code Online (Sandbox Code Playgroud)

尽管这不仅是下一个警报,而且还有另外三个呼叫,但它不会发出警告。因此,注释中的描述可能更适合与对象相关的内容。

一种解决方案是从SimpleXMLElement扩展并干扰名称空间注册,以便在执行xpath查询时,所有结果元素也可以注册名称空间前缀。但这将是很多工作,并且当您访问结果的更多子级时将不起作用。

另外,您不能分配数组或对象来将数据存储在SimpleXMLElement中,否则它将始终创建新的元素节点,然后出错,即不支持对象/数组。

避免这种情况的一种方法是,不将其存储在SimpleXMLElement内,而是将其存储在DOM中,而DOM可通过进行访问dom_import_simplexml

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

dom_import_simplexml($xml)->ownerDocument-> /** your named field here **/
Run Code Online (Sandbox Code Playgroud)

为此,需要循环引用。我在PHP的SimpleXMLElement Magic Wonder World中对此进行了概述,并且易于访问的封装变体如下所示:

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

    ...
Run Code Online (Sandbox Code Playgroud)

此类的构造函数会确保DOMXPath实例可用,并根据在ctor中传递的SimpleXMLElement设置私有属性。

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

    /**
     * @param SimpleXMLElement $xml
     *
     * @return SimpleXpath
     */
    public static function of(SimpleXMLElement $xml)
    {
        $self = new self($xml);
        return $self;
    }
Run Code Online (Sandbox Code Playgroud)

现在,在实例化时,SimpleXpath始终具有xpath对象和simplexml对象。因此,它只需要包装DOMXpath拥有的所有方法,并将返回的节点转换回simplexml以使其兼容。这是一个有关如何将DOMNodeList转换为原始类的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;
    }

    ...
Run Code Online (Sandbox Code Playgroud)

对于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);
    }

    ...
Run Code Online (Sandbox Code Playgroud)

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

/**
 * 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

进行了这种修改,如果您使用该子类,则可以与您的示例透明地工作:

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

}
Run Code Online (Sandbox Code Playgroud)

然后,该示例运行无错误,请参见此处:https : //eval.in/398767

完整的代码也有其要点:https : //gist.github.com/hakre/1d9e555ac1ebb1fc4ea8