So i have an Xml file like
<cars>
<id>1</id>
<photos>
<img>http://sit.com/img.jpg</img>
<img>http://sit.com/img.jpg</img>
<img>http://sit.com/img.jpg</img>
<img>http://sit.com/img.jpg</img>
</photos>
</cars>
So i need to change all tag name to alternative and i need get something like
<cars>
<ex_id>1</ex_id>
<images>
<photo>http://sit.com/img.jpg</photo>
<photo>http://sit.com/img.jpg</photo>
<photo>http://sit.com/img.jpg</photo>
<photo>http://sit.com/img.jpg</photo>
</images>
</cars>
My code is
foreach ($dom->getElementsByTagName('cars') as $item) {
for ($i = 0; $i < $item->childNodes->length; ++$i) {
$car = $item->childNodes->item($i);
$NewElement = $dom->createElement($newName,$value);
$car->parentNode->replaceChild($NewElement->cloneNode(TRUE), $car);
}
}
Do something like that
<cars>
<ex_id>1</ex_id>
<images/>
</cars>
So it cut all childrens of <photos>, so my question is how to preserve children and also change childrens tags from <img> to <photo>
Here are several issues:
getElementByTagName() and $childNodes return 'live' lists, they change if you change the DOM. You can use iterator_to_array() to copy them into an array.
Here are not only element nodes. Comments, cdata sections and text (even containing only whitespaces) are nodes, too. If you iterate $childNodes you will have to validate the DOMNode::$nodeType.
Do not use the second argument of DOMDocument::createElement(). It has a broken escaping. Create a text node and append it.
1 and 2 go away if you use Xpath to fetch the nodes.
$dom = new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXPath($dom);
foreach ($xpath->evaluate('/cars/images/img') as $photo) {
$newNode = $dom->createElement('photo');
$newNode->appendChild($dom->createTextNode($photo->textContent));
$photo->parentNode->replaceChild($newNode, $photo);
}
echo $dom->saveXml();
Output:
<?xml version="1.0"?>
<cars>
<ex_id>1</ex_id>
<images>
<photo>http://sit.com/img.jpg</photo>
<photo>http://sit.com/img.jpg</photo>
<photo>http://sit.com/img.jpg</photo>
<photo>http://sit.com/img.jpg</photo>
</images>
</cars>
Changing an DOM document is often a bad idea. It is easier to extract data from a source document and build a new target document:
$source = new DOMDocument();
$source->loadXml($xml);
$xpath = new DOMXPath($source);
$target = new DOMDocument();
$target->formatOutput = TRUE;
$cars = $target->appendChild($target->createElement('cars'));
$cars
->appendChild($target->createElement('ex_id'))
->appendChild(
$target->createTextNode(
$xpath->evaluate('string(/cars/id)')
)
);
$images = $cars->appendChild($target->createElement('images'));
foreach ($xpath->evaluate('/cars/photos/img') as $photo) {
$images
->appendChild($target->createElement('photo'))
->appendChild($target->createTextNode($photo->textContent));
}
echo $target->saveXml();
Output:
<?xml version="1.0"?>
<cars>
<ex_id>1</ex_id>
<images>
<photo>http://sit.com/img.jpg</photo>
<photo>http://sit.com/img.jpg</photo>
<photo>http://sit.com/img.jpg</photo>
<photo>http://sit.com/img.jpg</photo>
</images>
</cars>
Here is a language dedicated to transforming XML - XSLT. XSLT is supported by PHPs ext/xsl.
Related
I have two xml files with a similar structure, and I need to merge them based on a common attribute. To be more explicit, here are two samples:
file1.xml
<Products>
<record ProductId="366" ProductName="Test" ProductCategory="Categ1"></record>
</Products>
file2.xml
<Productprices>
<record ProductId="366" ProductPrice="10" ProductVAT="24"></record>
</Productprices>
The common attribute in both files is ProductId. I need to merge all the attributes so that the combined file would look like this:
<Products>
<record ProductId="366" ProductName="Test" ProductCategory="Categ1" ProductPrice="10" ProductVAT="24"></record>
</Products>
Unfortunately, all I have managed to do so far is simply merge the two files, the merged file looks like this:
<Products>
<record ProductId="366" ProductName="Test" ProductCategory="Categ1"></record>
<record ProductId="366" ProductPrice="10" ProductVAT="24"></record>
</Products>
This is the PHP code I used:
$doc1 = new DOMDocument();
$doc1->load('file1.xml');
$doc2 = new DOMDocument();
$doc2->load('file2.xml');
$res1 = $doc1->getElementsByTagName('Products')->item(0);
$items2 = $doc2->getElementsByTagName('record');
for ($i = 0; $i < $items2->length; $i ++) {
$item2 = $items2->item($i);
$item1 = $doc1->importNode($item2, true);
$res1->appendChild($item1);
}
$doc1->save('file1.xml');
Is there any way I can merge all the attributes into one record based on the common ProductId by using DomDocument? I would rather not go into XSLT.
Any help will be greatly appreciated.
Thanks in advance.
I use Xpath to fetch nodes and values from DOM. In your case I see two tasks.
One task to iterate all record elements in on document, fetch the attributes of the matching element from the second document and copy the attributes.
The other task to iterate all record elements in the second document and add them to the first if here is no element with that ProductId.
$xmlOne = <<<'XML'
<Products>
<record ProductId="366" ProductName="Test" ProductCategory="Categ1"></record>
</Products>
XML;
$xmlTwo = <<<'XML'
<Productprices>
<record ProductId="366" ProductPrice="10" ProductVAT="24"></record>
<record ProductId="444" ProductPrice="23" ProductVAT="32"></record>
</Productprices>
XML;
$targetDom = new DOMDocument();
$targetDom->loadXml($xmlOne);
$targetXpath = new DOMXpath($targetDom);
$addDom = new DOMDocument();
$addDom->loadXml($xmlTwo);
$addXpath = new DOMXpath($addDom);
// merge attributes of record elements depending on ProductId
foreach ($targetXpath->evaluate('//record[#ProductId]') as $record) {
$productId = $record->getAttribute('ProductId');
foreach ($addXpath->evaluate('//record[#ProductId='.$productId.']/#*') as $attribute) {
if (!$record->hasAttribute($attribute->name)) {
$record->setAttribute($attribute->name, $attribute->value);
}
}
}
// copy records elements that are not in target dom
foreach ($addXpath->evaluate('//record[#ProductId]') as $record) {
$productId = $record->getAttribute('ProductId');
if ($targetXpath->evaluate('count(//record[#ProductId='.$productId.'])') == 0) {
$targetDom->documentElement->appendChild(
$targetDom->importNode($record)
);
}
}
echo $targetDom->saveXml();
You can use the attribute() function of SimpleXML
$xml = simplexml_load_file($filename);
foreach($xml->Products->record->attributes() as $attribute => $value) {
//do something
}
I have actually asked this before, but alas the PC got nicked that I had the solution on, and I no longer can get the previous solution to work.
I'm trying to add a new element to the XML below:
<?xml version="1.0" encoding="ISO-8859-1"?>
<data>
<comments>
<comment>
<date>20120509</date>
<time>10:21:05</time>
<name>Lucy</name>
<text>Hello etc</text>
</comment>
<comment> ...etc
The PHP code I'm using is:
$xml = new DOMDocument('1.0', 'utf-8');
$xml->load(filename.xml);
$parent = $xml->firstChild;
$refnode = $parent->firstChild;
$new = $parent->insertBefore($xml->createElement('comment'), $refnode);
However, this adds a new "comment" immediately after the "data" tag, and if I try to add children (such as "date", "time" etc...) with $new->addChild(tag, value), I get an "undefined method" error. I've tried all manner of permutations, but nothing works.
desired result would be:
<?xml version="1.0" encoding="ISO-8859-1"?>
<data>
<comments>
*<comment>
<date>20140225</date>
<time>17:39:05</time>
<name>Derek</name>
<text>New comment text</text>
</comment>*
<comment>
<date>20120509</date>
<time>10:21:05</time>
<name>Lucy</name>
<text>Hello etc</text>
</comment>
<comment> ...etc
Your XML file most likely contained the whitespace characters like in your example. These are interpreted as text nodes, which renders firstChild useless to obtain one of those elements you want.
You have to iterate over the children instead and get the first one, which is actually a DOMElement. Also you had to go one level deeper than you did. See appended sourcecode which outputs the result you want.
<?php
$xml = new DOMDocument();
$xml->loadXML('<?xml version="1.0" encoding="UTF-8"?>
<data>
<comments>
<comment>
<date>20120509</date>
<time>10:21:05</time>
<name>Lucy</name>
<text>Hello etc</text>
</comment>
</comments>
</data>');
$parent = $xml->firstChild;
foreach ($parent->childNodes as $c) {
if ($c instanceof DOMElement) {
$refnode = $c;
break;
}
}
foreach ($refnode->childNodes as $c) {
if ($c instanceof DOMElement) {
$refnode2 = $c;
break;
}
}
$insert = $xml->createElement('comment', 'test');
$refnode->insertBefore($insert, $refnode2);
echo $xml->saveHTML();
Removed ALL junk nodes in xml using php
This is the sample input for the example:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<!--comment1-->
<elem>a</elem>
<junk>b</junk>
<elem>
<!--comment2-->
<junk>c<junk>d</junk></junk>
</elem>
<!--comment3-->
<junk>e</junk>
</root>
This is the resulting XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<elem>a</elem>
<elem>
</elem>
</root>
I look documents and applied like this:
$doc = new DOMDocument();
$doc->loadXML($xml);
$xpath = new DOMXPath($doc);
foreach ($xpath->query('/root/') as $elem) {
$elem->parentNode->removeChild($elem);
}
for my case will remove all node "junk" and they will everywhere in xml document.
foreach ($xpath->query('//junk') as $elem)
quick question: I need to transform a default RSS Structure into another XML-format.
The RSS File is like....
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>Name des RSS Feed</title>
<description>Feed Beschreibung</description>
<language>de</language>
<link>http://xml-rss.de</link>
<lastBuildDate>Sat, 1 Jan 2000 00:00:00 GMT</lastBuildDate>
<item>
<title>Titel der Nachricht</title>
<description>Die Nachricht an sich</description>
<link>http://xml-rss.de/link-zur-nachricht.htm</link>
<pubDate>Sat, 1. Jan 2000 00:00:00 GMT</pubDate>
<guid>01012000-000000</guid>
</item>
<item>
<title>Titel der Nachricht</title>
<description>Die Nachricht an sich</description>
<link>http://xml-rss.de/link-zur-nachricht.htm</link>
<pubDate>Sat, 1. Jan 2000 00:00:00 GMT</pubDate>
<guid>01012000-000000</guid>
</item>
<item>
<title>Titel der Nachricht</title>
<description>Die Nachricht an sich</description>
<link>http://xml-rss.de/link-zur-nachricht.htm</link>
<pubDate>Sat, 1. Jan 2000 00:00:00 GMT</pubDate>
<guid>01012000-000000</guid>
</item>
</channel>
</rss>
...and I want to extract only the item-elements (with childs and attributes) XML like:
<?xml version="1.0" encoding="ISO-8859-1"?>
<item>
<title>Titel der Nachricht</title>
<description>Die Nachricht an sich</description>
<link>http://xml-rss.de/link-zur-nachricht.htm</link>
<pubDate>Sat, 1. Jan 2000 00:00:00 GMT</pubDate>
<guid>01012000-000000</guid>
</item>
...
It hasn't to be stored into a file. I need just the output.
edit: Furthermore you need to know: The RSS File could have dynamic numbers of items. This is just a sample. So it has to be looped with while, for, for-each, ...
I tried different approaches with DOMNode, SimpleXML, XPath, ... but without success.
Thanks
chris
A different approach would be to use an XSLT:
$xsl = <<< XSL
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<items>
<xsl:copy-of select="//item">
<xsl:apply-templates/>
</xsl:copy-of>
</items>
</xsl:template>
</xsl:stylesheet>
XSL;
The above stylesheet has just one rule, namely deep copying all <item> elements from the source XML to an XML file and ignore everything else from the source file. The nodes will be copied into an <items> element for root node. To process this, you'd do
$xslDoc = new DOMDocument(); // create Doc for XSLT
$xslDoc->loadXML($xsl); // load stylesheet into it
$xmlDoc = new DOMDocument(); // create Doc for RSS
$xmlDoc->loadXML($xml); // load your XML/RSS into it
$proc = new XSLTProcessor(); // init XSLT engine
$proc->importStylesheet($xslDoc); // load stylesheet into engine
echo $proc->transformToXML($xmlDoc); // output transformed XML
Instead of outputting, you could just write the return value to file.
Further reading:
http://de3.php.net/manual/en/class.xsltprocessor.php
http://www.w3.org/TR/xslt#copy-of
What you ask for is hardly a transformation. You are basically just extracting the <item> elements as they are. Also, the result you give is not valid XML, as it lacks a root node.
Apart from that, you can simple do it like this:
$dom = new DOMDocument; // init new DOMDocument
$dom->loadXML($xml); // load some XML into it
$xpath = new DOMXPath($dom); // create a new XPath
$nodes = $xpath->query('//item'); // Find all item elements
foreach($nodes as $node) { // Iterate over found item elements
echo $dom->saveXml($node); // output the item node outerHTML
}
The above would echo the <item> nodes. You could simply buffer the output, concatenate it to a string, write to it an array and implode, etc - and write it to file.
If you want to do it properly with DOM (and a root node), the full code would be:
$dom = new DOMDocument; // init DOMDocument for RSS
$dom->loadXML($xml); // load some XML into it
$items = new DOMDocument; // init DOMDocument for new file
$items->preserveWhiteSpace = FALSE; // dump whitespace
$items->formatOutput = TRUE; // make output pretty
$items->loadXML('<items/>'); // create root node
$xpath = new DOMXPath($dom); // create a new XPath
$nodes = $xpath->query('//item'); // Find all item elements
foreach($nodes as $node) { // iterate over found item nodes
$copy = $items->importNode($node, TRUE); // deep copy of item node
$items->documentElement->appendChild($copy); // append item nodes
}
echo $items->saveXML(); // outputs the new document
Instead of saveXML(), you'd use save('filename.xml') to write it to a file.
Try:
<?php
$xmlFile = new DOMDocument(); //Instantiate new DOMDocument
$xmlFile->load("URL TO RSS/XML FILE"); //Load in XML/RSS file
$xmlString = file_get_contents("URL TO RSS/XML FILE");
$title[] = "";
$description[] = "";
$link[] = "";
$pubDate[] = "";
$guid[] = "";
for($i = 0; $i < substr_count($xmlString, "<item>"); $i++)
{
$title[] = $xmlFile->getElementsByTagName("title")->item(0)->nodeValue; //Get the value of the node <title>
$description[] = $xmlFile->getElementsByTagName("description")->item(0)->nodeValue;
$link[] = $xmlFile->getElementsByTagName("link")->item(0)->nodeValue;
$pubDate[] = $xmlFile->getElementsByTagName("pubDate")->item(0)->nodeValue;
$guid[] = $xmlFile->getElementsByTagName("guid")->item(0)->nodeValue;
}
?>
Untested but the arrays
$title[]
$description[]
$link[]
$pubDate[]
$guid[]
should be populated with all of the data that you need!
EDIT:
OK so another approach:
<?php
$xmlString = file_get_contents("URL TO RSS/XML FILE");
$titles = preg_filter("/<title>([.]*)</title>/","\\1", mixed $xmlString);
$descriptions = preg_filter("/<description>([.]*)</description>/","\\1", mixed $xmlString);
$links = preg_filter("/<link>([.]*)</link>/","\\1", mixed $xmlString);
$pubDates = preg_filter("/<pubDate>([.]*)</pubDate>/","\\1", mixed $xmlString);
$guids = preg_filter("/<guid>([.]*)</guid>/","\\1", mixed $xmlString);
?>
In this example each variable will be filled with the correct values.
Using PHP, how do I get an entire subset of nodes from an XML document? I can retrieve something like:
<?xml version="1.0" encoding="utf-8"?>
<people>
<certain>
<name>Jane Doe</name>
<age>21</age>
</certain>
<certain>
<certain>
<name>John Smith</name>
<age>34</age>
</certain>
</people>
But what if I only want to return the child nodes of like this?
<certain>
<name>Jane Doe</name>
<age>21</age>
</certain>
<certain>
<certain>
<name>John Smith</name>
<age>34</age>
</certain>
EDIT: I'm trying to get a subset of XML and pass that directly, not an object like simplexml would give me. I am basically trying to get PHP to do what .NET's OuterXml does... return literally the above subset of XML as is... no interpreting or converting or creating a new XML file or anything... just extract those nodes in situ and pass them on. Am I going to have to get the XML file, parse out what I need and then rebuild it as a new XML file? If so then I need to get rid of the <?xml version="1.0" encoding="utf-8"?> bit... ugh.
The answer would be to use XPath.
$people = simplexml_load_string(
'<?xml version="1.0" encoding="utf-8"?>
<people>
<certain>
<name>Jane Doe</name>
<age>21</age>
</certain>
<certain>
<name>John Smith</name>
<age>34</age>
</certain>
</people>'
);
// get all <certain/> nodes
$people->xpath('//certain');
// get all <certain/> nodes whose <name/> is "John Smith"
print_r($people->xpath('//certain[name = "John Smith"]'));
// get all <certain/> nodes whose <age/> child's value is greater than 21
print_r($people->xpath('//certain[age > 21]'));
Take 2
So apparently you want to copy some nodes from a document into another document? SimpleXML doesn't support that. DOM has methods for that but they're kind of annoying to use. Which one are you using? Here's what I use: SimpleDOM. In fact, it's really SimpleXML augmented with DOM's methods.
include 'SimpleDOM.php';
$results = simpledom_load_string('<results/>');
foreach ($people->xpath('//certain') as $certain)
{
$results->appendChild($certain);
}
That routine finds all <certain/> node via XPath, then appends them to the new document.
You could use DOMDocument.GetElementsByTagName or you could:
Use XPath?
<?php
$xml = simplexml_load_file("test.xml");
$result = $xml->xpath("//certain");
print_r($result);
?>
Use DOM and XPath. Xpath allows you to select nodes (and values) from an XML DOM.
$dom = new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXpath($dom);
$result = '';
foreach ($xpath->evaluate('/people/certain') as $node) {
$result .= $dom->saveXml($node);
}
echo $result;
Demo: https://eval.in/162149
DOMDocument::saveXml() has a context argument. If provided it saves that node as XML. Much like outerXml(). PHP is able to register your own classes for the DOM nodes, too. So it is even possible to add an outerXML() function to element nodes.
class MyDomElement extends DOMElement {
public function outerXml() {
return $this->ownerDocument->saveXml($this);
}
}
class MyDomDocument extends DOMDocument {
public function __construct($version = '1.0', $encoding = 'utf-8') {
parent::__construct($version, $encoding);
$this->registerNodeClass('DOMElement', 'MyDomElement');
}
}
$dom = new MyDomDocument();
$dom->loadXml($xml);
$xpath = new DOMXpath($dom);
$result = '';
foreach ($xpath->evaluate('/people/certain') as $node) {
$result .= $node->outerXml();
}
echo $result;
Demo: https://eval.in/162157
See http://www.php.net/manual/en/domdocument.getelementsbytagname.php
The answer turned out to be a combination of the xpath suggestion and outputting with asXML().
Using the example given by Josh Davis:
$people = simplexml_load_string(
<?xml version="1.0" encoding="utf-8"?>
<people>
<certain>
<name>Jane Doe</name>
<age>21</age>
</certain>
<certain>
<name>John Smith</name>
<age>34</age>
</certain>
</people>'
);
// get all <certain/> nodes
$nodes = $people->xpath('/people/certain');
foreach ( $nodes as $node ) {
$result .= $node->asXML()."\n";
}
echo $result;