Find and fill XML nodes from a PHP object - php

First of all, sorry about my english.
So, I have an XML with a lot of nodes, like:
<first>
<second>
<third/>
<fourth/>
</second>
<fifth>
<sixth>
<seventh/>
</sixth>
</fifth>
</first>
and I have an object, like:
Object{
third: "asd",
fourth: "asdasd",
seventh: "asdasdasd"
}
How can I run through all the nodes of the XML, regardless of their parents, and fill every one of them with the values of the existing properties from the object?

Here's an approach using the SimpleXML:
<?php
$object = (object) array(
'third' => 'asd',
'fourth' => 'asdasd',
'seventh' => 'asdasdasd'
);
$xml = <<<XML
<first>
<second>
<third />
<fourth />
</second>
<fifth>
<sixth>
<seventh />
</sixth>
</fifth>
</first>
XML;
$sxe = new SimpleXMLElement($xml);
foreach ($object as $key => $value) {
$node = $sxe->xpath("//*[./{$key}]");
$node[0]->{$key} = $value;
}
echo $sxe->asXML();
Output:
<?xml version="1.0"?>
<first>
<second>
<third>asd</third>
<fourth>asdasd</fourth>
</second>
<fifth>
<sixth>
<seventh>asdasdasd</seventh>
</sixth>
</fifth>
</first>

Related

how to correctly append xml child elements in php

I'm trying to iterate over an array of movie names and am struggling as shown below. I've included the actual output vs expected output. thanks!
$root = new SimpleXMLElement('<movies/>');
$movies = ['foo', 'bar'];
foreach($movies as $movie_name){
$movie_el = buildMovieTag($movie_name);
$root->addChild($movie_el->asXML());
}
function buildMovieTag($name){
$movie_tag = new SimpleXMLElement('<movie/>');
$movie_tag->addChild('name', $name);
return $movie_tag;
}
I'm expecting this:
<?xml version="1.0"?>
<movies>
<movie><name>foo</name></movie>
<movie><name>bar</name></movie>
</movies>
but am getting this:
<?xml version="1.0"?>
<movies><<?xml version="1.0"?> // extra xml tag
<movie><name>foo</name></movie>
/><<?xml version="1.0"?> // extra xml tag
<movie><name>bar</name></movie>
/></movies>
The new SimpleXMLElement then asXML() is the issue.
addChild is enough to make the element then its return value is the node, then subsequent addChild's on the node make the children.
<?php
$root = new SimpleXMLElement('<movies/>');
$movies = ['foo', 'bar'];
foreach($movies as $movie_name) {
$movie = $root->addChild('movie');
buildMovieTag($movie, $movie_name);
}
function buildMovieTag($node, $name) {
$node->addChild('name', $name);
}
echo $root->saveXML();
<?xml version="1.0"?>
<movies><movie><name>foo</name></movie><movie><name>bar</name></movie></movies>
Edit: if your data is more extensive, structure it so its standard key/value array then loop over the items. The actual issue to the original question is noted on the first line of the answer.
<?php
$root = new SimpleXMLElement('<movies/>');
$movies = [
['name'=>'foo', 'released' => '2020-12-01 00:00:00', 'plot' => 'foo is a foofoo'],
['name'=>'bar', 'released' => '2020-12-02 00:00:00', 'plot' => 'bar is a barbar'],
];
foreach ($movies as $movie) {
buildMovieTag($root->addChild('movie'), $movie);
}
function buildMovieTag($node, $data) {
foreach ($data as $key => $item) {
// just year from datetime string
if ($key === 'released') $item = date_create($item)->format('Y');
$node->addChild($key, $item);
}
}
echo $root->saveXML();
Result (manual formatted)
<?xml version="1.0"?>
<movies>
<movie>
<name>foo</name>
<released>2020</released>
<plot>foo is a foofoo</plot>
</movie>
<movie>
<name>bar</name>
<released>2020</released>
<plot>bar is a barbar</plot>
</movie>
</movies>

PHP DOM XML to arrray conversion

I have an xml document below:
<?xml version="1.0" encoding="UTF-8" ?>
<books>
<book>
<name>Title One</name>
<year>2014</year>
<authors>
<author>
<name>Author One</name>
</author>
</authors>
</book>
<book serie="yes">
<name>Title Two</name>
<year>2015</year>
<authors>
<author>
<name>Author two</name>
</author>
<author>
<name>Author three</name>
</author>
</authors>
</book>
<book serie="no">
<name>Title Three</name>
<year>2015</year>
<authors>
<author>
<name>Author four</name>
</author>
</authors>
</book>
</books>
I want to convert it into an array bellow.
array(
array('Tittle one', 2014, 'Author One'),
array('Tittle two', 2015, 'Author two, Author three'),
array('Tittle three', 2015, 'Author four'),
);
My code below is failing to produce the array structure that I would want:
function arrayRepresentation(){
$xmldoc = new DOMDocument();
$xmldoc->load("data/data.xml");
$parentArray = array();
foreach ($xmldoc->getElementsByTagName('book') as $item) {
$parentArray[] = array_generate($item);
}
var_dump($parentArray);
}
function array_generate($item){
$movieArray = array();
$childMovieArray = array();
for ($i = 0; $i < $item->childNodes->length; ++$i) {
$child = $item->childNodes->item($i);
if ($child->nodeType == XML_ELEMENT_NODE) {
if(hasChild($child)){
$childMovieArray = array_generate($child);
}
}
$movieArray[] = trim($child->nodeValue);
}
if(!empty($childMovieArray)){
$movieArray = array_merge($movieArray,$childMovieArray);
}
return $movieArray;
}
function hasChild($p)
{
if ($p->hasChildNodes()) {
foreach ($p->childNodes as $c) {
if ($c->nodeType == XML_ELEMENT_NODE)
return true;
}
}
}
arrayRepresentation();
Basically I am looping through the the nodes to get xml element values. I am then checking if I Node has more child nodes and if it has I loop through it again to get the values. I am failling to deduce a way that: (i) will not give me some empty array elements (ii) Check for any child nodes and put all the xml sibling elements in a single string
PHPs DOM supports Xpath expressions for fetching specific nodes and values. That drastically reduces the amount of loops and conditions you will need.
Here is a demo:
// bootstrap the XML document
$document = new DOMDocument();
$document->loadXML($xml);
$xpath = new DOMXpath($document);
$data = [];
// iterate the node element nodes
foreach ($xpath->evaluate('/books/book') as $book) {
$authors = array_map(
fn ($node) => $node->textContent,
// fetch author name nodes as an array
iterator_to_array($xpath->evaluate('authors/author/name', $book))
);
$data[] = [
// cast first name element child to string
$xpath->evaluate('string(name)', $book),
// cast first year element child to string
$xpath->evaluate('string(year)', $book),
implode(', ', $authors)
];
}
var_dump($data);

How to add spaces between values in xml?

I have to create an xml out of other xml. I already have the answer to that, but then I'm facing another problem. The output of the xml is quite messy in the tag.
The xml is this:
<rss>
<item id="12907">
<g:productname>Black Bag</g:productname>
<g:detailed_images>
<g:detailed_image>Image1.jpg</g:detailed_image>
<g:detailed_image>Image2.jpg</g:detailed_image>
<g:detailed_image>Image3.jpg</g:detailed_image>
<g:detailed_image>Image4.jpg</g:detailed_image>
<g:detailed_image>Image5.jpg</g:detailed_image>
<g:detailed_image>Image6.jpg</g:detailed_image>
<g:detailed_image>Image7.jpg</g:detailed_image>
<g:detailed_image>Image8.jpg</g:detailed_image>
<g:detailed_image>Image9.jpg</g:detailed_image>
<g:detailed_image>Image10.jpg</g:detailed_image>
<g:detailed_image>Image11.jpg</g:detailed_image>
<g:detailed_image>Image12.jpg</g:detailed_image>
</g:detailed_images>
</item>
<item id="12906">
<g:productname>Yellow Bag</g:productname>
<g:detailed_images>
<g:detailed_image>Image1.jpg</g:detailed_image>
<g:detailed_image>Image2.jpg</g:detailed_image>
<g:detailed_image>Image3.jpg</g:detailed_image>
<g:detailed_image>Image4.jpg</g:detailed_image>
<g:detailed_image>Image5.jpg</g:detailed_image>
<g:detailed_image>Image6.jpg</g:detailed_image>
<g:detailed_image>Image7.jpg</g:detailed_image>
<g:detailed_image>Image8.jpg</g:detailed_image>
<g:detailed_image>Image9.jpg</g:detailed_image>
<g:detailed_image>Image10.jpg</g:detailed_image>
<g:detailed_image>Image11.jpg</g:detailed_image>
<g:detailed_image>Image12.jpg</g:detailed_image>
</g:detailed_images>
</item>
</rss>
The php code that I'm using to create another xml file is this
<?php
$document = new DOMDocument;
$document->formatOutput = true;
$document->preserveWhiteSpace = false;
$document->load('xml_feeds.xml');
$xpath = new DOMXPath($document);
$fields = [
'productname' => 'string(g:productname)',
'detailed_images' => 'string(g:detailed_images)'
];
$xml = new DOMDocument;
$xml->formatOutput = true;
$xml->preserveWhiteSpace = false;
$rss = $xml->appendChild($xml->createElement('rss'));
foreach ($xpath->evaluate('//item') as $item) {
//create tag item
$createItem = $rss->appendChild($xml->createElement('item'));
//getting item's attribute value
$valueID = $item->getAttribute('id');
//create attribute
$itemAttribute = $xml->createAttribute('id');
$itemAttribute->value = $valueID;
$createItem->appendChild($itemAttribute);
foreach ($fields as $caption => $expression) {
$value = $xpath->evaluate($expression, $item);
$createItem->appendChild($xml->createElement($caption, $value));
}
}
$xml->save('new_createxml2.xml');
?>
The result of the new_createxml2.xml is this
<?xml version="1.0"?>
<rss>
<item id="12907">
<productname>Black Bag</productname>
<detailed_images>Image1.jpgImage2.jpgImage3.jpgImage4.jpgImage3.jpgImage4.jpgImage5.jpgImage6.jpgImage7.jpgImage8.jpgImage9.jpgImage10.jpgImage11.jpgImage12.jpg</detailed_images>
</item>
<item id="12906">
<productname>Yellow Bag</productname>
<detailed_images>Image1.jpgImage2.jpgImage3.jpgImage4.jpgImage3.jpgImage4.jpgImage5.jpgImage6.jpgImage7.jpgImage8.jpgImage9.jpgImage10.jpgImage11.jpgImage12.jpg</detailed_images>
</item>
</rss>
I really wonder how can I create the xml tidier than what I've made. I want it to display like this actually:
<?xml version="1.0"?>
<rss>
<item id="12907">
<productname>Black Bag</productname>
<detailed_images>Image1.jpg, Image2.jpg, Image3.jpg, Image4.jpg, Image3.jpg, Image4.jpg, Image5.jpg, Image6.jpg, Image7.jpg, Image8.jpg, Image9.jpg, Image10.jpg, Image11.jpg, Image12.jpg</detailed_images>
</item>
<item id="12906">
<productname>Yellow Bag</productname>
<detailed_images>Image1.jpg, Image2.jpg, Image3.jpg, Image4.jpg, Image3.jpg, Image4.jpg, Image5.jpg, Image6.jpg, Image7.jpg, Image8.jpg, Image9.jpg, Image10.jpg, Image11.jpg, Image12.jpg</detailed_images>
</item>
</rss>
Thank you for your help
The way you've tried it is quite simple for some things, but as you've found out that this method fails when you want processing for each individual piece of data. When you use 'string(g:detailed_images)' - this is the text content of all of the subnodes in your document, which is why you have all of the values stuck together. You could then process this string with some form of regular expression - but as you have no control of the content it's difficult to know what you are going to find.
Changing it to a more traditional - get values using a specific XPath call and processing the values allows you more control over the result.
foreach ($xpath->evaluate('//item') as $item) {
//create tag item
$createItem = $rss->appendChild($xml->createElement('item'));
//getting item's attribute value
$valueID = $item->getAttribute('id');
//create attribute
$itemAttribute = $xml->createAttribute('id');
$itemAttribute->value = $valueID;
$createItem->appendChild($itemAttribute);
$prodName = $xpath->evaluate("string(g:productname)", $item);
$createItem->appendChild($xml->createElement('productname', $prodName));
$images = [];
foreach ( $xpath->query("descendant::g:detailed_image", $item) as $image ) {
$images[] = $image->nodeValue;
}
$createItem->appendChild($xml->createElement('detailed_images',
implode(",", $images)));
}

CodeIgniter - Process Simple XML & PHP

in the controller I have _send method. This method returns something like below:
$xmlstr = <<<XML
<?xml version='1.0' standalone='yes' ?>
<status id="555555555" date="Wed, 28 Mar 2013 12:35:00 +0300">
<id>3806712345671174984921381</id>
<id>3806712345671174984921382</id>
<id>3806712345671174984921383</id>
<id>3806712345671174984921384</id>
<state error="Unknown1">Rejected1</state>
<state error="Unknown2">Rejected2</state>
<state error="">Accepted</state>
<state error="">Accepted</state>
</status>
XML;
This method called:
$req = $this->_send('bulk',$all_phones,$this->input->post('message'));
I am unable to create array or object suitable for passing to model for inserting into DB.
Below what I have now.
$xml = new SimpleXMLElement($xmlstr);
foreach ($xml as $child) {
if ($child->getName() == 'id') {
$id[] = $child->id;
}
if ($child->getName() == 'state') {
$state[] = $child;
//$state[] = $child['error'];
}
}
return array_merge($id,$state);
I am attempting to achieve something like this array:
array(0 => array(
'id' => '3806712345671174984921381',
'state' => 'Rejected1',
'state_error' => 'Unknown1'),
1 => array( ....
Problem with error attribute with fault array_merge.
Any ideas?
You can do write this using a simple while statement.
$cnt=$xml->id->count();$i=0;
while($i<$cnt)
{
$new_arr[$i]['id']=(string)$xml->id[$i];
$new_arr[$i]['state'] = (string)$xml->state[$i];
$new_arr[$i]['state_error'] = (string)$xml->state[$i]['error'];
$i++;
}
print_r($new_arr);
Demonstration
This is how you could do it:
// Load XML
$xmlstr = '<?xml version="1.0" standalone="yes" ?>
<status id="555555555" date="Wed, 28 Mar 2013 12:35:00 +0300">
<id>3806712345671174984921381</id>
<id>3806712345671174984921382</id>
<id>3806712345671174984921383</id>
<id>3806712345671174984921384</id>
<state error="Unknown1">Rejected1</state>
<state error="Unknown2">Rejected2</state>
<state error="">Accepted</state>
<state error="">Accepted</state>
</status>';
$xml = new SimpleXMLElement($xmlstr);
// Init
$parsed_data = array();
// Parse Id
foreach ($xml->id as $id)
{
$parsed_data[] = array(
'id' => (string)$id,
'state' => '',
'state_error' => ''
);
}
// Parse State & State Error
$i = 0;
foreach ($xml->state as $state)
{
$parsed_data[$i]['state'] = (string)$state;
$parsed_data[$i]['state_error'] = (string)$state['error'];
$i++;
}
// Output
var_dump($parsed_data);
Here's the output I got:

How do extract child element in XML using DOM in PHP 5.0?

I am having the XML like this
<?xml version="1.0" encoding="utf-8"?>
<root>
<mynode catid="10" catname="Animals" label="Animals" catdesc="" parent_id="2">
<mynode catid="11" catname="Lions" label="Lions" catdesc="" parent_id="10">
<mynode catid="12" catname="lion" label="lion" catdesc="" parent_id="11"/>
<mynode catid="13" catname="lioness" label="lioness" catdesc="" parent_id="11"/>
</mynode>
</mynode>
</root>
From this I want to remove
<?xml version="1.0" encoding="utf-8"?>
<root>
and
</root>
So expected result is
<mynode catid="10" catname="Animals" label="Animals" catdesc="" parent_id="2">
<mynode catid="11" catname="Lions" label="Lions" catdesc="" parent_id="10">
<mynode catid="12" catname="lion" label="lion" catdesc="" parent_id="11"/>
<mynode catid="13" catname="lioness" label="lioness" catdesc="" parent_id="11"/>
</mynode>
</mynode>
How can I do this?
Edit 1:TO Phil
$dom = new DomDocument();
//$dom->preserveWhitespace = false;
$dom->load('treewithchild.xml');
function DOMinnerHTML($element)
{
$innerHTML = "";
$children = $element->childNodes;
foreach ($children as $child)
{
$tmp_dom = new DOMDocument();
$tmp_dom->appendChild($tmp_dom->importNode($child, true));
$innerHTML.=trim($tmp_dom->saveXML());
echo $tmp_dom->saveXML();
}
return $innerHTML;
}
$dom->preserveWhiteSpace = false;
$domTable = $dom->getElementsByTagName("mynode");
foreach ($domTable as $tables)
{
//echo $tables;
DOMinnerHTML($tables);
}
As you want the inner markup of the <root> node, that is the element who's child nodes you'll want to iterate. You can access this element using the DOMDocument::documentElement property.
Try this (tested and working)
$doc = new DOMDocument;
$doc->load('treewithchild.xml');
$inner = '';
foreach ($doc->documentElement->childNodes as $child) {
$inner .= $doc->saveXML($child);
}
echo $inner;
I expect that the root element is returned also, you have to know that for each xml file an is added impliicitly, even if it exists in your file. so try to do this
$children = $element->childNodes->childNodes;
i think that would help you.

Categories