<?php
/* 
    Yahoo Weather API by Alex Figueroa
    www.thelostfaith.net

    Class xmlParser => (this guy made my life really simple)
        Author: Eric Pollmann
        *    Released into public domain September 2003
        *    http://eric.pollmann.net/work/public_domain/


    Free for any usage you'd like, not necessary but
    a link to my site wouldn't be any harm.

    *** TO USE ***
    $data = new weather;
    $data = $data->retrieve(areaCode, units);

    returns an array


    * All credit of the icons goes to whoever made them. *
*/

class weather {

    var 
$areaCode;
    var 
$units;
    private 
$data;
    
    
/*
        returns errors
    */
    
function printErr($err) {
        
        if(
is_array($err)) {

            echo 
"<b>ERROR:</b>";

            
$eCount count($err);
            for(
$i=0$i<$eCount$i++) {

                echo 
$eCount."<br>";
            }
        }
        else {
            if(
strlen($err) != 0) {

                echo 
"<b>ERROR:</b>"$err;
            }
            else {
                return 
false;
            }
        }
    }
    
/*
        open connection, retrieve data,
        close connection
    */
    
function retrieveData() {
        
        
$feed "http://weather.yahooapis.com/forecastrss?p=".$this->areaCode."&u=".$this->units;
        
$ch curl_init();
        
curl_setopt($chCURLOPT_URL$feed);
        
curl_setopt($chCURLOPT_HEADER0);
        
curl_setopt ($chCURLOPT_RETURNTRANSFER1);
        
$this->cache curl_exec($ch);
        
$error curl_error($ch);

        if(
$error != '') {
            
$err[] = "Unable to retrieve stats.";
            
$err[] = "cURL: ".$error;
            return 
$this->printErr($err);
        }
        else {            
            
$parsedXML = new xmlParser($this->cache);
            
$this->cache $parsedXML->get_tree();
            
            
$this->remove_bullshit();
            return 
$this->cache;
        }
    }

    
/*
        xmlParser($this->cache) returns an array of _all_ the data.
        We need to get rid of some of it (namely the specifications
        of the document. Then we'll place the wanted data into one array.
    */
    
function remove_bullshit() {

        
$this->data $this->cache;
        
//the level we're looking for is [RSS]=>[CHANNEL]
        
$level $this->data['RSS']['CHANNEL'];
        
//there are a few array elements we don't want, mainly [type], [TITLE],
        //    [LINK], [DESCRIPTION], [LANGUAGE]
        
$bsArr = array('type','TITLE','LINK','DESCRIPTION','LANGUAGE','LASTBUILDDATE','TTL');
        foreach(
$level as $k=>$v) {
            if(
in_array($k$bsArr)) {
                unset(
$level[$k]);
            }
        }    
        
$level $this->recursive_remove_bullshit($level);
        
$this->data $level;
    }
    function 
recursive_remove_bullshit(&$data) {
        if(
is_array($data)) {
            if(isset(
$data['type'])) {
                unset(
$data['type']);
            }
            if(isset(
$data['value'])) {
                
$data $data['value'];
                return 
$data;
            }
            if(isset(
$data['attributes'])) {
                foreach(
$data['attributes'] as $k => $v) {
                    
$data[$k] = $v;                    
                    unset(
$data['attributes']);
                }
            }
            if(
is_array($data)) {
                foreach(
$data as $k => $v) {
                    
$this->recursive_remove_bullshit($data[$k]);
                }
            }
        }
        return 
$data;
    }
    function 
translate_wind_direction($degrees) {

        
$directionArr = array(
            
0.00=>"N",11.25=>"NbE",22.50=>"NNE",33.75=>"NEbN",
            
45.00=>"NE",56.25=>"NEbE",67.50=>"ENE",78.75=>"EbN",
            
90.00=>"E",101.25=>"EbS",112.50=>"ESE",123.75=>"SEbE",
            
135.00=>"SE",146.25=>"SEbS",157.50=>"SSE",168.75=>"SbE",
            
180.00=>"S",191.25=>"SbW",202.50=>"SSW",213.75=>"SWbS",
            
225.00=>"SW",236.25=>"SWbW",247.50=>"WSW",258.75=>"WbS",
            
270.00=>"W",281.25=>"WbN",292.50=>"WNW",303.75=>"NWbW",
            
315.00=>"NW",326.25=>"NWbN",337.50=>"NNW",348.75=>"NbW");
        
        
$degrees %= 360;
        
$d intval(round($degrees 11.25));
        
$dl 11.25 $d;
        
$dh $dl 11.25;

        return 
$directionArr[$dh] . "(" $dh "&#186;)";
    }    

    
/* 
        splits each section into its own array, more or less manually
    */
    
function section_off() {

        
$dataArr = array();
        
// [YWEATHER:LOCATION]
        
$dataArr['city'] = $this->data['YWEATHER:LOCATION']['CITY'];
        
$dataArr['state'] = $this->data['YWEATHER:LOCATION']['REGION'];
        
$dataArr['country'] = $this->data['YWEATHER:LOCATION']['COUNTRY'];
        
//[YWEATHER:WIND]
        
$dataArr['wind_chill'] = $this->data['YWEATHER:WIND']['CHILL'] . "&#186;" $this->data['YWEATHER:UNITS']['TEMPERATURE'];
        
$dataArr['wind_direction'] = $this->translate_wind_direction($this->data['YWEATHER:WIND']['DIRECTION']);
        
$dataArr['wind_speed'] = $this->data['YWEATHER:WIND']['SPEED'] . $this->data['YWEATHER:UNITS']['SPEED'];
        
//[YWEATHER:ATMOSPHERE]
        
$dataArr['humidity'] = $this->data['YWEATHER:ATMOSPHERE']['HUMIDITY'] . "%";
        
$dataArr['visibility'] = $this->data['YWEATHER:ATMOSPHERE']['VISIBILITY'] . $this->data['YWEATHER:UNITS']['DISTANCE'];
        
$dataArr['pressure'] = $this->data['YWEATHER:ATMOSPHERE']['PRESSURE'] . $this->data['YWEATHER:UNITS']['PRESSURE'];
        
$dataArr['status'] = $this->data['YWEATHER:ATMOSPHERE']['RISING'];
        switch(
$dataArr['status']) {
            case 
0:
                
$dataArr['status'] = 'steady';
            break;
            case 
1:
                
$dataArr['status'] = 'rising';
            break;
            case 
2:
                
$dataArr['status'] = 'falling';
            break;
        }
        
//[YWEATHER:ASTRONOMY]
        
$dataArr['sunrise'] = $this->data['YWEATHER:ASTRONOMY']['SUNRISE'];
        
$dataArr['sunset'] = $this->data['YWEATHER:ASTRONOMY']['SUNSET'];
        
//[ITEM]
        
$dataArr['title'] = $this->data['ITEM']['TITLE'];
        
$dataArr['lat'] = $this->data['ITEM']['GEO:LAT'];
        
$dataArr['long'] = $this->data['ITEM']['GEO:LONG'];
        
$dataArr['link'] = $this->data['ITEM']['LINK'];
        
//->[YWEATHER:CONDITION]
        
$dataArr['current_text'] = $this->data['ITEM']['YWEATHER:CONDITION']['TEXT'];
        
$dataArr['current_temp'] = $this->data['ITEM']['YWEATHER:CONDITION']['TEMP']  . "&#186;" $this->data['YWEATHER:UNITS']['TEMPERATURE'];
        
//->[YWEATHER:FORECAST]
        
$dataArr['forecast']['today']['day'] = $this->data['ITEM']['YWEATHER:FORECAST'][0]['DAY'];
        
$dataArr['forecast']['today']['date'] = $this->data['ITEM']['YWEATHER:FORECAST'][0]['DATE'];
        
$dataArr['forecast']['today']['low'] = $this->data['ITEM']['YWEATHER:FORECAST'][0]['LOW']  . "&#186;" $this->data['YWEATHER:UNITS']['TEMPERATURE'];
        
$dataArr['forecast']['today']['high'] = $this->data['ITEM']['YWEATHER:FORECAST'][0]['HIGH']  . "&#186;" $this->data['YWEATHER:UNITS']['TEMPERATURE'];
        
$dataArr['forecast']['today']['text'] = $this->data['ITEM']['YWEATHER:FORECAST'][0]['TEXT'];
        
$dataArr['forecast']['tomorrow']['day'] = $this->data['ITEM']['YWEATHER:FORECAST'][1]['DAY'];
        
$dataArr['forecast']['tomorrow']['date'] = $this->data['ITEM']['YWEATHER:FORECAST'][1]['DATE'];
        
$dataArr['forecast']['tomorrow']['low'] = $this->data['ITEM']['YWEATHER:FORECAST'][1]['LOW']  . "&#186;" $this->data['YWEATHER:UNITS']['TEMPERATURE'];
        
$dataArr['forecast']['tomorrow']['high'] = $this->data['ITEM']['YWEATHER:FORECAST'][1]['HIGH']  . "&#186;" $this->data['YWEATHER:UNITS']['TEMPERATURE'];
        
$dataArr['forecast']['tomorrow']['text'] = $this->data['ITEM']['YWEATHER:FORECAST'][1]['TEXT'];

        return 
$dataArr;
    }

    
/*
        called from outside, this will return
        unstanitised data straight from the feed
        **PARAMS**
        $areaCode = int
        $units = units (F / C)
    */
    
function retrieve($areaCode$units$debug=false) {
        
        
$this->debug $debug;
        
$this->areaCode = (int) $areaCode;
        
$this->units $units;
        if(!
$this->weatherData $this->retrieveData()) {            
            
$err "Unable to connect to the server.";
            return 
$this->printErr($err);
        }
        else {            
            
$this->remove_bullshit();
            
//return $this->data;
            
return $this->section_off();
        }
    }
}

class 
XMLParser {

/*
 * Based on code found online at:
 * http://php.net/manual/en/function.xml-parse-into-struct.php
 * 
 * Some things to keep in mind:  
 *     1.) all indexes that appear within the document are UPPER CASE.
 *  2.) attributes of a tag will be represented in **lower case** as "attributes":
 *             this is done to avoid collisions, in case there's a tag with the name
 *             of "attributes"... 
 *  3.) Anything that has a tag named "values" will be represented in the final array
 *             by "VALUES/VALUES", as retrieved by get_path() (see "get_path()" notes).
 * 
 * TODO: implement something to take array like this class returns & put it in XML form.
 */

    
var $data;            // Input XML data buffer
    
var $vals;            // Struct created by xml_parse_into_struct
    
var $collapse_dups;    // If there is only one tag of a given name,
                        //   shall we store as scalar or array?
    
var $index_numeric;    // Index tags by numeric position, not name.
                        //   useful for ordered XML like CallXML.
    
private $a2p;
    private 
$xmlTags;
    private 
$xmlIndex;
    private 
$levelArr;
    private 
$childTagDepth 0;
    
    
//=================================================================================
    /**
     * CONSTRUCTOR: Read in XML on object creation, via raw data (string), stream, filename, or URL.
     */
    
function __construct($data_source$data_source_type='raw'$collapse_dups=1$index_numeric=0) {
        
$this->collapse_dups $collapse_dups;
        
$this->index_numeric $index_numeric;
        
$this->data '';
        if(
$data_source_type == 'raw')
        {
            
$this->data $data_source;
        }
        elseif (
$data_source_type == 'stream')
        {
            while (!
feof($data_source))
            {
                
$this->data .= fread($data_source1000);
            }
        }
        
// try filename, then if that fails...
        
elseif (file_exists($data_source))
        {
            
$this->data implode(''file($data_source)); 

        }
        
// try URL.
        
else
        {
            
//something went horribly wrong.
            
throw new exception("XMLParser{}: FATAL: unable to find resource");
        }
    }
//end __construct()
    //=================================================================================
    
    
    
    //=================================================================================
    /**
     * Pase the XML file into a verbose, flat array struct.  Then, coerce that into a 
     * simple nested array.
     */
    
function get_tree() {
        
$parser xml_parser_create('ISO-8859-1');
        
xml_parser_set_option($parserXML_OPTION_SKIP_WHITE1);
        
        
//initialize some variables, before dropping them into xml_parse_into_struct().
        
$vals = array();
        
$index = array();
        
xml_parse_into_struct($parser$this->data$vals$index); 
        
xml_parser_free($parser);
        
        
$i = -1;
        return(
$this->get_children($vals$i));
    }
//end get_tree()
    //=================================================================================
    
    
    
    //=================================================================================
    /**
     * Internal function: build a node of the tree.
     */
    
private function build_tag($thisvals$vals, &$i$type) {
        
        
$tag = array();
        
$tag['type'] = $type;

        if(
$type === 'complete')
        {
            
// complete tag, just return it for storage in array.
            
if(isset($thisvals['attributes']))
            {
                
$tag['attributes'] = $thisvals['attributes'];
            }
            if(isset(
$thisvals['value']))
            {
                
$tag['value'] = $thisvals['value'];
            }
        }
        else
        {
            
// open tag, recurse
            
$myChildren $this->get_children($vals$i);
            if(isset(
$thisvals['attributes']))
            {
                
$tag['attributes'] = $thisvals['attributes'];
            }
            
$tag array_merge($tag$myChildren);
        }
        

        return(
$tag);
    }
//end build_tag()
    //=================================================================================
    
    
    
    //=================================================================================
    /**
     * Internal function: build an nested array representing children
     */
    
private function get_children($vals, &$i) {
        
$children = array();     // Contains node data
        
        
if ($i > -&& isset($vals[$i]['value']))
        {
            
//Node has CDATA before it's children.
            
$children['VALUE'] = $vals[$i]['value'];
        }

        
// Loop through children, until hit close tag or run out of tags
        
while (++$i count($vals))
        {
            
$type $vals[$i]['type'];
            
            
/* TODO: find something that causes this instance to fire-off, so I can tell WTF it should do.
            if ($type === 'cdata')
            {
                //TODO: find somewhere that causes this instance to fire off, so we can 
                // 'cdata':    Node has CDATA after one of it's children
                //         (Add to cdata found before in this case)
                $children['VALUE'] .= $vals[$i]['value'];
            }
            else#*/
            
if($type === 'complete' || $type === 'open')
            {
                
// 'complete':    At end of current branch
                // 'open':    Node has children, recurse
                
$tag $this->build_tag($vals[$i], $vals$i$type);
                if (
$this->index_numeric)
                {
                    
$tag['TAG'] = $vals[$i]['tag'];
                    
$children[] = $tag;
                }
                else
                {
                    
$children[$vals[$i]['tag']][] = $tag;
                }
            }
            elseif (
$type === 'close')
            {
                
// 'close:    End of node, return collected data
                //        Do not increment $i or nodes disappear!
                
break;
            }
        } 
        
        if (
$this->collapse_dups)
        {
            foreach(
$children as $key => $value)
            {
                if (
is_array($value) && (count($value) == 1))
                {
                    
$children[$key] = $value[0];
                }
            }
        }
        return 
$children;
    }
//end get_children()
    //=================================================================================
    
    
    
    //=================================================================================
    /**
     * To get data in an XML document via a simple path, as though it were a filesystem...
     * EXAMPLE PATH:
     *     "NEW-ORDER-NOTIFICATION/BUYER-SHIPPING-ADDRESS/EMAIL"
     * 
     * @param $path            (string) path in XML document to traverse...
     */
    
public function get_path($path=NULL)
    {
        
$a2p = new arrayToPath($this->get_tree());
        return(
$a2p->get_data($path));
    }
//end get_path()
    //=================================================================================
    
    
    
    //=================================================================================
    
public function get_root_element()
    {
        
//get EVERYTHING.
        
$myData $this->get_path();
        
$keys array_keys($myData);
        return(
$keys[0]);
    }
//end get_root_element()
    //=================================================================================
    
    
    
    //=================================================================================
    
public function get_value($path) {
        
$retval NULL;
        if(!
is_null($path)) {
            
$path preg_replace('/\/$/'''$path);
            
$path strtoupper($path);
            
$path $path '/value';
            
            
$retval $this->get_path($path);
        }
        
        return (
$retval);
    }
//end get_value()
    //=================================================================================
    
    
    
    //=================================================================================
    
public function get_attribute($path$attributeName=NULL) {
        
$retval NULL;
        if(!
is_null($path)) {
            
$path preg_replace('/\/$/'''$path);
            
$path strtoupper($path);
            
$path $path '/attributes/'strtoupper($attributeName);
            
            
$retval $this->get_path($path);
        }
        
        return(
$retval);
        
    }
//end get_attribute()
    //=================================================================================
}

?>