I was trying to find a way to convert any xml feed into an associative array, I noticed many other people have looked for the same thing, and there has been many attempts, some of them have failed, I found the following way of doing it, credit goes to
http://gaarf.info/2009/08/13/xml-string-to-php-array/
I slightly changed the code, and here is the outcome.
function xmlNameSpaceToArray(SimpleXmlIterator $xml, $nameSpaces=Null){
$output = Null;
$preparedArray = array();
for($xml->rewind(); $xml->valid(); $xml->next()) {
$key = $xml->key();
if(!isset($preparedArray[$key])) { $preparedArray[$key] = array(); $i=0; }
else $i = count($preparedArray[$key]);
$simple = true;
foreach($xml->current()->attributes() as $k=>$v) {
$preparedArray[$key][$i][$k]=(string)$v;
$simple = false;
}
if($nameSpaces) foreach($nameSpaces as $nid=>$name) {
foreach($xml->current()->attributes($name) as $k=>$v) {
$preparedArray[$key][$i][$nid.':'.$k]=(string)$v;
$simple = false;
}
}
if($xml->hasChildren()) {
if($simple) $preparedArray[$key][$i] = xmlNameSpaceToArray($xml->current(), $nameSpaces);
else $preparedArray[$key][$i]['content'] = xmlNameSpaceToArray($xml->current(), $nameSpaces);
} else {
if($simple) $preparedArray[$key][$i] = strval($xml->current());
else $preparedArray[$key][$i]['content'] = strval($xml->current());
}
$i++;
}
$output = $preparedArray;
return $preparedArray;
}
function xmlToArray($xmlFilePath){
$xml = new SimpleXmlIterator($xmlFilePath , null, true);
$nameSpaces = $xml->getNamespaces(true);
$output = xmlNameSpaceToArray($xml,$nameSpaces);
return $output;
}
$xmlFilePath = 'http://forums.devshed.com/rss/feed-5.xml';
$output = xmlToArray($xmlFilePath);
print_r($output);
What I'm trying to find out now is potential problems this could have, the goal is to make this work for EVERY well structured XML feed, without any php warnings, notices and without losing any data.
Can you find a flaw in this or a feed that doesn't work? It worked for everything I tested it for.
Easiest way to do this is to use the built in functions, then convert to an array.
<?php
$obj = simplexml_load_string($xml); // Parse XML
$array = json_decode(json_encode($obj), true); // Convert to array
?>
This piece of XML seems to break it.
<BackupJob ID="2011-11-09-05-00-00" StartTime="2011-11-09 04:56:51" EndTime="2011-11-09 05:02:01" BackupJobStatus="BS_STOP_SUCCESS" NumOfWarnEntries="0" NumOfErrorEntries="0" NumOfNewFiles="0" TotalNewFilesSize="0" NumOfUpdatedFiles="1" TotalUpdatedFilesSize="8709755" NumOfDeletedFiles="0" TotalDeletedFilesSize="0" NumOfMovedFiles="0" TotalMovedFilesSize="0" NumOfUpdatedPermissionFiles="0" TotalUpdatedPermissionFilesSize="0"></BackupJob>
http://php.net/manual/en/book.simplexml.php
The syntax looks something like this for your example
<aaaa Version="1.0">
<bbb>
<cccc>
<dddd Id="id:pass" />
<eeee name="hearaman" age="24" />
</cccc>
</bbb>
</aaaa>
PHP code
<?php
$xml = new SimpleXMLElement($xmlString);
echo $xml->bbb->cccc->dddd['Id'];
echo $xml->bbb->cccc->eeee['name'];
// or...........
foreach ($xml->bbb->cccc as $element) {
foreach($element as $key => $val) {
echo "{$key}: {$val}";
}
}
Related
I've been trying unsuccessfully with PHP to loop through two XML files and print the result to the screen. The aim is to take a country's name and output its regions/states/provinces as the case may be.
The first block of code successfully prints all the countries but the loop through both files gives me a blank screen.
The countries file is in the format:
<row>
<id>6</id>
<name>Andorra</name>
<iso2>AD</iso2>
<phone_code>376</phone_code>
</row>
And the states.xml:
<row>
<id>488</id>
<name>Andorra la Vella</name>
<country_id>6</country_id>
<country_code>AD</country_code>
<state_code>07</state_code>
</row>
so that country_id = id.
This gives a perfect list of countries:
$xml = simplexml_load_file("countries.xml");
$xml1 = simplexml_load_file("states.xml");
foreach($xml->children() as $key => $children) {
print((string)$children->name); echo "<br>";
}
This gives me a blank screen except for the HTML stuff on the page:
$xml = simplexml_load_file("countries.xml");
$xml1 = simplexml_load_file("states.xml");
$s = "Jamaica";
foreach($xml->children() as $child) {
foreach($xml1->children() as $child2){
if ($child->id == $child2->country_id && $child->name == $s) {
print((string)$child2->name);
echo "<br>";
}
}
}
Where have I gone wrong?
Thanks.
I suspect your problem is not casting the name to a string before doing your comparison. But why are you starting the second loop before checking if it's needed? You're looping through every single item in states.xml needlessly.
$countries = simplexml_load_file("countries.xml");
$states = simplexml_load_file("states.xml");
$search = "Jamaica";
foreach($countries->children() as $country) {
if ((string)$country->name !== $search) {
continue;
}
foreach($states->children() as $state) {
if ((string)$country->id === (string)$state->country_id) {
echo (string)$state->name . "<br/>";
}
}
}
Also, note that naming your variables in a descriptive manner makes it much easier to figure out what's going on with code.
You could probably get rid of the loops altogether using an XPath query to match the sibling value. I don't use SimpleXML, but here's what it would look like with DomDocument:
$search = "Jamaica";
$countries = new DomDocument();
$countries->load("countries.xml");
$xpath = new DomXPath($countries);
$country = $xpath->query("//row[name/text() = '$search']/id/text()");
$country_id = $country[0]->nodeValue;
$states = new DomDocument();
$states->load("states.xml");
$xpath = new DomXPath($states);
$states = $xpath->query("//row[country_id/text() = '$country_id']/name/text()");
foreach ($states as $state) {
echo $state->nodeValue . "<br/>";
}
I need to do some search on a WS, I have it working, but I need to be able to know when the soap can not find anything with the word I'm looking.
For example, when I search for KE-5977 I get a response and all the data
<GetChoferesResult xmlns:a="http://schemas.datacontract.org/2004/07/asdf.DTO" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:Choferes>
<a:ChoferesDTO>
<a:dc_patente>KE-5977</a:dc_patente>
<a:dc_patente_habitual>KE-5977</a:dc_patente_habitual>
<a:dc_rut_chofer>10165014-6</a:dc_rut_chofer>
<a:dc_tipo_equipo>4</a:dc_tipo_equipo>
<a:dc_tipo_transportista>E</a:dc_tipo_transportista>
<a:dg_nombre_chofer>JUAN SOTO</a:dg_nombre_chofer>
<a:dg_tipo_de_equipo>CamiĆ³n</a:dg_tipo_de_equipo>
<a:dg_tipo_transportista>Externo (Se llama a transporte ocasionalmente)</a:dg_tipo_transportista>
<a:dn_ano_fabricacion>1988</a:dn_ano_fabricacion>
<a:dq_capacidad_equipo>30.0000</a:dq_capacidad_equipo>
</a:ChoferesDTO>
</a:Choferes>
</GetChoferesResult>
but if I put it wrong like this "KE-7759" I get nothing.
<GetChoferesResult xmlns:a="http://schemas.datacontract.org/2004/07/asdf.DTO" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:Choferes/>
</GetChoferesResult>
</GetChoferesResponse>
</s:Body>
</s:Envelope>
I need to show a message "code not found" when I get an empty response from soap (soap request did not found anything with that code).
Please help me.
Update:
This is taken from SoapUI:
http://i.stack.imgur.com/oy4lc.jpg
In PHP I do it like this:
I ask if $isarray is array just because the response from soap can have multiple responses, to get the values when i get multiple responses i have to ask for "$response->GetChoferesResult->Choferes->ChoferesDTO", if i get only one response, the values are in "$response->GetChoferesResult->Choferes".
$response = $sClient->GetChoferes(array("req"=>array("PatenteHabitual"=>$patente)));
$isarray = $response->GetChoferesResult->Choferes->ChoferesDTO;
$xml = $response->GetChoferesResult->Choferes;
if (is_array($isarray)){
foreach($xml as $chofer){
foreach($chofer as $chofer2){
$rut=$chofer2->dc_rut_chofer;
$nombre=$chofer2->dg_nombre_chofer;
?><option value="<?php echo strtoupper($rut); ?>" selected="selected"><?php echo strtoupper($nombre); ?></option><?php
}
}
}else{
foreach($xml as $chofer){
if($chofer->dc_patente && $chofer->dg_nombre_chofer && $chofer->dc_patente_habitual){
$rut=$chofer->dc_rut_chofer;
$nombre=$chofer->dg_nombre_chofer;
?><option value="<?php echo strtoupper($rut); ?>" selected="selected"><?php echo strtoupper($nombre); ?></option><?php
}else{
?><option value="" selected="selected">Patente no encontrada</option><?php
}
}
}
Also, i would like to know if i can order the responses by a value, in this case by dg_nombre_chofer (by name).
Thanks.
You can use strpos($response, '<a:Choferes/>')
http://php.net/strpos
I use simplexml and convert the object to array using:
function objectsIntoArray($arrObjData, $arrSkipIndices = array()){
$arrData = array();
// if input is object, convert into array
if (is_object($arrObjData)) {
$arrObjData = get_object_vars($arrObjData);
}
if (is_array($arrObjData)) {
foreach ($arrObjData as $index => $value) {
if (is_object($value) || is_array($value)) {
$value = objectsIntoArray($value, $arrSkipIndices); // recursive call
}
if (in_array($index, $arrSkipIndices)) {
continue;
}
$arrData[$index] = $value;
}
}
return $arrData;
}
$process = $response->RetrieveListingDataResult;
$process = utf8_encode($process);
$xml = '<?xml version="1.0" encoding="UTF-8"?>'.$process;
$xmlObj = simplexml_load_string($xml);
$arrXml = objectsIntoArray($xmlObj);
I am parsing the following RSS feed (relevant part shown)
<item>
<title>xxx</title>
<link>xxx</link>
<guid>xxx</guid>
<description>xxx</description>
<prx:proxy>
<prx:ip>101.226.74.168</prx:ip>
<prx:port>8080</prx:port>
<prx:type>Anonymous</prx:type>
<prx:ssl>false</prx:ssl>
<prx:check_timestamp>1369199066</prx:check_timestamp>
<prx:country_code>CN</prx:country_code>
<prx:latency>20585</prx:latency>
<prx:reliability>9593</prx:reliability>
</prx:proxy>
<prx:proxy>...</prx:proxy>
<prx:proxy>...</prx:proxy>
<pubDate>xxx</pubDate>
</item>
<item>...</item>
<item>...</item>
<item>...</item>
Using the php code
$proxylist_rss = file_get_contents('http://www.xxx.com/xxx.xml');
$proxylist_xml = new SimpleXmlElement($proxylist_rss);
foreach($proxylist_xml->channel->item as $item) {
var_dump($item); // Ok, Everything marked with xxx
var_dump($item->title); // Ok, title
foreach($item->proxy() as $entry) {
var_dump($entry); //empty
}
}
While I can access everything marked with xxx, I cannot access anything inside prx:proxy - mainly because : cannot be present in valid php varnames.
The question is how to reach prx:ip, as example.
Thanks!
Take a look at SimpleXMLElement::children, you can access the namespaced elements with that.
For example: -
<?php
$xml = '<xml xmlns:prx="http://example.org/">
<item>
<title>xxx</title>
<link>xxx</link>
<guid>xxx</guid>
<description>xxx</description>
<prx:proxy>
<prx:ip>101.226.74.168</prx:ip>
<prx:port>8080</prx:port>
<prx:type>Anonymous</prx:type>
<prx:ssl>false</prx:ssl>
<prx:check_timestamp>1369199066</prx:check_timestamp>
<prx:country_code>CN</prx:country_code>
<prx:latency>20585</prx:latency>
<prx:reliability>9593</prx:reliability>
</prx:proxy>
</item>
</xml>';
$sxe = new SimpleXMLElement($xml);
foreach($sxe->item as $item)
{
$proxy = $item->children('prx', true)->proxy;
echo $proxy->ip; //101.226.74.169
}
Anthony.
I would just strip out the "prx:"...
$proxylist_rss = file_get_contents('http://www.xxx.com/xxx.xml');
$proxylist_rss = str_replace('prx:', '', $proxylist_rss);
$proxylist_xml = new SimpleXmlElement($proxylist_rss);
foreach($proxylist_xml->channel->item as $item) {
foreach($item->proxy as $entry) {
var_dump($entry);
}
}
http://phpfiddle.org/main/code/jsz-vga
Try it like this:
$proxylist_rss = file_get_contents('http://www.xxx.com/xxx.xml');
$feed = simplexml_load_string($proxylist_rss);
$ns=$feed->getNameSpaces(true);
foreach ($feed->channel->item as $item){
var_dump($item);
var_dump($item->title);
$proxy = $item->children($ns["prx"]);
$proxy = $proxy->proxy;
foreach ($proxy as $key => $value){
var_dump($value);
}
}
I need to get a XML File into a Database. Thats not the problem. Cant read it, parse it and create some Objects to map to the DB. Problem is, that sometimes the XML File can contain namespaces and sometimes not. Furtermore sometimes there is no namespace defined at all.
So what i first got was something like this:
<?xml version="1.0" encoding="UTF-8"?>
<struct xmlns:b="http://www.w3schools.com/test/">
<objects>
<object>
<node_1>value1</node_1>
<node_2>value2</node_2>
<node_3 iso_land="AFG"/>
<coords lat="12.00" long="13.00"/>
</object>
</objects>
</struct>
And the parsing:
$obj = new stdClass();
$nodes = array('node_1', 'node_2');
$t = $xml->xpath('/objects/object');
foreach($nodes AS $node) {
if($t[0]->$node) {
$obj->$node = (string) $t[0]->$node;
}
}
Thats fine as long as there are no namespaces. Here comes the XML File with namespaces:
<?xml version="1.0" encoding="UTF-8"?>
<b:struct xmlns:b="http://www.w3schools.com/test/">
<b:objects>
<b:object>
<b:node_1>value1</b:node_1>
<b:node_2>value2</b:node_2>
<b:node_3 iso_land="AFG"/>
<b:coords lat="12.00" long="13.00"/>
</b:object>
</b:objects>
</b:struct>
I now came up with something like this:
$xml = simplexml_load_file("test.xml");
$namespaces = $xml->getNamespaces(TRUE);
$ns = count($namespaces) ? 'a:' : '';
$xml->registerXPathNamespace("a", "http://www.w3schools.com/test/");
$nodes = array('node_1', 'node_2');
$obj = new stdClass();
foreach($nodes AS $node) {
$t = $xml->xpath('/'.$ns.'objects/'.$ns.'object/'.$ns.$node);
if($t[0]) {
$obj->$node = (string) $t[0];
}
}
$t = $xml->xpath('/'.$ns.'objects/'.$ns.'object/'.$ns.'node_3');
if($t[0]) {
$obj->iso_land = (string) $t[0]->attributes()->iso_land;
}
$t = $xml->xpath('/'.$ns.'objects/'.$ns.'object/'.$ns.'coords');
if($t[0]) {
$obj->lat = (string) $t[0]->attributes()->lat;
$obj->long = (string) $t[0]->attributes()->long;
}
That works with namespaces and without. But i feel that there must be a better way. Before that i could do something like this:
$t = $xml->xpath('/'.$ns.'objects/'.$ns.'object');
foreach($nodes AS $node) {
if($t[0]->$node) {
$obj->$node = (string) $t[0]->$node;
}
}
But that just wont work with namespaces.
You could make 'http://www.w3schools.com/test/' the default namespace. This way a:objectswould match regardless of whether the document says <a:objects> or <objects>.
If memory usage is not a issue you can even do it with a textual replacement, e.g.
$data = '<?xml version="1.0" encoding="UTF-8"?>
<struct xmlns:b="http://www.w3schools.com/test/">
<objects>
<object>
<node_1>value1</node_1>
<node_2>value2</node_2>
<node_3 iso_land="AFG"/>
<coords lat="12.00" long="13.00"/>
</object>
</objects>
</struct>';
$data = str_replace( // or preg_replace(,,,1) if you want to limit it to only one replacement
'xmlns:b="http://www.w3schools.com/test/"',
'xmlns="http://www.w3schools.com/test/" xmlns:b="http://www.w3schools.com/test/"',
$data
);
$xml = new SimpleXMLElement($data);
$xml->registerXPathNamespace("a", "http://www.w3schools.com/test/");
foreach($xml->xpath('//a:objects/a:object') as $n) {
echo $n->node_1;
}
You can make your XPATH statements more generic by matching on any element * and using a predicate filter to match on the local-name(), which will match on the element name with/without namespaces.
An XPATH like this:
/*[local-name()='struct']/*[local-name()='objects']/*[local-name()='object']/*[local-name()='coords']
Applied to the code sample you were using:
$obj = new stdClass();
$nodes = array('node_1', 'node_2');
$t = $xml->xpath('/*[local-name()="objects"]/*[local-name()="object"]');
foreach($nodes AS $node) {
if($t[0]->$node) {
$obj->$node = (string) $t[0]->$node;
}
}
Take a look at This
http://blog.sherifmansour.com/?p=302
It helped me a lot.
I know how to use simplexml_load_file to get XML results if the XML format is
<bowlcontents>
<banana>yellow</banana>
<apple>red</apple>
</bowlcontents>
However, I have some code that is in the format
<bowlcontents>
<fruit type="banana" skin="yellow" />
<fruit type="apple" skin="red" />
</bowlcontents>
and I want to manipulate it in the same way as in the first example. How would I do this?
EDIT: This is precisely what I want to do, yet the code below doesn't work.
<?php
$url = "http://worldsfirstfruitAPI.com/fruit.xml";
$xml = (simplexml_load_file($url));
$results = array();
foreach ($xml->bowlcontents->fruit as $fruit) {
$results[] = array(
$fruit['type'] => $fruit['skin'],
);
}
return $results;
}
?>
So at the end of it I would like to have an array, key=value:
banana=yellow
apple=red
...
I hope this clarifies. Thanks!
As per PHP's manual, attributes are accessed using the array notation:
$bowlcontents->fruit['type'];
Come to think of it, you didn't say in your question what was your problem. If that's about iterating over nodes, you can do it using foreach.
/*
$bowlcontents = simplexml_load_string(
'<bowlcontents>
<fruit type="banana" skin="yellow" />
<fruit type="apple" skin="red" />
</bowlcontents>'
);
*/
$url = "http://worldsfirstfruitAPI.com/fruit.xml";
$bowlcontents = simplexml_load_file($url);
foreach ($bowlcontents->fruit as $fruit)
{
echo $fruit['type'], "'s skin is ", $fruit['skin'], "<br/>\n";
}