如何在保持当前属性的同时动态创建PHP SimpleXMLElement对象?

时间:2013-04-12 16:32:43

标签: php xml object

我正在读取一个xml文件,该文件返回xml的SimpleXMLElement对象表示。我将采用一个数组并为该对象提供新值。我不知道我将在那个数组中做什么。

如果我蛮力,我会做这样的事情。

//Solution 1: Brute Force

    //Just creating an array and value for purposes of demonstration.
    $arOfData = array( [0]=>"theFirstNode", [1]=>"theSecondNode",[2]=>"theThirdNode" );
    $value = "The XML Node Value";

    $simpleXml->$arOfData[0]->$arOfData[1]->$arOfData[2] = $value;

    //The next best thing I can think of doing is something like this.
    //Solution 2: Semi-brute force
    //
    foreach($this->arrayData as $key => $value) {
                $xmlNodes = explode( '-', $key);
                $numNodes = count($xmlNodes);
                switch($numNodes) {
                    case 1:
                        $simpleXml->$xmlNodes[0] = $value;
                        break;
                    case 2:
                        $simpleXml->$xmlNodes[0]->$xmlNodes[1] = $value;
                        break;
                    case 3:
                        $simpleXml->$xmlNodes[0]->$xmlNodes[1]->$xmlNodes[2] = $value;
                        break;
                    case 4:
                        $simpleXml->$xmlNodes[0]->$xmlNodes[1]->$xmlNodes[2]->$xmlNodes[3] = $value;
                        break;
                    case 5:
                        $simpleXml->$xmlNodes[0]->$xmlNodes[1]->$xmlNodes[2]->$xmlNodes[3]->$xmlNodes[4] = $value;
                        break;
                }
            }

* note此解决方案使用数组键并将其展开为由短划线分隔的数组,然后将该数组值用作新的xml值。所以不要让它分散你的注意力。

解决方案#2的问题是:当我们得到一个深度超过5的xml节点时会发生什么?它不会被塞进我们正在创建的新对象中。哦,哦。它也不是很优雅;)。我不知道如何以更加递归的方式做到这一点。

1 个答案:

答案 0 :(得分:1)

就像你在问题中写的一样,你需要动态地拥有它,因为你不知道父元素的数量。

您需要深入了解simpexml如何完成这项工作。

但首先让我建议你有一个不同的符号,不是你的减号,而是路径中的斜线。

first/second/third

这在Xpath中也很常见,我认为它很适合自己。减号也可以是元素名称的一部分,但斜杠不能。所以这只是更好一点。

在我向您展示如何轻松访问 <third> 元素节点以设置其值之前,首先让我们看一下simplexml中的一些分配基础。

要在SimpleXMLElement中访问和设置此元素节点,请参阅以下示例:

$xml = new SimpleXMLElement('<root><first><second><third/></second></first></root>');    

$element = $xml->first->second->third;
$element[0] = "value";

这很简单,但你可以在这里看到两件事:

  1. <third> 元素已存在于文档中。
  2. 代码使用simplexml-self-reference([0]),它允许设置元素变量的XML值(而不是变量)。这特定于 SimpleXMLElement 的工作方式。
  3. 第二点还包含如何处理不存在的元素的问题的解决方案。如果元素不存在,$element[0]NULL

    $xml = new SimpleXMLElement('<root><first><second/></first></root>');
    
    $element = $xml->first->second->third;
    var_dump($element[0]); # NULL
    

    因此,让我们尝试有条件地添加第三个​​元素,以防它不存在:

    if ($xml->first->second->third[0] === NULL) {
        $xml->first->second->third = "";
    }
    

    这确实解决了这个问题。因此,唯一要做的就是以迭代的方式为路径的所有部分做到这一点:

    first/second/third
    

    为了保持这一点,请为此创建一个函数:

    /**
     * Modify an elements value specified by a string-path.
     *
     * @param SimpleXMLElement $parent
     * @param string           $path
     * @param string           $value (optional)
     *
     * @return SimpleXMLElement the modified element-node
     */
    function simplexml_deep_set(SimpleXMLElement $parent, $path, $value = '') 
    {
        ### <mocked> to be removed later: ###
    
        if ($parent->first->second->third[0] === NULL) {
            $parent->first->second->third = "";
        }
    
        $element = $parent->first->second->third;
    
        ### </mocked> ###
    
        $element[0] = $value;
    
        return $element;
    }
    

    因为该函数是模拟的,所以可以直接使用:

    $xml = new SimpleXMLElement('<root><first><second/></first></root>');
    
    simplexml_deep_set($xml, "first/second/third", "The XML Node Value");
    
    $xml->asXML('php://output');
    

    这有效:

    <?xml version="1.0"?>
    <root><first><second><third>The XML Node Value</third></second></first></root>
    

    所以现在删除模拟。首先插入 explode ,就像你拥有它一样。然后,所有需要做的就是沿着路径的每一步进行,并且如果它不存在则有条件地创建元素。最后 $element 将是要修改的元素:

    $steps   = explode('/', $path);
    
    $element = $parent;
    foreach ($steps as $step)
    {
        if ($element->{$step}[0] === NULL) {
            $element->$step = '';
        }
    
        $element = $element->$step;
    }
    

    需要使用 foreach 将mock替换为工作版本。与全功能定义一目了然:

    function simplexml_deep_set(SimpleXMLElement $parent, $path, $value = '')
    {
        $steps   = explode('/', $path);
    
        $element = $parent;
        foreach ($steps as $step)
        {
            if ($element->{$step}[0] === NULL) {
                $element->$step = "";
            }
    
            $element = $element->$step;
        }
    
        $element[0] = $value;
    
        return $element;
    }
    

    让我们修改更多疯狂的东西来测试它:

    $xml = new SimpleXMLElement('<root><first><second/></first></root>');
    
    simplexml_deep_set($xml, "first/second/third", "The XML Node Value");
    
    simplexml_deep_set(
        $xml, "How/do/I/dynamically/create/a/php/simplexml/object/while/keeping/current/properties"
        , "The other XML Node Value"
    );
    
    $xml->asXML('php://output');
    

    示例 - 输出(美化):

    <?xml version="1.0"?>
    <root>
      <first>
        <second>
          <third>The XML Node Value</third>
        </second>
      </first>
      <How>
        <do>
          <I>
            <dynamically>
              <create>
                <a>
                  <php>
                    <simplexml>
                      <object>
                        <while>
                          <keeping>
                            <current>
                              <properties>The other XML Node Value</properties>
                            </current>
                          </keeping>
                        </while>
                      </object>
                    </simplexml>
                  </php>
                </a>
              </create>
            </dynamically>
          </I>
        </do>
      </How>
    </root>
    

    See it in action.