converting xml into array, unable to handle node inside a node - php

I want to convert a xml script into a associative array in PHP. The XML script is :
<ages>
<Peter>
<Peterchild>4</Peterchild>
<Peterchild>6</Peterchild>
</Peter>
<Quagmire>30</Quagmire>
<Joe>34</Joe>
</ages>
and the code i wrote for converting it to an array is
${$xml->getName()} = array();
foreach($xml->children() as $child){
$ages[$child->getName()] = (string)$child;
}
which gives the output as
Array
(
[Peter] =>
[Quagmire] => 30
[Joe] => 34
)
The problem is that I am unable to figure out a condition to recursively traverse through the children of children (children of Peter in this example). How to I change this code to consider those nodes as well?

You'll need to create a function that parses one level, then have the function call itself when it finds a node inside the current level. It's pretty complex and can get messy. Out of curiosity, why would you do this when you can access all the information you need in object format using a SimpleXML object?

${$xml->getName()} = array();
foreach($xml->children() as $child)
{
if (count($child->children() > 0)
{
foreach ($child->children as $childnode)
{
$ages[$childnode->getName()] = (string)$childnode;
}
}
else {
$ages[$child->getName()] = (string)$child;
}
}
This will only work if it is 1 level deep, if it is going to be more levels you should write a function, that will cycle through it level by level.

Related

Complex Loop through a complex SimpleXMLElement

I need to save some values from XML.
First step - I get the structure:
$xml = $dom_xml->saveXML();
$xml_ = new \SimpleXMLElement($xml);
dd($xml_);
Here TextFrame has 8 arrays. Each of them has PathPointType, which has
4 more arrays with 3 attributes each. And these attributes I need from each TextFrame.
I can get, for instance, Anchor value doing this:
$res = $xml_
->Spread
->TextFrame
->Properties
->PathGeometry
->GeometryPathType
->PathPointArray
->PathPointType
->attributes();
dd($res['Anchor']);
(BTW: is there more prettier way to get it?)
But the question is - how is it possible to loop through all arrays and save values separately for each array?
I assume here has to be a multidimensional foreach loop in conjunction with for loop?
Or is better to achieve it using DOMDocument?
As it looks as though you are starting off with DOMDocument (as you are using $dom_xml->saveXML() to generate the XML), it may be easier to continue using it and it also has some easy features for getting the details your after.
Using getElementsByTagName() allows you to get a list of the elements with a specific tag name from a start point, so starting with $dom_xml, get all of the <TextFrame> elements. Then foreach() over this list and using this element as a start point, use getElementsByTagName("PathPointType") to get the nested <PathPointType> elements. At this point you can then use getAttribute("Anchor") for each of the attributes you need from the <PathPointType> elements...
$textFrames = $dom_xml->getElementsByTagName("TextFrame");
foreach ( $textFrames as $frame ) {
$pathPointTypes = $frame->getElementsByTagName("PathPointType");
foreach ( $pathPointTypes as $type ) {
echo $type->getAttribute("Anchor").PHP_EOL;
}
}
Edit
You can extend the code to build an array of frames and then the anchors within that. This code also stores the anchor in an associative array so that if you add the other attributes, you can add them here (or remove it if you don't need another layer of detail)...
$frames =[];
foreach ( $textFrames as $frame ) {
$anchors = [];
$pathPointTypes = $frame->getElementsByTagName("PathPointType");
foreach ( $pathPointTypes as $type ) {
$anchors[] = ['Anchor' => $type->getAttribute("Anchor")];
}
$frames[] = $anchors;
}
Also if you have some way of identifying the frames, you could create an associative array at that level as well...
$frames[$frameID] = $anchors;
As a complement to the existing answer from Nigel Ren, I thought I'd show how the same loops look with SimpleXML.
Firstly, note that you don't need to convert the XML to string and back if you want to switch between DOM and SimpleXML for any reason, you can use simplexml_import_dom which just swaps out the interface:
$sxml = simplexml_import_dom($dom_xml);
Next we need our TextFrame elements; we could either step through the structure explicitly, as you had before:
$textFrames = $sxml->Spread->TextFrame;
Or we could use XPath to search for matching tag names within our current node (. is the current element, and // means "any descendant":
$textFrames = $sxml->xpath('.//TextFrame');
The first will give you a SimpleXMLElement object, and the second an array, but either way, you can use foreach to go through the matches.
This time we definitely want an XPath expression to get the PathPointType nodes, to avoid all the nested loops through levels we're not that interested in:
foreach ( $textFrames as $frame ) {
$pathPointTypes = $frame->xpath('.//PathPointType');
foreach ( $pathPointTypes as $type ) {
echo $type['Anchor'] . PHP_EOL;
}
}
Note that you don't need to call $type->attributes(); unless you're dealing with namespaces, all you need to get an attribute is $node['AttributeName']. Beware that attributes in SimpleXML are objects though, so you'll often want to force them to be strings with (string)$node['AttributeName'].
To take the final example, you might then have something like this:
$frames = [];
foreach ( $sxml->Spread->TextFrame as $frame ) {
$anchors = [];
$pathPointTypes = $frame->xpath('.//PathPointType');
foreach ( $pathPointTypes as $type ) {
$anchors[] = ['Anchor' => (string)$type['Anchor']];
}
$frames[] = $anchors;
}

Traversing B-Tree Structure Algorithm

I'm having a hard time trying to write a traversing function that outputs a child-node's parent-nodes.
Take a look at the example b-tree
Here is the sample dataset I'm using:
$nodes = array(
array('f','b'),
array('f','g'),
array('b','a'),
array('b','d'),
array('g','i'),
array('d','c'),
array('d','e'),
array('i','h')
);
I'm trying to output a results array that contains all child node arrays which contain parent associations.
Example Outputs:
node(d)'s parents are (b,f)
node(c)'s parents are (d,b,f)
node(h)'s parents are (i,g,f)
I can't figure out how to traverse past the direct parent node.
foreach($nodes as $node){
//CHECK IF NODE EXISTS
if(array_key_exists($node[1],$results)){
//DO NOTHING
array_push($results[$node[1]],$node[0]);
}
else{
//CREATE NEW CHILD ARRAY
$results[$node[1]] = [];
//PUSH PARENT INTO CHILD ARRAY
array_push($results[$node[1]],$node[0]);
}
}
foreach($results as $k => $v){
echo "child[$k] parents(" . implode($v,', ').")" ;
echo "</br>";
}
Question: How can I achieve this output in the most efficient manor?
The best way to deal with such cases is to use recursive functions.
echo findParents('h',$nodes);
function findParents($find,$btree){
$parents;
foreach($btree as $node){
if($node[1]===$find){
$parents .=$find.',';
return $parents .= findParents($node[0], $btree);
}
}
return $find;
}
Check the live code here: https://www.tehplayground.com/ElTdtP61DwFT1qIc
The only drawback is that it would return the original node in the return list. But I think you can live with that.
I think the better representation of the tree would be:
$nodes = array(
'f'=>array('d','g'),
'b'=>array('a','d'),
'g'=>array('i'),
'd'=>array('c','e'),
'i'=>array('h')
);
But that would need slight modification to the above code.
To get an array as the response:
function findParentsArray($find,$btree){
$parentsArray = explode(',',findParents($find,$btree));
array_shift($parentsArray);
return $parentsArray;
}
It should be possible to have that done directly in findParents() but I don't have time to look into it now.

I try to foreach loop XML file that is loaded from XML to json

I try to foreach loop XML file. First it is turned to JSON from XML file and then to PHP array that I am trying to loop with foreach function (With Zend Framework).
Problem is when XML file haves only one activity then whole code not work and when XML file haves a more than one activity then code works:
<?php
header('Content-type: application/javascript; charset=utf-8');
set_include_path(implode(PATH_SEPARATOR, array(
'library',
get_include_path()
)));
require_once('Zend/Loader.php');
Zend_Loader::loadClass('Zend_Loader_Autoloader');
$autoloader = Zend_Loader_Autoloader::getInstance();
$xmlStringContents = file_get_contents("activities.xml");
$jsonContents = Zend_Json::fromXml($xmlStringContents, true);
$decodedValues = Zend_Json::decode($jsonContents);
$project[0] = array("key" => "", "value" => "Please select...");
$i = 1;
foreach ($decodedValues['Envelope'] as $key => $value) {
foreach ($value as $keyEnvelope => $valueEnvelope) {
if (is_array($valueEnvelope)) {
foreach ($valueEnvelope['EFIAIFProjActivity'] as $keyEFIAIFProjTable => $valueEFIAIFProjTable) {
if (is_array($valueEFIAIFProjTable)) {
foreach ($valueEFIAIFProjTable as $projectValue) {
if (isset($_GET['project'])) {
if ($_GET['project']==$projectValue['Project']['ProjId'] && $_GET['project'] != "") {
$project[$i] = array("key" => $projectValue['smmActivities']['ActivityNumber'], "value" => $projectValue['smmActivities']['PSADescription']);
$i++;
}
}
}
}
}
}
}
}
if ($i==1) {
$project[$i] = array("key" => "NO_ACTIVITY_000001", "value" => "No Activity");
}
$projects = array("values" => $project);
if (isset($_GET['callback'])) {
echo $_GET['callback']."(";
echo Zend_Json::encode($projects);
echo ")";
}
I need to get this code work like that way it makes a JSON string with "Please select..." and "No Activity" to Activity dynamic drop down menu if dynamic drop down menu Project selected project haves no activities in Atlassian JIRA and Tempo Add-on. And if selected project haves one or more activities in XML file then Activity drop down menu haves these activities.
The easy solution is to not convert the XML to JSON. SimpleXML allows you to use object/traversable syntax on the XML DOM. So if you access a child element as object property is is treated as a single value, if you access as as a list (with foreach) it is treated as a list.
The main difference to you current solution is that you have to use object property syntax, not array syntax.
Another thing is that you code looks like it reads SOAP. While SOAP uses a XML syntax for serialization, it is NOT "just XML". You're better of using a specific SOAP library.
If XML has single node then Zend Framework converts it to JSON as key value pair.
If XML has more than one node then Zend Framework converts it to a JSON array.
You can handle this in 2 ways.
Check if its an array then loop it else access the values directly by its key
If the node is not an array, convert it to an array after its decoded.

XML Multidimensional Array, Sort and reference

I am REALLY not getting arrays at all, I have read many examples, most plagiarized and amended, am an yet to find help that I understand I do hope someone can walk me through this, so I understand fully.
I have an XML file, which I need to read and display with options to sort using PHP, then output the required fields into my page(s). I have managed to retreive the XML file and rehouse locally and check the age of the file (milestone) and using the code (below) have managed to read the XML file and output as an array, but I can get no further and after two days I turn to you the community for assistance:
<?php
function std_class_object_to_array($stdclassobject) {
$_array = is_object($stdclassobject) ? get_object_vars($stdclassobject) : $stdclassobject;
foreach ($_array as $key => $value) {
$value = (is_array($value) || is_object($value)) ? std_class_object_to_array($value) : $value;
$array[$key] = $value;
}
return $array;
}
$request = $domain.$filename;
$API_results = file_get_contents($request);
$xml = new SimpleXMLElement($API_results);
$Details = std_class_object_to_array($xml);
echo "<pre>";
print_r($Details[hotel]);
echo "</pre>"; ?>
The Output looks like this (shortened)
Array
(
[0] => Array
(
[hotel_ref] => 157258
[hotel_name] => Hotel Kong Arthur
continued
[1] => Array
(
[hotel_ref] => 98813
[hotel_name] => Hotel Lautruppark
Firstly is the code I have used for reading the XML good? Is there a better way that could be used? How do I sort the results? How do I output the individual fields?
Your help, examples and direction would be greatly appreciated,
Stu
//----------------------- UPDATE ----------------------- \
OK, so this is where I am at now with the suggestion to use SimpleXML, but its still not right and I am not really a great deal further on:
$request = $domain.$filename;
$xmlobj = simplexml_load_file($request);
$xml = simplexml_load_file($request) or die("NO XML");
foreach($xml as $hotels)
echo $hotels->hotel_name." (".$hotels->hotel_ref.")<br/>";
The code you have written for reading the XML is absolutely fine.
However, you are unnecessarily converting from a SimpleXMLElement object to an array, as SimpleXMLElement is already iterable. You can access the <hotel> element of the XML as simply as:
$Details->hotel[0]
You can iterate through the child elements of <hotel> by the following:
<?php
// Loop through the elements, and with each...
foreach($Details->hotel[0] as $index => $hotel) {
// ...output the name of the hotel:
echo $hotel->hotel_name;
}
?>
Here, $index will be the numeric index, and $hotel will be another SimpleXMLElement.
N.B. In terms of sorting the results, it might actually be worth doing the object-to-array conversion. All of the sorting functions in PHP work on arrays, not objects.
Hope this helps!

multi-dimensional stdClass Object

I've got a rather large multidimensional stdClass Object being outputted from a json feed with PHP.
It goes about 8 or 9 steps deep and the data that I need is around 7 steps in.
I'm wondering if I can easily grab one of the entires instead of doing this:
echo $data->one->two->anotherone->gettinglong->omg->hereweare;
I'm saying this because the data structure may change over time.
Is this possible?
You could try to parse the object into an array and search the array for the wanted values, it just keeps looping through each level of the object.
function parseObjectArrayToArray($objectPassed, &$in_arr = array()) {
foreach($objectPassed as $element) {
if(is_object($element) || is_array($element)) {
parseObjectArrayToArray($element,$in_arr);
} else {
// XML is being passed, need to strip it
$element = strip_tags($element);
// Trim whitespace
$element = trim($element);
// Push to array
if($element != '' && !in_array($element,$in_arr)) {
$in_arr[] = $element;
}
}
}
return $in_arr;
}
How to call
$parsed_obj_arr = parseObjectArrayToArray($objectPassed);
Not without searching through whats probably inefficient.
Json is a structured data object with the purpose of eliminating something like this.
If the datastructure can change, but doesn't very often, your best bet is to write a wrapper object so you will only have to change a path at a single point on change:
class MyDataWrapp {
public $json;
function __construct($jsonstring){
$this->json = json_decode($jsonstring);
}
function getHereWeAre(){
return $this->json->one->two->anotherone->gettinglong->omg->hereweare;
}
}
If the datastructure changes dramatically and constantly, I'd json_decode as an array of arrays, and probably use RecursiveFilterIterator.

Categories