I do a SOAP-call to my client. It returns a XML-document as a string (this is a workaround that I can't do anything about). I have the XML in a variable and I need to read this XML to grab the information I want.
I am looking for the fields DomesticCustomer, Addresses and GridOwner. I guess if someone helps me to get to the DomesticCustomer-part I can do the rest on my own.
Note: In this example, there is only one entry under each field, but there could be multiple, so I need to be able to foreach this.
Note #2: Because the client I use has some weird workaround for this to work, the response (the xml) is a simple string.
The XML is:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<MeteringPointIdResponse xmlns="http://www.ediel.no/schemas/public/nubix/MeteringPointIdResponse">
<RequestId xmlns="">3423424234</RequestId>
<Requestor xmlns="">
<GLN>234234234</GLN>
</Requestor>
<Customers xmlns="">
<DomesticCustomer>
<LastName>Name</LastName>
<FirstName>Name</FirstName>
<BirthDate>xxx-xx-xx</BirthDate>
<MeterNumber>xxxxx</MeterNumber>
<Addresses>
<Address>
<Address1>345345</Address1>
<PostCode>3514</PostCode>
<Location>xxxxxx</Location>
<CountryCode>xx</CountryCode>
<Installation>
<Description>xxxxx</Description>
<MeteringPointId>xxxxxxxxxxxxxxx</MeteringPointId>
<MeteringMethod>xxxxxx</MeteringMethod>
<InstallationStatus>xxxx</InstallationStatus>
<LastReadOffDate>xxxx-xx-xx</LastReadOffDate>
</Installation>
<GridOwner>
<GLN>xxxxxxx</GLN>
<Name>xxxxxxxx</Name>
<ProdatAddress>
<InterchangeRecipient>
<Id>xxxxxxx</Id>
<Qualifier>xx</Qualifier>
<Subaddress>xxxxx</Subaddress>
</InterchangeRecipient>
<Party>
<Id>xxxxxxxxxx</Id>
<CodeListResponsible>xxxx</CodeListResponsible>
</Party>
<EDISyntax>
<CharSet>xxx</CharSet>
<SyntaxId>xxxx</SyntaxId>
</EDISyntax>
<SMTPAddress>test#hey.com</SMTPAddress>
</ProdatAddress>
</GridOwner>
</Address>
</Addresses>
</DomesticCustomer>
</Customers>
</MeteringPointIdResponse>
</soap:Body>
</soap:Envelope>
If you use the built in library for php, it parses the response and returns a mixed object/array object that is INFINITELY easier to deal with than the xml
Edit: since you are using php's built in client, here is a simple class that I wrote built around it. It "flattens" the responce and makes it easy to retrieve responces like:
$soap = new SOAP($wsdl, $options);
$soap->call("stuff goes here");
$soap->find("what you are looking for goes here");
/**
* #author Troy Knapp
* #copyright 2011
*
* #version .1.1
*/
class Soap {
//***VARIABLES***//
var $request; //..............string; holds last soap request
var $requestHeaders; //.......string; holds the headers for the last request
var $response; //.............string; xml response
var $responseHeaders; //......string; holds the headers for the last response
var $result; //...............array; the soap response parsed into an array
var $wsdlLocation; //.........string; url for the wsdl
var $parameters; //...........array; saved array of parameters
var $function; //.............string; name of function to be accessed
var $findResult = array();
var $flatArray = array(); //..array; holds an easy to search array
//
//***OBJECTS***//
var $client; //...................instance of SoapClient
var $exception; //................obj; SoapFault exception object
//
//***DEFAULTS***//
public $options = array(
'trace' => 1
);
function __construct($wsdl, $options = false) {
if ($options == false) {
$options = $this->options;
} else {
$this->options = $options;
}
$this->wsdlLocation = $wsdl;
$this->client = new SoapClient($wsdl, $options);
}
/*
* Executes a given function when supplied the proper function name,
* parameters and options.
*/
function call($function, $parameters, $options=NULL) {
$this->function = $function;
$this->parameters = $parameters;
try {
//$this->response = $this->client->__soapCall($function, $parameters, $options);
$this->response = $this->client->$function($parameters, $options);
} catch (SoapFault $exception) {
$this->$exception = $exception;
}
//get info about the last request
$this->request = $this->client->__getLastRequest();
$this->requestHeaders = $this->client->__getLastRequestHeaders();
//more info about the last responce
$this->responseHeaders = $this->client->__getLastResponseHeaders();
//set up an easily searchable array of results
$this->flatten();
return $this->response;
}
/*
* Prints all kinds of interesting info about what went on for debugging
* purposes
*/
function printInfo() {
echo '<h2>SoapClient Info:</h2>';
echo 'wsdl location: ' . $this->wsdl_location . '<br/>';
echo 'SoapClient Options:';
echoPre($this->options);
echo '<h2>Call Info:</h2>';
echo 'Function Name: ' . $this->function . '<br/>';
echo 'Parameters: ';
echoPre($this->parameters);
echo '<h2>Last Request: <br></h2>';
echo $this->format($this->request);
echo '<h2>Request Headers: <br></h2>';
echo $this->format($this->requestHeaders);
echo '<h2>Last Response: <br></h2>';
echoPre($this->response);
echo '<h2>Response Headers: <br></h2>';
echo $this->format($this->responseHeaders);
}
/*
* Formats the xml to make it nice and purdy for display and debugging
* purposes
*/
function format($xml) {
// add marker linefeeds to aid the pretty-tokeniser (adds a linefeed between all tag-end boundaries)
$xml = preg_replace('/(>)(<)(\/*)/', "$1\n$2$3", $xml);
// now indent the tags
$token = strtok($xml, "\n");
$result = ''; // holds formatted version as it is built
$pad = 0; // initial indent
$matches = array(); // returns from preg_matches()
// scan each line and adjust indent based on opening/closing tags
while ($token !== false) :
// test for the various tag states
// 1. open and closing tags on same line - no change
if (preg_match('/.+<\/\w[^>]*>$/', $token, $matches)) :
$indent = 0;
// 2. closing tag - outdent now
elseif (preg_match('/^<\/\w/', $token, $matches)) :
$pad--;
// 3. opening tag - don't pad this one, only subsequent tags
elseif (preg_match('/^<\w[^>]*[^\/]>.*$/', $token, $matches)) :
$indent = 1;
// 4. no indentation needed
else :
$indent = 0;
endif;
// pad the line with the required number of leading spaces
$line = str_pad($token, strlen($token) + $pad, ' ', STR_PAD_LEFT);
$result .= $line . "\n"; // add to the cumulative result, with linefeed
$token = strtok("\n"); // get the next token
$pad += $indent; // update the pad size for subsequent lines
endwhile;
$result = highlight_string($result);
//nl2br(htmlentities($result));
return $result;
}
/*
* Searches the pre flattened array for a given key. If there is only one
* result, this will return a single value, if there are multiple results,
* it will return an array of values.
*
* #param string; $search - search for a response with this key
*/
function find($search=false) {
if ($search == false) {
return $this->flatArray;
} else {
if (isset($this->flatArray[$search])) {
$result = $this->flatArray[$search];
} else {
return false;
}
}
if(count($result)==1){
return $result[0];
}
else{
return $result;
}
}
/*
* This method flattens an array/object result into an array that is easy
* to search through. Search terms are set as keys with results set as
* arrays owned by said keys.
*/
function flatten($array=false) {
if ($array == false) {
$array = $this->response;
}
if (is_object($array)) {
//get the variables of object
$array = get_object_vars($array);
}
//howdy('array');
//echoPre($array);
//echo "_______________<br>";
if (is_array($array)) {
//loop through the sub elements and make sure they are arrays
foreach ($array as $key => $value) {
//if it's an object, we need to convert it to an array
if (is_object($value)) {
//get the variables of object
$value = get_object_vars($value);
}
//echo "key: $key value: ";
//echoPre($value);
//echo "_______________<br>";
//push the key=>value pairs to the flat array
if (!isset($this->flatArray[$key])) {
$this->flatArray[$key] = array();
}
array_push($this->flatArray[$key], $value);
if (is_array($value)) {
$this->flatten($value);
}
}
}
}
function getWSDL() {
$wsdl = file_get_contents($this->wsdlLocation);
}
}
It was that simple. Forgot to register the namespace.
$xml = simplexml_load_string($xml);
$xml->registerXPathNamespace('soap', 'http://schemas.xmlsoap.org/soap/envelope/');
foreach ($xml->xpath('//DomesticCustomer') as $item)
{
print_r($item);
}
Use http://php.net/manual/en/class.soapclient.php (Proving your on PHP5)
Related
I read this article.
https://qiita.com/yasumodev/items/74a73ed4b3f1dd45edb8
And I did the same thing.
// XML(RSSなど)を取得
$strXml = file_get_contents("./doc.xml");
// XML⇒JSONに変換
$strJson = xml_to_json($strXml);
// 出力
echo $strJson;
//**********************************
// XML ⇒ JSONに変換する関数
//**********************************
function xml_to_json($xml)
{
// コロンをアンダーバーに(名前空間対策)
$xml = preg_replace("/<([^>]+?):([^>]+?)>/", "<$1_$2>", $xml);
// プロトコルのは元に戻す
$xml = preg_replace("/_\/\//", "://", $xml);
// XML文字列をオブジェクトに変換(CDATAも対象とする)
$objXml = simplexml_load_string($xml, NULL, LIBXML_NOCDATA);
// 属性を展開する
xml_expand_attributes($objXml);
// JSON形式の文字列に変換
$json = json_encode($objXml, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
// "\/" ⇒ "/" に置換
return preg_replace('/\\\\\//', '/', $json);
}
//**********************************
// XMLタグの属性を展開する関数
//**********************************
function xml_expand_attributes($node)
{
if($node->count() > 0) {
foreach($node->children() as $child)
{
foreach($child->attributes() as $key => $val) {
$node->addChild($child->getName()."#".$key, $val);
}
xml_expand_attributes($child); // 再帰呼出
}
}
}
But in this way , several objects name change to "#attributes".
I want the original object name here(T_T)
Please help me.
When you json_encode() XML with attributes, this will create the #attributes elements your getting. The only way round this is to remove them as you expand them. I've changed the routine, the first thing is that I've put the part that processes the attributes first, this ensures that the root node gets processed as well.
The main thing is that I've changed the way it works to use XPath to retrieve the attributes, this then encodes them as you have, but also allows you to remove the attribute from the original node (using unset($attribute[0]);)...
function xml_expand_attributes($node)
{
foreach ($node->xpath("#*") as $attribute) {
$node->addChild($node->getName()."#".$attribute->getName(), (string)$attribute);
unset($attribute[0]);
}
if($node->count() > 0) {
foreach($node->children() as $child)
{
xml_expand_attributes($child); // 再帰呼出
}
}
}
I'm trying to loop through a xml file and save nodes pared with it's value into an array (key => value). I also want it to keep track of the nodes it passed (something like array(users_user_name => "myName", users_user_email => "myEmail") etc.).
I know how to do this but there is a problem. All the nodes could have children and those children might also have children etc. so I need some sort of recursive function to keep looping through the children until it reaches the last child.
So far I got this:
//loads the xml file and creates simpleXML object
$xml = simplexml_load_string($content);
// for each root value
foreach ($xml->children() as $children) {
// for each child of the root node
$node = $children;
while ($children->children()) {
foreach ($children as $child) {
if($child->children()){
break;
}
$children = $node->getName();
//Give key a name
$keyOfValue = $xml->getName() . "_" . $children . "_" . $child->getName();
// pass value from child to children
$children = $child;
// no children, fill array: key => value
if ($child->children() == false) {
$parent[$keyOfValue] = (string)$child;
}
}
}
$dataObject[] = $parent;
}
The "break;" is to prevent it from giving me the wrong values because "child" is an object and not the last child.
Using recursion, you can write some 'complicated' processing, but the problem is loosing your place.
The function I use here passed in a couple of things to keep track of the name and the current output, but also the node it's currently working with. As you can see - the method checks if there are any child nodes and calls the function again to process each one of them.
$content = <<< XML
<users>
<user>
<name>myName</name>
<email>myEmail</email>
<address><line1>address1</line1><line2>address2</line2></address>
</user>
</users>
XML;
function processNode ( $base, SimpleXMLElement $node, &$output ) {
$base[] = $node->getName();
$nodeName = implode("_", $base);
$childNodes = $node->children();
if ( count($childNodes) == 0 ) {
$output[ $nodeName ] = (string)$node;
}
else {
foreach ( $childNodes as $newNode ) {
processNode($base, $newNode, $output);
}
}
}
$xml = simplexml_load_string($content);
$output = [];
processNode([], $xml, $output);
print_r($output);
This prints out...
Array
(
[users_user_name] => myName
[users_user_email] => myEmail
[users_user_address_line1] => address1
[users_user_address_line2] => address2
)
With this implementation, there are limitations to the content - so for example - repeating content will only keep the last value (say for example there were multiple users).
You'll want to use recursion!
Here's a simple example of recursion:
function doThing($param) {
// Do what you need to do
$param = alterParam($param);
// If there's more to do, do it again
if ($param != $condition) {
$param = doThing($param);
}
// Otherwise, we are ready to return the result
else {
return $param;
}
}
You can apply this thinking to your specific use case.
//Using SimpleXML library
// Parses XML but returns an Object for child nodes
public function getNodes($root)
{
$output = array();
if($root->children()) {
$children = $root->children();
foreach($children as $child) {
if(!($child->children())) {
$output[] = (array) $child;
}
else {
$output[] = self::getNodes($child->children());
}
}
}
else {
$output = (array) $root;
}
return $output;
}
I'll just add to this
I've had some trouble when namespaces come into the mix so i made the following recursive function to solve a node
This method goes into the deepest node and uses it as the value, in my case the top node's nodeValue contains all the values nested within so we have to dig into the lowest level and use that as the true value
// using the XMLReader to read an xml file ( in my case it was a 80gig xml file which is why i don't just load everything into memory )
$reader = new \XMLReader;
$reader->open($path); // where $path is the file path to the xml file
// using a dirty trick to skip most of the xml that is irrelevant where $nodeName is the node im looking for
// then in the next while loop i skip to the next node
while ($reader->read() && $reader->name !== $nodeName);
while ($reader->name === $nodeName) {
$doc = new \DOMDocument;
$dom = $doc->importNode($reader->expand(), true);
$data = $this->processDom($dom);
$reader->next($dom->localName);
}
public function processDom(\DOMNode $node)
{
$data = [];
/** #var \DomNode $childNode */
foreach ($node->childNodes as $childNode) {
// child nodes include of a lot of #text nodes which are irrelevant for me, so i just skip them
if ($childNode->nodeName === '#text') {
continue;
}
$childData = $this->processDom($childNode);
if ($childData === null || $childData === []) {
$data[$childNode->localName] = $childNode->nodeValue;
} else {
$data[$childNode->localName] = $childData;
}
}
return $data;
}
Is there any solution to download STANDARD-XML metadata from RETS using PHRETS?
Currently am able to extract each class metadata as an array using PHRETS function GetMetadataTable and combining & converting to XML format.
But then recently I found difference in single STANDARD-XML metadata(of entire resources and classes) and individual class metadata. Using metadata viewer service RETSMD.com(built on PHRETS) also, the class name getting from STANDARD-XML metadata is different and unable to view the details.
Note: I got the STANDARD-XML metadata via direct browser log-in using credentials, like this
http://rets.login.url/GetMetadata?Type=METADATA-TABLE&Format=STANDARD-XML&ID=0
Anyone faced the same? Is there any solution using PHP?
Thanks in Advance!
I got a solution by modifying PHRETS library.
Added a new function there with following code,
if (empty($this->capability_url['GetMetadata'])) {
die("GetServerInformation() called but unable to find GetMetadata location. Failed login?\n");
}
$optional_params['Type'] = 'METADATA-SYSTEM';
$optional_params['ID'] = '*';
$optional_params['Format'] = 'STANDARD-XML';
//request server information
$result = $this->RETSRequest($this->capability_url['GetMetadata'], $optional_params );
if (!$result) {
return false;
}
list($headers, $body) = $result;
$xml = $this->ParseXMLResponse($body);
Note: Main thing to note is,
$optional_params['ID'] = '*';
Should be '*' instead '0'
If anyone is still unable to retrieve STANDARD-XML data from the CREA DDF data feed using PhRETS v2.x.x, I created a fork to the ./src/Parsers/Search/OneX.php file. You can add the following protected methods to the end of the file:
protected function parseDDFStandardXMLData(&$xml)
{
// we can only work with an array
$property_details = json_decode(json_encode($xml), true);
$retn = array();
if(! empty($property_details['RETS-RESPONSE']['PropertyDetails'])) {
foreach($property_details['RETS-RESPONSE']['PropertyDetails'] as $property_array) {
$retn[] = $this->parseArrayElements(null, $property_array);
}
}
return $retn;
}
protected function parseArrayElements($parent_key, $element)
{
// three possible $element types
// 1. scalar value
// 2. sub-array
// 3. SimpleXMLElement Object
$retn = array();
if(is_object($element)) {
$element = json_decode(json_encode($element), true);
}
if(is_array($element)) {
foreach($element as $node_key => $node) {
$key = $node_key;
if(! empty($parent_key)) {
$key = $parent_key . '|' . $key;
}
if(is_array($node) || is_object($node)) {
$nodes = $this->parseArrayElements($key, $node);
if(!empty($nodes)) {
foreach($nodes as $k => $n) {
$retn[$k] = $n;
}
}
}else{
$retn[$key] = $node;
}
}
}else{
$retn[$parent_key] = $element;
}
return $retn;
}
protected function parseRecordFromArray(&$array, Results $rs)
{
$r = new Record;
foreach($rs->getHeaders() as $key => $name) {
$r->set($name, $array[$name]);
}
return $r;
}
Then replace the parseRecords() method with:
protected function parseRecords(Session $rets, &$xml, $parameters, Results $rs)
{
if (isset($xml->DATA)) {
foreach ($xml->DATA as $line) {
$rs->addRecord($this->parseRecordFromLine($rets, $xml, $parameters, $line, $rs));
}
}elseif (isset($xml->{"RETS-RESPONSE"}->PropertyDetails)) {
$data = $this->parseDDFStandardXMLData($xml);
if(! empty($data)) {
$fields_saved = false;
foreach ($data as $line) {
if(!$fields_saved) {
$rs->setHeaders(array_keys($line));
}
$rs->addRecord($this->parseRecordFromArray($line, $rs));
}
}
}
}
The line, }elseif (isset($xml->{"RETS-RESPONSE"}->PropertyDetails)) { in the latter method does the trick to identify the STANDARD-XML RETS-RESPONSE node and parse the data.
Hope this helps,
Cheers!
Using PHP, I would like to write a function that accomplishes what is shown by this pseudo code:
function return_value($input_string='array:subArray:arrayKey')
{
$segments = explode(':',$input_string);
$array_depth = count(segments) - 1;
//Now the bit I'm not sure about
//I need to dynamically generate X number of square brackets to get the value
//So that I'm left with the below:
return $array[$subArray][$arrayKey];
}
Is the above possible? I'd really appreciate some pointer on how to acheive it.
You can use a recursive function (or its iterative equivalent since it's tail recursion):
function return_value($array, $input_string) {
$segments = explode(':',$input_string);
// Can we go next step?
if (!array_key_exists($segments[0], $array)) {
return false; // cannot exist
}
// Yes, do so.
$nextlevel = $array[$segments[0]];
if (!is_array($nextlevel)) {
if (1 == count($segments)) {
// Found!
return $nextlevel;
}
// We can return $nextlevel, which is an array. Or an error.
return false;
}
array_shift($segments);
$nextsegments = implode(':', $segments);
// We can also use tail recursion here, enclosing the whole kit and kaboodle
// into a loop until $segments is empty.
return return_value($nextlevel, $nextsegments);
}
Passing one object
Let's say we want this to be an API and pass only a single string (please remember that HTTP has some method limitation in this, and you may need to POST the string instead of GET).
The string would need to contain both the array data and the "key" location. It's best if we send first the key and then the array:
function decodeJSONblob($input) {
// Step 1: extract the key address. We do this is a dirty way,
// exploiting the fact that a serialized array starts with
// a:<NUMBEROFITEMS>:{ and there will be no "{" in the key address.
$n = strpos($input, ':{');
$items = explode(':', substr($input, 0, $n));
// The last two items of $items will be "a" and "NUMBEROFITEMS"
$ni = array_pop($items);
if ("a" != ($a = array_pop($items))) {
die("Something strange at offset $n, expecting 'a', found {$a}");
}
$array = unserialize("a:{$ni}:".substr($input, $n+1));
while (!empty($items)) {
$key = array_shift($items);
if (!array_key_exists($key, $array)) {
// there is not this item in the array.
}
if (!is_array($array[$key])) {
// Error.
}
$array = $array[$key];
}
return $array;
}
$arr = array(
0 => array(
'hello' => array(
'joe','jack',
array('jill')
)));
print decodeJSONblob("0:hello:1:" . serialize($arr));
print decodeJSONblob("0:hello:2:0" . serialize($arr));
returns
jack
jill
while asking for 0:hello:2: would get you an array { 0: 'jill' }.
you could use recursion and array_key_exists to walk down to the level of said key.
function get_array_element($key, $array)
{
if(stripos(($key,':') !== FALSE) {
$currentKey = substr($key,0,stripos($key,':'));
$remainingKeys = substr($key,stripos($key,':')+1);
if(array_key_exists($currentKey,$array)) {
return ($remainingKeys,$array[$currentKey]);
}
else {
// handle error
return null;
}
}
elseif(array_key_exists($key,$array)) {
return $array[$key];
}
else {
//handle error
return null;
}
}
Use a recursive function like the following or a loop using references to array keys
<?php
function lookup($array,$lookup){
if(!is_array($lookup)){
$lookup=explode(":",$lookup);
}
$key = array_shift($lookup);
if(!isset($array[$key])){
//throw exception if key is not found so false values can also be looked up
throw new Exception("Key does not exist");
}else{
$val = $array[$key];
if(count($lookup)){
return lookup($val,$lookup);
}
return $val;
}
}
$config = array(
'db'=>array(
'host'=>'localhost',
'user'=>'user',
'pass'=>'pass'
),
'data'=>array(
'test1'=>'test1',
'test2'=>array(
'nested'=>'foo'
)
)
);
echo "Host: ".lookup($config,'db:host')."\n";
echo "User: ".lookup($config,'db:user')."\n";
echo "More levels: ".lookup($config,'data:test2:nested')."\n";
Output:
Host: localhost
User: user
More levels: foo
I have created a website that contains that uses XML to drive some of its contents, for example, the current exchange rates as shown below.
The website compares three exchange rates and I currently use a separate functions for each city that perform identical goals e.g. returning the current exchange rates.
The only difference between these functions are the positions of the item tag from the XML document that I request the data from.
$exchange['rate'] = $xml->channel->item[15]->description;
Notice item[15] in the above array which provides access to the Euro currency. In the following, the USA currency is item[56] as below.
$exchange['rate'] = $xml->channel->item[56]->description;
Any ideas if it is possible to combine the three functions into a single one to increase cohesion?
The function I use for accessing the Euro currency is:
<?php
function get_rate1(SimpleXMLElement $xml) {
// Match numeric values before and after decimal place
$exchange['rate'] = $xml->channel->item[15]->description;
preg_match('/([0-9]+\.[0-9]+)/', $exchange['rate'], $matches);
$rate = $matches[0];
// Get currency type from title
$title['rate'] = $xml->channel->item[15]->title;
$title = explode('/', $title['rate']);
$title = $title[0];
echo $rate . ' ' . $title . '<br />';
return $rate;
}
?>
The feed URL's are set in another configuration script called cityConfig.php
<?php
// City 1 //
$city1 = 'Paris';
$exchangeRate1 = '1 Euro';
$exchangeRate1RssUrl = 'http://themoneyconverter.com/rss-feed/EUR/rss.xml';
?>
Thanks in advance
Try this on for size - it uses the three letter currency codes, and uses XPath to find them, doing away with those nasty indexes altogether. All you need to supply is the three letter code of the source currency, and the three letter code of the destination, or an array of destinations if you want to get more than one at once.
MODIFIED so that $dest is now optional. If only $source is supplied, return an array of all exchange rates.
function get_rate ($source, $dest = NULL) {
// Make sure source currency is upper case
$source = strtoupper($source);
// Construct the source URL
$url = "http://themoneyconverter.com/rss-feed/$source/rss.xml";
// This will hold the results
$result = array();
// Fetch and parse the data
if (!$xml = simplexml_load_file($url)) {
return FALSE;
}
if ($dest === NULL) {
// Get all <item> nodes and loop them
foreach ($xml->xpath("//item") as $item) {
// Find the code of this currency
$currencyParts = explode('/', $item->title);
// Get the value of this currency
$result[$currencyParts[0]] = (preg_match('/([0-9]+\.[0-9]+)/', $item->description, $matches)) ? (float) $matches[0] : FALSE;
}
} else {
// Loop the destination currencies
foreach ((array) $dest as $currency) {
// Make sure destination currencies are upper case
$currency = strtoupper($currency);
// Perform an XPath search for this currency
$nodes = $xml->xpath("//item[title='$currency/$source']/description");
// If we found the currency and could extract the value, add it to the
// result array as a float
$result[$currency] = (count($nodes) === 1 && preg_match('/([0-9]+\.[0-9]+)/', $nodes[0], $matches)) ? (float) $matches[0] : FALSE;
}
}
// If only a single value was requested return it, otherwise return the array
return (count($result) === 1) ? array_shift($result) : $result;
}
Example usage:
$result = get_rate('GBP', 'USD');
var_dump($result);
/*
float(1.58014)
*/
$result = get_rate('GBP', array('USD', 'EUR'));
var_dump($result);
/*
array(2) {
["USD"]=>
float(1.58014)
["EUR"]=>
float(1.20236)
}
*/
$result = get_rate('GBP');
var_dump($result);
/*
array(64) {
["AED"]=>
float(5.80445)
["ARS"]=>
float(6.85316)
["AUD"]=>
float(1.47589)
["BBD"]=>
float(3.16103)
["BHD"]=>
float(0.59427)
["BOB"]=>
float(10.92135)
["BRL"]=>
float(2.72171)
["CAD"]=>
float(1.57968)
["CHF"]=>
float(1.44883)
["CLP"]=>
float(759.35947)
["CNY"]=>
float(9.96753)
["COP"]=>
float(2840.97943)
["CZK"]=>
float(30.15863)
["DKK"]=>
float(8.97219)
["EGP"]=>
float(9.53446)
["EUR"]=>
float(1.20265)
["HKD"]=>
float(12.24901)
["HUF"]=>
float(350.91425)
["IDR"]=>
float(14121.92063)
["ILS"]=>
float(5.87877)
["INR"]=>
float(77.48491)
["ISK"]=>
float(194.46687)
["JMD"]=>
float(136.31954)
["JOD"]=>
float(1.12059)
["JPY"]=>
float(120.36272)
["KES"]=>
float(132.28924)
["KRW"]=>
float(1763.3828)
["KWD"]=>
float(0.43897)
["LBP"]=>
float(2382.62959)
["LKR"]=>
float(180.02093)
["LTL"]=>
float(4.1525)
["LVL"]=>
float(0.84522)
["MAD"]=>
float(13.39206)
["MXN"]=>
float(20.24582)
["MYR"]=>
float(4.77078)
["NAD"]=>
float(12.10631)
["NGN"]=>
float(253.27781)
["NOK"]=>
float(9.21948)
["NPR"]=>
float(123.97585)
["NZD"]=>
float(1.89597)
["OMR"]=>
float(0.6077)
["PAB"]=>
float(1.58052)
["PEN"]=>
float(4.25316)
["PHP"]=>
float(67.48803)
["PKR"]=>
float(142.95779)
["PLN"]=>
float(5.03909)
["QAR"]=>
float(5.75308)
["RON"]=>
float(5.23271)
["RUB"]=>
float(47.73085)
["SAR"]=>
float(5.92694)
["SEK"]=>
float(10.66422)
["SGD"]=>
float(1.96993)
["THB"]=>
float(48.79218)
["TRY"]=>
float(2.77931)
["TWD"]=>
float(46.6742)
["UAH"]=>
float(12.71293)
["USD"]=>
float(1.58052)
["UYU"]=>
float(30.74107)
["VEF"]=>
float(6.79622)
["VND"]=>
float(33119.73602)
["XAF"]=>
float(788.88394)
["XCD"]=>
float(4.2674)
["XOF"]=>
float(788.88394)
["ZAR"]=>
float(12.10631)
}
*/
How about you change the rate function to this:
<?php
function get_rate(SimpleXMLElement $xml, $id) {
// Match numeric values before and after decimal place
$exchange['rate'] = $xml->channel->item[$id]->description;
preg_match('/([0-9]+\.[0-9]+)/', $exchange['rate'], $matches);
$rate = $matches[0];
// Get currency type from title
$title['rate'] = $xml->channel->item[$id]->title;
$title = explode('/', $title['rate']);
$title = $title[0];
echo $rate . ' ' . $title . '<br />';
return $rate;
}
?>
Then you can call it like this instead:
$rate1 = get_rate($xml,15);
$rate2 = get_rate($xml,56);
Since I can't comment yet...helk's answer is perfect - you could take it one step further and define some class constants or define()'d constants so you aren't dealing with hard to remember numbers in your code (remember, you may not be the only person who ever looks at this code - you know what 15 is, but Jr. Dev Newguy does not):
<?php
define('CURRENCY_USA', 56);
define('CURRENCY_EURO', 15);
// OR something like:
class CurrencyHelper
{
const CURRENCY_USA = 56;
const CURRENCY_EURO = 15;
// of course, you could also define get_rate here too...
public function __construct(SimpleXmlElement $xml)
{
$this->xml = $xml;
}
public function get_rate($id)
{
// ... your code here
}
}
// Or, define your get_rate as originally intended:
function get_rate(SimpleXMLElement $xml, $id) {
// Match numeric values before and after decimal place
$exchange['rate'] = $xml->channel->item[$id]->description;
preg_match('/([0-9]+\.[0-9]+)/', $exchange['rate'], $matches);
$rate = $matches[0];
// Get currency type from title
$title['rate'] = $xml->channel->item[$id]->title;
$title = explode('/', $title['rate']);
$title = $title[0];
echo $rate . ' ' . $title . '<br />';
return $rate;
}
// Finally:
echo get_rate($xml, CURRENCY_EURO); // define
echo get_rate($xml, CurrencyHelper::CURRENCY_USA; // class constant
$myCurr = new CurrencyHelper($xml);
$myCurr->get_rate(CurrencyHelper::CURRENCY_USA); // Over-oop'd solution : P
?>