I want to insert a node with children at a specific location in the XML file. How do I do it?
For eg. If I have an XML like:
<myvalues>
<image name="img01">
<src>test</src>
</image>
<image name="img02">
<src>test</src>
</image>
<image name="img03">
<src>test</src>
</image>
</myvalues>
I want to insert:
<image name="img11">
<src>test2</src>
</image>
between <image name="img01">
& <image name="img02">
. How do I do this? I am using SimpleXML right now to read the XML.
Thanks.
EDIT: I tried the following code. But, the new node is added at the bottom of the XML outside the XML structure.
$xml = new DomDocument();
$xml->preserveWhitespace = false;
$xml->load('myXMLFile.xml');
$newNode = $xml->createElement('tryimage');
$xpath = new DOMXpath($xml);
$elements = $xpath->query('/myvalues/image[name="img01"]');
$re开发者_开发问答fNode = $elements->item(0);
$xml->insertBefore($newNode, $refNode->nextSibling);
header('Content-Type: text/plain');
echo $xml->saveXML();
The output is something like this:
<xml....>
<myvalues>
<image name="01">
</image>
.
.
.
</myvalues>
<tryimage />
Well, there's no easy way that I can see with SimpleXML (it's supposed to be simple after all).
One way, would be to move the myvalues node over to DOMDocument, add the node there, then replace it with the dom node. Given that $myvalues
is your <myvalues>
node in SimpleXML:
$domMyValues = dom_import_simplexml($myvalues);
$newNode = $domMyValues->ownerDocument->createElement('mynewelement');
//Apply attributes and whatever to $newNode
//find the node that you want to insert it before (from the $domMyValues class
$nodes = $domMyValues->getElementsByTagName('image');
$refNode = null;
foreach ($nodes as $node) {
if ($node->getAttribute('name') == 'img02') {
$refNode = $node;
}
}
$domMyValues->insertBefore($newNode, $refNode);
Note, there's no need to convert back to SimpleXML, since any changes to the DOMElement will be applied automatically to the SimpleXML version... It will automatically append the new child if it can't find the $refNode
(because it doesn't exist, etc)...
EDIT: Adding XPath
Replace the foreach block with this (Functionally equivalent, if I got the query right):
$xpath = new DOMXpath($domMyValues->ownerDocument);
$elements = $xpath->query('//image[@name="img02"]');
$refNode = $elements->item(0);
Since DOMNodeList::item()
returns null for a non-existent offset, we don't even need to check to see if there are items in it.
Now, you may need/want to adjust the xpath query to be more/less specific. Here's a decent tutorial...
Edit 2
I forgot that xpath needed an @
character to tell it to check an attribute.
Here's my working code (since I don't know your exact schema):
$x = '<?xml version="1.0" ?>
<myvalues>
<images>
<image name="01">Foo</image>
<image name="02">Bar</image>
</images>
</myvalues>';
$dom = new DomDocument();
$dom->loadXML($x);
$xpath = new DOMXpath($dom);
$elements = $xpath->query('//images/image[@name="01"]');
$elements = $xpath->query('//image[@name="01"]');
$elements = $xpath->query('/myvalues/images/image[@name="01"]');
You can, as mentioned in ircmaxel's answer make use of the DOM classes/methods to do what you want. Below is a concise (probably less code than you would really want to use) example of inserting a SimpleXMLElement after another one.
function insertAfter(SimpleXMLElement $new, SimpleXMLElement $target) {
$target = dom_import_simplexml($target);
$new = $target->ownerDocument->importNode(dom_import_simplexml($new), true);
if ($target->nextSibling) {
$target->parentNode->insertBefore($new, $target->nextSibling);
} else {
$target->parentNode->appendChild($new);
}
}
$sxe = new SimpleXMLElement($xml);
$img = $sxe->xpath('//image[@name="img01"]'); // xpath returns an array
if (!empty($img)) {
$new = new SimpleXMLElement('<image name="img11" foo="bar"><src>test</src></image>');
insertAfter($new, $img[0]);
}
foreach ($sxe as $image) {
echo $image['name'] . PHP_EOL;
}
/*
img01
img11
img02
img03
*/
精彩评论