1

I have an XML which is a catalog containing several range nodes which in turn contain several item nodes:

<cat>
    <range>
        <item>
            <a></a>
            <b></b>
            <c></c>
        </item>
        <item>
            <a></a>
            <b></b>
            <c></c>
        </item>
        <item>
            <a></a>
            <b></b>
            <c></c>
        </item> 
        <item>
            <a></a>
            <b></b>
            <c></c>
        </item>
        <!-- node 'a' need to be added here -->
        <b></b>
        <d></d>
    </range>
    <range>
        <item>
            <a></a>
            <b></b>
            <c></c>
        </item>
        <item>
            <a></a>
            <b></b>
            <c></c>
        </item>
        <!-- node 'a' need to be added here -->
        <b></b>
        <d></d>
    </range>    
</cat>

Now I need to add node a inside each range just after ALL item nodes and just before node b.

NOTE: nodes a & b inside and outside item is the same

I was able to add the b node inside range, like this:

foreach($dom->getElementsByTagname('range') as $range) {
    forearch ($range->getElementsByTagName('d') as $d) {
        $b = $dom->createElement('b');
        $d = $d->parentNode->insertBefore($b, $d);
    }
}

If I use the same code to try and add node a outside all the item nodes, it won't work since the reference node b is the same inside and outside the item nodes; so it will just add node a inside the item nodes.

I found an insertAfter function;

function insertAfter(\DOMNode $newNode, \DOMNode $referenceNode)
{
  if($referenceNode->nextSibling === null) {
      return $referenceNode->parentNode->appendChild($newNode);
  } else {
      return $referenceNode->parentNode->insertBefore($newNode, $referenceNode->nextSibling);
  }
}

but it will not give me the desired result since if I used item as the reference node, it will add the new node just after the first item whereas I need it after all the item nodes.

2 Answers 2

2

The easiest way to achieve what you are trying to do is by using xPath.

$xpath = new DOMXpath($dom);
$bs = $xpath->query("//range/b");
$a = new DOMElement('a');

foreach ($bs as $b) {
    $b->parentNode->insertBefore($a, $b);
}

This gets alls the "b" that are a direct child of range but not its grandchildren.

Sign up to request clarification or add additional context in comments.

Comments

0

Getting the first level children compare until find the one you want ($b) and insert before it:

foreach($dom->getElementsByTagname('range') as $range) {
    foreach($range->childNodes as $first_level_child) {
        if($first_level_child->tagname=='b') {
            $a = $dom->createElement('a');
            $first_level_child->parentNode->insertBefore($a, $first_level_child);
        }
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.