// B text // C text // d one // d two // // will parse to // simpleNode { // tag ="a", // b = "B text" // c = simplenode { // attributes = array(1) { attr->"c-attr" } // cdata = "C text" // } // d = array(2) { "d one", "d two" } // } // // Simple example is // // $parser = new simpleParser(); // $xml = $parser.parseFile("somefile"); // echo "b=" . $xml->b . "\n"; // node text, i.e. "B text" // echo echo "c=" . $xml->c->cdata . "\n"; // node text, i.e. "C text" // foreach ($xml->d as $d) { // loop thru all nodes // echo "d=$d\n"; // } class simpleNode { var $tag; var $cdata = ''; var $hasChildren = false; function simpleNode($tag, $attributes) { $this->tag = $tag; if (count($attributes) != 0) { $this->attributes = $attributes; } } /** * Safe getter for undefined fields i.e. it stops "Notice: Undefined property" errors * if logging notices is turned on * @return the field value, or '' if it is not set */ function undefinedSafeGet($field) { return isset($this->{$field})? $this->{$field} : ''; } function attributeCount() { if (isset($this->attributes)) { return count($this->attributes); } return 0; } // PHP5 will return cdata for this node function __toString() { return cdata; } function debugString() { return $str = '[' . $this->tag . ', attrCount=' . $this->attributeCount() . ', haschildren='.$this->hasChildren . ']'; } } // end of class simpleNode class simpleParser { var $parser; var $nodeStack; var $currentNode; function simpleParser() { } /** * Parse XML within a file into an array * @return the parsed array, or FALSE for failure */ function parseFile($file) { $this->_createParser(); if (!($fp = fopen($file, "r"))) { die ("Cannot locate XML file : $xmlFile"); } // read & parse while ($data = fread($fp, 4096)) { if (!xml_parse($this->parser, $data, feof($fp))) { $this->error = sprintf("XML error: %s at line %d", xml_error_string(xml_get_error_code($this->parser)), xml_get_current_line_number($this->parser)); } } return $this->_freeParser(); } /** * Parse XML in a string into an array * @return the parsed array, or FALSE for failure */ function parseString(&$data) { $this->_createParser(); if (!xml_parse($this->parser, $data, TRUE)) { $this->error = sprintf("XML error: %s at line %d", xml_error_string(xml_get_error_code($this->parser)), xml_get_current_line_number($this->parser)); } return $this->_freeParser(); } function getError() { return $this->error; } function _createParser() { $this->nodeStack = array(); $this->currentNode = -1; unset($this->error); $this->parser = xml_parser_create(); xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0); xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE, 1); xml_set_object($this->parser, $this); xml_set_element_handler($this->parser, "_tag_open", "_tag_close"); xml_set_character_data_handler($this->parser, "_cdata"); } function _freeParser() { xml_parser_free($this->parser); if (isset($this->error)) { unset($this->nodeStack); return false; } $root =& $this->nodeStack[0]; unset($this->nodeStack); return $root; } function _tag_open($parser, $tag, $attributes) { // just pop the new node onto the stack $this->nodeStack[] =& new simpleNode($tag,$attributes); $this->currentNode++; } function _cdata($parser, $cdata) { // add characrer data to the current node $this->nodeStack[$this->currentNode]->cdata .= $cdata; } function _tag_close($parser, $tag) { // get current node and tidy it up $node =& $this->nodeStack[$this->currentNode]; $node->cdata = trim($node->cdata); if ($node->cdata == '') { unset($node->cdata); } // don't do anything to the root node, and also make sure we keep it // to return from the parse functions if ($this->currentNode > 0) { $tag = $node->tag; // if current node only has character data then add it as a // string to the parent, but if it has children or attributes // then add it as a simpleNode if (!$node->hasChildren && $node->attributeCount() == 0) { $node = $node->cdata; } else { $node->hasChildren = false; } // get field "$node->tag" from parent (i.e. the field with the same name // as this nodes tag), so we can add this node to it. // need to decide if it was unset, a single value, or an array $parent =& $this->nodeStack[$this->currentNode - 1]; if (!isset($parent->{$tag})) { // no field $parent->{$tag} = $node; $parent->hasChildren = true; } elseif (count($parent->{$tag}) > 1) { // field is array $parent->{$tag}[] = $node; } else { // single field, so make array $parent->{$tag} = array($parent->{$tag}, $node); } array_pop($this->nodeStack); } else { // tidy up root node $node->hasChildren = false; } $this->currentNode--; } } // end of class simpleParser // cacheHeaders - send "Not Modified" response, or set Last-Modified header // $depends should be an array of dependant files function cacheHeaders($depends) { $modDate = getlastmod(); // check for extra dependancies from script if (!empty($depends) && is_array($depends)) { while (list($key,$item) = each ($depends)) { $fileModDate = filemtime($item); if ($fileModDate > $modDate) { $modDate = $fileModDate; } } } $fileModDate = filemtime(__FILE__); if ($fileModDate > $modDate) { $modDate = $fileModDate; } // send HTTP 304 if possible if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $modDate) { if (substr(php_sapi_name(), 0, 3) == 'cgi') { header('Status: 304 Not Modified'); } else { header('HTTP/1.1 304 Not Modified'); } exit; } header('Last-Modified: '.gmdate('D, d M Y H:i:s', $modDate).' GMT'); } // httpCache - cache an HTTP request for a REST service class httpCache { var $url; var $destFile; // Keep time we last asked the server for the XML file. Don't set the // modified date on $destFile as 304 processing in cacheHeaders won't work // correctly. // Stores the remote server time in accessTime, so when we send the // If-Modified-Since header it is the correct date. However, we also use // this for checking if the cache is stale, so the local time is stored in // the modifiedTime. var $destCacheInfoFile; var $timeout; var $xml = null; function httpCache($url, $destFile, $timeout=7200) { $this->url = $url; $this->destFile = $destFile; $this->destCacheInfoFile = $destFile . '.cacheinfo'; $this->timeout = $timeout; } // remove the file from the cache function delete() { unlink($this->destFile); unlink($this->destCacheInfoFile); } // Fetch the file from the remote server and cache it locally. function fetch() { if (file_exists($this->destFile) && file_exists($this->destCacheInfoFile)) { $lastChecked = filemtime($this->destCacheInfoFile); } else { $lastChecked = 0; } if($lastChecked < (time() - $this->timeout)) { // Initialize the session $session = curl_init($this->url); // Set curl options curl_setopt($session, CURLOPT_HEADER, true); curl_setopt($session, CURLOPT_RETURNTRANSFER, true); // send the If-Modified-Since header with the last accessed time if ($lastChecked != 0) { $fileModGMT = gmdate('D, d M Y H:i:s', fileatime($this->destCacheInfoFile)).' GMT'; curl_setopt($session, CURLOPT_HTTPHEADER, array("If-Modified-Since: $fileModGMT")); } // Make the request $response = curl_exec($session); // Close the curl session curl_close($session); // Get HTTP Status code from the response $statusCode = array(); if (!preg_match('/\d\d\d/', $response, $statusCode)) { $this->error = 'Invalid response from '+$this->url; } // Check the HTTP Status code switch( $statusCode[0] ) { case 200: // Success break; case 304: // file on server was not modified - so update the file with the current date // from the server (otherwise on the next view of this page we will try to // fetch it again), and return the cached file contents (since the request // returns no data) $this->_setCacheInfoDate($response); return; default: $this-> error = 'Retrieving data from '.$this->url.' failed, and returned an unexpected HTTP status of:' . $statusCode[0]; return; } // Get the XML from the response, bypassing the header if (!($this->xml = strstr($response, 'error = 'Cannot find XML in response from '.$this->url; return; } $tmpf = tempnam('/tmp','YWS'); $fp = fopen($tmpf,"w"); fwrite($fp, $this->xml); fclose($fp); rename($tmpf, $this->destFile); $this->_setCacheInfoDate($response); } } // Return the actual XML data function getXml() { if ($this->xml) { return $this->xml; } return file_get_contents($this->destFile); } function isError() { return isset($this->error); } function getError() { return $this->error; } // internal function to update modified/accessed date on cacheinfo file. function _setCacheInfoDate($response) { $modDate = array(); if (preg_match('/^Date: (.*)$/m', $response, $modDate)) { $remoteDate = strtotime($modDate[1]); touch($this->destCacheInfoFile, time(), $remoteDate); } else { touch($this->destCacheInfoFile); } } } // end of class HttpCache ?>