// 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
?>