Before I start my question,
I want to point out that I have tried bits and pieces of different codes that was posted here,
and I was not too successful.
So I am working on a page that loads in project description data from an external XML file. I have used simplexml_load_file function to load in the xml file.
XML file:
<?xml version='1.0' encoding='UTF-8'?>
<items>
<item category="Game">
<year>2015</year>
<projectName>Some Game</projectName>
<clientName>Some Company</clientName>
<imagePath>some_imagefile.png</imagePath>
<types>
<type>Work A</type>
<type>Work B</type>
</types>
<languages>
<language>Language X</language>
<language>Language Y</language>
<language>Language Z</language>
</languages>
<platforms>
<platform>Device 1</platform>
</platforms>
<visible>false</visible>
</item>
</items>
The above XML file is loaded in my PHP like:
<?php
$xml = simplexml_load_file("myXMLfile.xml") or die("Error: cannot create object");
?>
I am able to successfully load the XML data and use the retrieved data exactly how I want, except for the fact that it is not ordered.
So I poked around here and there and found out that I can sort my SimpleXMLElement after I transfer the value over to an Array and use usort().
So this is what I did:
function sortByYearDesc($a, $b) {
if ($a->year == $b->year) {
return 0;
}
return $a->year > $b->year ? -1 : 1;
}
function sortByProjectNameAsc($a, $b) {
return strcmp($a->projectName, $b->projectName);
}
$xmlArray = array();
foreach ($xml->item as $iTemp) {
$xmlArray[] = iTemp;
}
usort($xmlArray, "sortByProjectNameAsc");
usort($xmlArray, "sortByYearDesc");
I used two different sorting functions to sort the projects by projectName (ascending) and also by year (descending). So that I have the project displayed by the release year of the game while titles released in the same year are displayed by ascending order of its title.
Backtracking to what I originally had in my code before I added the sort functions, I have been using the loaded SimpleXMLElement by doing something like this:
<?php
foreach ($xml->item as items) {
// get_bool is a function that I made to switch a boolean to a String
if (get_bool($items->visible)) {
// echo stuff
// by stuff I mean any xml data to display
}
}
?>
I assumed that I can simply substitute the SimpleXMLElement to an array:
<?php
foreach ($xmlArray->item as items) {
// I changed $xml to $xmlArray
if (get_bool($items->visible)) {
// echo stuff
// by stuff I mean any xml data to display
}
}
?>
And I get a message "Warning: Invalid argument supplied for foreach()".
Any suggestions?
What am I missing?
***** added *****
First of all, thanks to splash58 for pointing out missing $ in my code.
Other than that, I did find a solution myself like below:
function sortByYearDesc($a, $b) {
// I noticed that the value was compared in String value, not as an integer
// so I casted each value to int
if ((int)$a->year == (int)$b->year) {
return 0;
}
return (int)$a->year > (int)$b->year ? -1 : 1;
}
function sortByProjectNameAsc($a, $b) {
return strcmp($a->projectName, $b->projectName);
}
$xmlArray = array();
foreach ($xml->item as $iTemp) {
$xmlArray[] = $iTemp; // changed "iTemp" to "$iTemp"
}
usort($xmlArray, "sortByProjectNameAsc");
usort($xmlArray, "sortByYearDesc");
I was also missing $ in my echo part of the PHP, which I had in my actual code but not in the code that I posted here:
<?php
// original post:
// foreach ($xmlArray->item as items) {
// xmlArray already is an array of "item" so does not need to go to the "item" node
// and the obvious change from "items" to "$items"
foreach ($xmlArray as $items) {
// I changed $xml to $xmlArray
if (get_bool($items->visible)) {
// echo stuff
// by stuff I mean any xml data to display
}
}
?>
Related
I have a nested multidimensional XML string that I get into SimpleXML. I want to convert it into an associative array. The examples listed on php.net do not work correctly or they do only for flat xmls.
This works better than the example on SimpleXML manual page, but in its current form it discards the attributes.
function xml2array($xmlObject, $out = array())
{
foreach ($xmlObject as $node) {
if ($node->count() > 0) {
$out[$node->getName()][] = xml2array($node);
} else {
$out[$node->getName()] = (string)$node;
}
}
return $out;
}
I am trying to read this xml file, but the code I am trying to make should work for any xml-file:
<?xml version="1.0"?>
<auto>
<binnenkant>
<voorkant>
<stuur/>
<gas/>
</voorkant>
<achterkant>
<stoel/>
<bagage/>
</achterkant>
</binnenkant>
<buitenkant>
<dak>
<dakkoffer>
<sky/>
<schoen/>
</dakkoffer>
</dak>
<trekhaak/>
<wiel/>
</buitenkant>
</auto>
I am using the two functions below to turn the XML-file into an array and turn that array into a tree.
I am trying to keep the parent-child relationship of the XML file. All I am getting back from the second function is an array with all the tags in the xml-file.
Can someone please help me?
function build_xml_tree(array $vals, $parent, $level) {
$branch = array();
foreach ($vals as $item) {
if (($item['type'] == "open") || $item['type'] == "complete") {
if ($branch && level == $item['level']) {
array_push($branch, ucfirst(strtolower($item['tag'])));
} else if ($parent == "" || $level < $item['level']) {
$branch = array(ucfirst(strtolower($item['tag'])) => build_xml_tree($vals, strtolower($item['tag']), $level));
}
}
}
return $branch;
}
function build_tree ($begin_tree, $content_naam) {
$xml = file_get_contents('xml_files/' . $content_naam . '.xml');
$p = xml_parser_create();
xml_parse_into_struct($p, $xml, $vals, $index);
?>
<pre>
<?php
print_r($vals);
?>
</pre>
<?php
$eindarray = array_merge($begin_tree, build_xml_tree($vals, "", 1));
return $eindarray;
}
There are many classes which can load an XML file. Many of them already represent the file in a tree structure: DOMDocument is one of them.
It seems a bit strange that you want to make a tree as an array when you already have a tree in a DOMDocument object: since you'll have to traverse the array-tree in some way... why don't you traverse directly the tree structure of the object-tree for printing for example?
Anyway the following code should do what you're asking for: I've used a recursive function in which the array-tree is passed by reference.
It should be trivial at this point to arrange my code to better suit your needs - i.e. complete the switch statement with more case blocks.
the $tree array has a numeric key for each node level
the tag node names are string values
if an array follows a string value, it contains the node's children
any potential text node is threated as a child node
function buildTree(DOMNode $node = NULL, &$tree) {
foreach ($node->childNodes as $cnode) {
switch($cnode->nodeType) {
case XML_ELEMENT_NODE:
$tree[] = $cnode->nodeName;
break;
case XML_TEXT_NODE:
$tree[] = $cnode->nodeValue;
break;
}
if ($cnode->hasChildNodes())
buildTree($cnode, $tree[count($tree)]);
}
}
$source ='the string which contains the XML';
$doc = new DOMDocument();
$doc->preserveWhiteSpace = FALSE;
$doc->loadXML($source, LIBXML_NOWARNING);
$tree = array();
buildTree($doc, $tree);
var_dump($tree);
I have this foreach loop.
$i2 looks like this (everytime):
$i2 = array(
'id' => "category['id']"
);
And here is a foreach loop.
foreach ($i2 as $o3 => $i3)
{
if (is_array($i3) !== true)
{
$new .= "<{$o3}>{$node->$i3}</{$o3}>";
} else {
$new .= "<{$o3}>";
$new .= "</{$o3}>";
}
}
}
$node is a new SimpleXMLElement($xml_reader->readOuterXML());. But this is working properly.
The problem is here: if I use $node->$i3 in order to get XML value of that field - it doesn't work. But if I substitute it for $node->category['id'] it does. Which seems weird as $i3 contains category['id'] and I can check that with debug tools.
I was using this in the previous projects and variable of variable was working fine. Now it doesn't. Why?
#edit
This is what happens before the code:
// i move the cursor to the first product tag
while ($xml_reader->read() and $xml_reader->name !== 'product');
// i iterate over it as long as I am inside of it
while ($xml_reader->name === 'product')
{
// i use SimpleXML inside XMLReader to work with nodes easily but without the need of loading the whole file to memory
$node = new SimpleXMLElement($xml_reader->readOuterXML());
foreach ($this->columns as $out => $in) // for each XML tag inside the product tag
{
// ... do stuff
Basically this is what happens before the code in question. The $columns is an array that enables the configuration of input XML file (keys are Prestashops XML tags and values are names of tags in the user's XML file).
For example:
<product>
<associations>
<categories>
<category>
<id></id>
</category>
<category>
</associations>
</products>
And in input one:
<category id="1"></category>
So in $columns:
$columns = ('associations' => array(
'categories' => array(
'category' => 'category['id'] // this is what $i3 later is
)
));
I can get to the given point of XML file easily. I get the category['id'] value and this is what $i3 is.
The PHP preprocessor tries to find the $i3 property on $node. But the object $node has no such property, and then it fails.
Are you sure you use the same syntax when trying to reach the category['id'] property in your previous projects?
You can try this syntax:
foreach ($i2 as $o3 => $i3)
{
if (is_array($i3) !== true)
{
$new .= "<{$o3}>" . eval("return \$node->$i3;") ."</{$o3}>";
} else {
$new .= "<{$o3}>";
$new .= "</{$o3}>";
}
}
I have the following code to find an xml element that matches preg_match
foreach($xml->Items->Item->AlternateVersions->AlternateVersion->Binding as $BookBinding) { //loop through the xml data to find the correct ASIN for the kindle edition
foreach ($xml->Items->Item->AlternateVersions->AlternateVersion->ASIN as $Kindlestring)
{
var_dump ($BookBinding);
if (preg_match('/Kindle Edition/i',$BookBinding))
{
//do stuff
}
}
}
however it is only var_dumping the first loop of Binding, how come?
I'm only guessing here because I don't know the structure of the XML but my guess is you're looping on the wrong part of the XML. This is my guess on what you should do:
foreach($xml->Items as $item) {
$bookBinding = $item->Item->AlternateVersions->AlternateVersion->Binding;
$kindleString = $item->Item->AlternateVersions->AlternateVersion->ASIN;
if (preg_match('/Kindle Edition/i',$BookBinding)) {
//do stuff
}
}
Also it looks like there may be multiple AlternateVersions so you may need to do a nested loop like so:
foreach ($xml->Items as $item) {
foreach ($item->Item->AlternateVersions as $version) {
$bookBinding = $version->AlternateVersion->Binding;
$kindleString = $version->AlternateVersion->ASIN;
if (preg_match('/Kindle Edition/i',$BookBinding)) {
//do stuff
}
}
}
I have a XML in form of String (after XLS transform):
<course>
<topic>
<chapter>Some value</chapter>
<title>Some value</title>
<content>Some value</content>
</topic>
<topic>
<chapter>Some value</chapter>
<title>Some value</title>
<content>Some value</content>
</topic>
....
</course>
Then I'm pushing above mentioned XML into the Array():
$new_xml = $proc->transformToXML($xml);
$xml2 = simplexml_load_string($new_xml);
$root = $xml2->xpath("//topic");
$current = 0;
$topics_list = array();
// put the xml values into multidimensional array
foreach($root as $data) {
if ($data === 'chapter') {
$topics_list[$current]['chapter'] = $data->chapter;
}
if ($data === 'title') {
$topics_list[$current]['title'] = $data->title;
}
if ($data === 'content') {
$topics_list[$current]['content'] = $data->content;
}
$current++;
}
print_r($topics_list);
Problem: Result is empty array. I've tried string like:
$topics_list[$current]['chapter'] = (string) $data->chapter;
but result is still empty. Can anyone explain, where is my mistake. Thanks.
Because my topic element has only simple child elements and not attributes, I can cast it to array and add it to the list (Demo):
$xml2 = SimpleXMLElement($new_xml);
$topics_list = array();
foreach ($xml2->children() as $data) {
$topics_list[] = (array) $data;
}
The alternative method is to map get_object_vars on the topic elements (Demo):
$topics_list = array_map('get_object_vars', iterator_to_array($xml2->topic, false));
But that might become a bit hard to read/follow. Foreach is probably more appropriate.
And here is the first working version of my code:
$xml2 = SimpleXMLElement($new_xml);
$current = 0;
$topics_list = array();
foreach($xml2->children() as $data) {
$topics_list[$current]['chapter'] = (string) $data->chapter;
$topics_list[$current]['title'] = (string) $data->title;
$topics_list[$current]['content'] = (string) $data->content;
$current++;
}
Thanks again to #Jack, #CoursesWeb and #fab for their investigation.
There are several mistakes.
1. return value of xpath()
$root = $xml2->xpath("//topic");
Here you assign $root to a list of all nodes retrieved by the XPath //topic. So, when you iterate over it with
foreach($root as $data)
$data refers to each of the <topic> elements, not the children of those.
2. comparison of SimpleXMLElements with strings
Let's assume, you loop over the right elements and $data refers to the <chapter> element: then the following expressions are true:
$data == 'Some value'
(string) $data === 'Some value'
But you cannot do a type safe comparison (===) between a SimpleXMLElement and a string, and the conversion to string does not result in the element name. What you want to do is:
if ($data->getName() === 'chapter')
3. how to get the text value
it should already be clear from the explanation above but you also will have to replace
$data->chapter
with
(string) $data
The problem is that you not get the name of the xml element.
To get the name of the xml element, apply elm->getName()
In your code should be:
if ($data->getName() === 'chapter')
For more details about traversing and getting xml elements with Simplexml, see this tutorial: http://coursesweb.net/php-mysql/php-simplexml