PHP SimpleXML: вставить узел в определенную позицию


Скажем, у меня есть XML:

<root>
  <nodeA />
  <nodeA />
  <nodeA />
  <nodeC />
  <nodeC />
  <nodeC />
</root>

Как вставить "nodeB" между As и Cs? В PHP, предпочтительно через SimpleXML? Например:

<root>
  <nodeA />
  <nodeA />
  <nodeA />
  <nodeB />
  <nodeC />
  <nodeC />
  <nodeC />
</root>
2 9

2 ответа:

Ниже приведена функция для вставки нового SimpleXMLElement после некоторого другого SimpleXMLElement. Поскольку это непосредственно невозможно с помощью SimpleXML, он использует некоторыеDOM классы/методы за кулисами для выполнения работы.

function simplexml_insert_after(SimpleXMLElement $insert, SimpleXMLElement $target)
{
    $target_dom = dom_import_simplexml($target);
    $insert_dom = $target_dom->ownerDocument->importNode(dom_import_simplexml($insert), true);
    if ($target_dom->nextSibling) {
        return $target_dom->parentNode->insertBefore($insert_dom, $target_dom->nextSibling);
    } else {
        return $target_dom->parentNode->appendChild($insert_dom);
    }
}

И пример того, как он может быть использован (применительно к вашему вопросу):

$sxe = new SimpleXMLElement('<root><nodeA/><nodeA/><nodeA/><nodeC/><nodeC/><nodeC/></root>');
// New element to be inserted
$insert = new SimpleXMLElement("<nodeB/>");
// Get the last nodeA element
$target = current($sxe->xpath('//nodeA[last()]'));
// Insert the new element after the last nodeA
simplexml_insert_after($insert, $target);
// Peek at the new XML
echo $sxe->asXML();

Если вы хотите / нуждаетесь в объяснении , как это работает (код довольно прост, но может включать в себя иностранные понятия), просто спросите.

Ответ салата действительно помог мне, но так как я использовал метод addChild SimpleXMLElement, я искал решение, чтобы сделать вставку детей в качестве первого ребенка более прозрачной. Решение состоит в том, чтобы взять эту функциональность на основе DOM и скрыть ее в подклассе SimpleXMLElement:

class SimpleXMLElementEx extends SimpleXMLElement
{
    public function insertChildFirst($name, $value, $namespace)
    {
        // Convert ourselves to DOM.
        $targetDom = dom_import_simplexml($this);
        // Check for children
        $hasChildren = $targetDom->hasChildNodes();

        // Create the new childnode.
        $newNode = $this->addChild($name, $value, $namespace);

        // Put in the first position.
        if ($hasChildren)
        {
            $newNodeDom = $targetDom->ownerDocument->importNode(dom_import_simplexml($newNode), true);
            $targetDom->insertBefore($newNodeDom, $targetDom->firstChild);
        }

        // Return the new node.
        return $newNode;
    }
}

В конце концов, SimpleXML позволяет указать, какой класс элементов использовать:

$xml = simplexml_load_file($inputFile, 'SimpleXMLElementEx');

Теперь вы можете вызвать insertChildFirst для любого элемента, чтобы вставить новый дочерний элемент в качестве первого дочернего элемента. Метод возвращает новое значение элемент как элемент SimpleXML, поэтому его использование похоже на addChild. Конечно, было бы легко создать метод insertChild, который позволяет указать точный элемент, чтобы вставить элемент раньше, но так как мне это не нужно прямо сейчас, я решил не делать этого.