Consider the following code:
$xml = <<<XML
<root>
<region id='thisRegion'></region>
<region id='thatRegion'></region>
</root>
XML;
$partials['thisRegion'] = "<p>Here's this region</p>";
$partials['thatRegion'] = "<p>Here's that region</p>";
$DOM = new DOMDocument;
$DOM->loadXML($xml);
$regions = $DOM->getElementsByTagname('region');
foreach( $regions as $region )
{
$id = $region->getAttribute('id');
$partial = $DOM->createDocumentFragment();
$partial->appendXML( $partials[$id] );
$region->parentNode->replaceChild($partial, $region);
}
echo $DOM->saveXML();
The output is:
<root>
<p>Here's this region</p>
<region id="thatRegion"/>
</root>
I cannot for the life of me figure out why all of the region tags aren't being replaced. This is a problem in my project, and at first I thought that it wasn't replacing elements I appended after the loadXML, but with some experimenting I haven't been able to narrow down the pattern here.
I would appreciate a code correction to allow me to replace all tags in a DOMDocument with a given Element Node. I also wouldn't mind any input into a more efficient/practical way to execute this if I haven't found it.
Thanks in advance!
[edit] PHP 5.3.13
NodeLists are live.
So when you remove an item inside the document, the NodeList also will be modified. Avoid using a reference to the NodeList and start replacing at the last item:
$DOM = new DOMDocument;
$DOM->loadXML($xml);
$regions = $DOM->getElementsByTagname('region');
$regionsCount = $DOM->getElementsByTagName('region')->length;
for($i= $regionsCount;$i>0;--$i)
{
$region=$DOM->getElementsByTagName('region')->item($i-1);
$id = $region->getAttribute('id');
$partial = $DOM->createDocumentFragment();
$partial->appendXML( $partials[$id] );
$region->parentNode->replaceChild($partial, $region);
}
echo $DOM->saveXML();
?>
http://codepad.org/gTjYC4hr
Related
I used an XMLHttpRequest object to retrieve data from a PHP response.
Then, I created an XML file:
<?xml version="1.0" encoding="UTF-8"?>
<persons>
<person>
<name>Ce</name>
<gender>male</gender>
<age>24</age>
</person>
<person>
<name>Lin</name>
<gender>female</gender>
<age>25</age>
</person>
</persons>
In the PHP file, I load the XML file and try to echo tag values of "name."
$dom = new DOMDocument("1.0");
$dom -> load("test.xml");
$persons = $dom -> getElementsByTagName("person");
foreach($persons as $person){
echo $person -> childNodes -> item(0) -> nodeValue;
}
But the nodeValue returned is null. However, when I change to item(1), the name tag values can be displayed. Why?
Change code to
$dom = new DOMDocument("1.0");
$dom -> load("test.xml");
$persons = $dom -> getElementsByTagName("persons");
foreach($persons as $person){
echo $person->childNodes[1]->nodeValue;
}
Anything in a DOM is a node, include texts and text with only whitespaces. So the first child of the person element node is a text node that contains the linebreak and indent before the name element node.
Here is a property that removes any whitespace node at parse time:
$document = new DOMDocument("1.0");
// do not preserve whitespace only text nodes
$document->preserveWhiteSpace = FALSE;
$document->load("test.xml");
$persons = $document->getElementsByTagName("person");
foreach ($persons as $person) {
echo $person->firstChild->textContent;
}
However typically a better way is to use Xpath expressions.
$document = new DOMDocument("1.0");
$document->load("test.xml");
$xpath = new DOMXpath($document)
$persons = $xpath->evaluate("/persons/person");
foreach ($persons as $person) {
echo $xpath->evaluate("string(name)", $person);
}
string(name) fetches the child element node name (position is not relevant) and casts it into a string. If here is no name element it will return an empty string.
Using DOM you need to get the right element to pick up the name, child nodes include all sorts of things including whitespace. The node 0 your trying to use is null because of this. So for DOM...
$dom = new DOMDocument("1.0");
$dom -> load("test.xml");
$persons = $dom -> getElementsByTagName("person");
foreach($persons as $person){
$name = $person->getElementsByTagName("name");
echo $name->item(0)->nodeValue.PHP_EOL;
}
If your requirements are as simple as this, you could alternatively use SimpleXML...
$sxml = simplexml_load_file("test.xml");
foreach ( $sxml->person as $person ) {
echo $person->name.PHP_EOL;
}
This allows you to access elements as though they are object properties and as you can see ->person equates to accessing <person>.
Hi I looped through an XML Document and searching for specific Word (that works) and after that i would like to a a certain Element if the word is included in that part.
<product>
<title>TestProduct</title>
<Specifications>
<item name="Specifications1">Test</item>
<item name="Specifications2">Hello World</item>
</Specifications>
<body>
<item name="Color">Black</item>
</body>
</product>
I tried this:
<?php
$dom = new DOMDocument;
$dom->load('Test.xml');
# set up the xpath
$xpath = new DOMXPath($dom);
$append= $dom->getElementsByTagName('Text');
print_r($append);
foreach ($xpath->query("*[contains(., 'Test')]") as $item) {
$element = $dom->createElement('ID', '123');
$append->appendChild($element);
}
?>
but it doesn't work can someone give me a hint? Tanks ;)
The problem is that your trying to add the new element into the wrong place. This combines the getElementsByTagName and the XPath into one loop and then adds the new element to any element it finds.
$dom = new DOMDocument;
$dom->load('Test.xml');
$xpath = new DOMXPath($dom);
foreach ($xpath->query("//*[contains(., 'Black')]") as $item) {
$element = $dom->createElement('ID', '123');
$item->appendChild($element);
}
echo $dom->saveXML();
See how the XPath finds any element which contains 'Test'. Then it adds the new element to each element it finds.
The main thing that I have changed is you were adding the ID to $append and not $item.
If you wanted to just update the first instance of a particular piece of text...
$matchList = $xpath->query("//*[contains(text(), 'Black')]");
$element = $dom->createElement('ID', '123');
$matchList->item(0)->appendChild($element);
echo $dom->saveXML().PHP_EOL;
Take the following example of this xml:
<xml>
<siblings>
<brother>Derek</brother>
<sister>Elaine</sister>
<sister>Flora</sister>
</siblings>
<siblings>
<brother>Gary</brother>
<sister>Hannah</sister>
</siblings>
</xml>
If I were to use the following code:
$xmlDoc=new DOMDocument();
$xmlDoc->load("Family.xml");
$siblings = $xmlDoc->getElementsByTagName('Siblings');
$sister = $xmlDoc->getElementsByTagName('Sister');
This would normally return all instances of the tag "Sister", in this case "Elaine", "Flora" and "Hannah". Would it be possible to change it so that you could filter the tagnames by the name of one of the other nodes? For instance, using the name "Derek" to change the output to "Elaine" and "Flora" only.
Xpath expressions allow you to use conditions to fetch nodes from a DOM.
$xml = <<<'XML'
<xml>
<siblings>
<brother>Derek</brother>
<sister>Elaine</sister>
<sister>Flora</sister>
</siblings>
<siblings>
<brother>Gary</brother>
<sister>Hannah</sister>
</siblings>
</xml>
XML;
$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);
$expression = '/xml/siblings[brother = "Derek"]/*[not(self::brother = "Derek")]';
foreach ($xpath->evaluate($expression) as $sibling) {
echo $sibling->textContent, "\n";
}
Output:
Elaine
Flora
The Xpath Expression
Fetch the siblings elements ...
/xml/siblings
... if they have a child element brother with the value Derek ...
/xml/siblings[brother = "Derek"]
... and fetch their child elements...
/xml/siblings[brother = "Derek"]/*
... if they are not a brother element node with the value Derek.
/xml/siblings[brother = "Derek"]/*[not(self::brother = "Derek")]
I have a xml which the format is like this:
<root>
<a>1</a>
<b>2</b>
<c></c>
</root>
This is the code I have tried:
$to = 3;
$dom = new DOMDocument();
$dom->formatOutput = true;
$dom->preserveWhiteSpace = false;
$dom->load("../xxx.xml");
$xpath = new DOMXPath($dom);
$query = "/root/*[position()=$to]";
$nodes = $xpath->query($query);
$node = $nodes[0];
$dom->removeChild($node);
$dom->save("../xxx.xml", LIBXML_NOEMPTYTAG);
How can I delete the tag with name "c" ?
Oh lord, the problem was lie under
$dom->removeChild($node);
should be
$node->parentNode->removeChild($node);
in order to delete a node, you have to get back to the parent node and then it will take the action..I think, this is just my two cents. if someone understand well, feel free to correct me
Trying to get all URLs values from xml.
I have hundreds of entry exactly in the form like e.g. this entry 16:
<?xml version="1.0" encoding="utf-8" ?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<entries>
<entry id="16">
<revision number="1" status="accepted" wordclass="v" nounclasses="" unverified="false"></revision>
<media type="audio" url="http://website.com/file/65.mp3" />
</entry>
<entry id="17">
....
</entry>
</entries>
</root>
I am using this code but cannot get it to work. Why?
$doc = new DOMDocument;
$doc->Load('data.xml');
$xpath = new DOMXPath($doc);
$query = '//root/entries/entry/media';
$entries = $xpath->query($query);
What is the correc query for that? Best would be to only get the url value.
Your query probably returns the proper elements, but by default gives you the content of the media tag ( which in your case are empty, since the tag is self-closing ).
To get the url attribute of the tag you should use getAttribute(), example :
$entries = $xpath->query('//root/entries/entry/media');
foreach($entries as $entry) {
print $entry->getAttribute("url")."<br/>";
}
Or you should just xpath-query the attribute instead and read out it's value:
$urlAttributes = $xpath->query('//root/entries/entry/media/#url');
#####
foreach ($urlAttributes as $urlAttribute)
{
echo $urlAttribute->value, "<br/>\n";
#####
}
See DOMAttr::$valueDocs:
value
The value of the attribute
I would do that with SimpleXML actually:
$file = 'data.xml';
$xpath = '//root/entries/entry/media/#url';
$xml = simplexml_load_file($file);
$urls = array();
if ($xml) {
$urls = array_map('strval', $xml->xpath($xpath));
}
Which will give you all URLs as strings inside the $urls array. If there was an error loading the XML file, the array is empty.