* @license http://www.contenido.org/license/LIZENZ.txt * @link http://www.4fb.de * @link http://www.contenido.org * @since file available since contenido release <= 4.6 * * {@internal * created unknown * modified 2008-06-30, Dominik Ziegler, add security fix * * $Id$: * }} * */ if (!defined('CON_FRAMEWORK')) { die('Illegal call'); } /** * Class for parsing XML documents using SAX * * This class is a abstraction class for the PHP Expat XML functions. * * You can define handler functions/objects for start, end, PI and data sections (1.) or * your can define path which will trigger the defined event when encountered (2.) * * Example: * * 1.) $parser->setEvents(array("startElement"=> "myFunction", * "endElement"=> "myFunction", * "characterData"=> "myFunction", * "processingInstruction" => "myFunction"); * * The value can also be an array with the object reference and the method to call. * i.e. "startElement"=>array(&$myObj, "myMethod") instead of "startelement"=>"myFunction" * * 2.) $parser->setEvents(array("/root/foo/bar"=>"myFunction")); * * Valid array keys are: 'startElement', 'endElement', 'characterData', 'processingInstruction' and paths * folowing the scheme '/root/element'. The path MUST begin from the root element and MUST start with '/'. * * The value can also be an array with the object reference and the method to call. * i.e. "/foo/bar"=>array(&$myObj, "myMethod") instead of "/foo/bar"=>"myFunction" * * It has 3 public methods: * * setEventHandlers - Set specific handlers for the xml parser * parseFile - Used to parse a XML file * parse - Used to parse a XML string * * A small example: * * include ("class.xmlparser.php"); * * // The XML String * $xml = ' * * * some text * another text * '; * * function myHandler($name, $attribs, $content) * { * echo "HIT: [ $name ] [ $content ]
"; * } * * $parser = new XmlParser; // Parser instance * $parser->setEventHandlers(array("/foo/bar"=>"myHandler")); // Define our handler * $parser->parse($xml); // Parse the XML string * * Report bugs to: jan.lengowski@4fb.de * * @author Jan Lengowski * @copyright four for business AG * @version 1.0 */ class clXmlParser { /** * XML Parser autofree * * @var bool */ var $autofree = true; /** * XML Parser object * * @var object * @access private */ var $parser; /** * Error message * * @var string * @access private */ var $error = ''; /** * Element depth * @var int * @access private */ var $depth = -1; /** * Element counter * @var int * @access private */ var $count = 0; /** * Path counter * @var int * @access private */ var $pcount = 0; /** * Array for creating the path * @var array * @access private */ var $paths = array(); /** * Data storage container for the path data * @var array * @access private */ var $pathdata = array(); /** * String storing the active path * @var string * @access private */ var $activepath = ''; /** * The active node * @var string * @access private */ var $activenode = ''; /** * The defined events * @var array * @access private */ var $events = array(); /** * Constructor function * * @access private * @param string $sEncoding Encoding used when parsing files (default: UTF-8, as in PHP5) * @return void */ function __construct($sEncoding = false) { if (!$sEncoding) { $sEncoding = "UTF-8"; } $this->_init($sEncoding); } /** * Initialize the XML Parser object and sets all options * * @access private * @param string $sEncoding Encoding used when parsing files (default: UTF-8, as in PHP5) * @return void */ function _init($sEncoding = false) { if (!$sEncoding) { $sEncoding = "UTF-8"; } // Create parser instance $this->parser = xml_parser_create($sEncoding); // Set parser options xml_set_object($this->parser, $this); xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, false); xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, $sEncoding); xml_set_element_handler($this->parser, '_startElement', '_endElement'); xml_set_character_data_handler($this->parser, '_characterData'); xml_set_processing_instruction_handler($this->parser, '_processingInstruction'); // Misc stuff $this->events['paths'] = array(NULL); } /** * Returns the XML error message * * @return string XML Error message * @access private */ function _error() { $this->error = "XML error: " . xml_error_string(xml_get_error_code($this->parser)) . " at line " . xml_get_current_line_number($this->parser); return $this->error; } /** * Define events for the XML parser * * You can define handler functions/objects for start, end, PI and data sections (1.) or * your can define path which will trigger the defined event when encountered (2.) * * Example: * * 1.) $parser->setEvents(array("startElement" => "myFunction", * "endElement" => "myFunction", * "characterData" => "myFunction", * "processingInstruction" => "myFunction"); * * The value can also be an array with the object reference and the method to call. * i.e. "startElement"=>array(&$myObj, "myMethod") instead of "startelement"=>"myFunction" * * 2.) $parser->setEvents(array("/root/foo/bar"=>"myFunction")); * * Valid array keys are: 'startElement', 'endElement', 'characterData', 'processingInstruction' and paths * folowing the scheme '/root/element'. The path MUST begin from the root element and MUST start with '/'. * * The value can also be an array with the object reference and the method to call. * i.e. "/foo/bar"=>array(&$myObj, "myMethod") instead of "/foo/bar"=>"myFunction" * * @param array Options array, valid keys are 'startElement', 'endElement', 'characterData', 'processingInstruction', or a path * * @access public * @return void */ function setEventHandlers($options = array(NULL)) { $options = $this->_changeKeyCase($options); if (array_key_exists('startelement', $options)) { $this->events['startelement'] = $options['startelement']; } if (array_key_exists('endelement', $options)) { $this->events['endelement'] = $options['endelement']; } if (array_key_exists('characterdata', $options)) { $this->events['characterdata'] = $options['characterdata']; } if (array_key_exists('processinginstruction', $options)) { $this->events['processinginstruction'] = $options['processinginstruction']; } $paths = $this->_getDefinedPaths($options); $this->events['paths'] = $paths; } /** * Set the processing instruction handler * * @param string Processing instruction handler * * @return void * @access private */ function _processingInstruction($parser, $target, $data) { $handler = $this->_getEventHandler('processinginstruction'); if ($handler) { if (is_array($handler)) { $handler[0]->$handler[1]($target, $data); } else { $handler($target, $data); } } } /** * Change all array keys to lowercase * (PHP function change_key_case is available at PHP 4.2 +) * * @param array Source array * * @return array Array with lowercased keys * @access private */ function _changeKeyCase($options = array()) { $tmp = array(); foreach ($options as $key => $value) { $tmp[strtolower($key)] = $value; } return $tmp; } /** * Returns events handlers if set * * @param string Event type * * @return sring Event handler name * @access private */ function _getEventHandler($event) { // Standard events if (array_key_exists($event, $this->events)) { return $this->events[$event]; } // Paths events if (array_key_exists($event, $this->events['paths'])) { return $this->events['paths'][$event]; } // No events found return false; } /** * Return all defined paths from the options * array and returns a new array containing * only the paths to function bindings * * @param array Options array * * @return array Paths array * @access private */ function _getDefinedPaths($options) { $tmp = array(); foreach ($options as $key => $value) { if (strstr($key, '/')) { $tmp[$key] = $value; } } return $tmp; } /** * Add a path to the stack * * @param string Element node name * @access private * @return void */ function _addPath($depth, $name) { $this->paths[$depth] = $name; } /** * Returns the current active path * * @access private */ function _getActivePath() { $tmp = array(); for ($i = 0; $i <= $this->depth; $i++) { $tmp[] = $this->paths[$i]; } $path = '/' . join('/', $tmp); return $path; } /** * XML start element handler * * @param resource XML Parser resource * @param string XML Element node name * @param array XML Element node attributes * * @return void * @access private */ function _startElement($parser, $name, $attribs) { // Increase depth $this->depth ++; // Set active node $this->activenode = $name; // Increase element counter if ($this->activenode == $this->pathdata[$this->activepath][$this->count]['name']) { $this->count ++; } else { $this->count = 0; } // Entering element context, add subpath $this->_addPath($this->depth, $name); // Get the handler for this event $handler = $this->_getEventHandler('startelement'); // If a handler is defined call it if ($handler) { if (is_array($handler)) { $handler[0]->$handler[1]($name, $attribs); } else { $handler($name, $attribs); } } // Check for defined path handlers $this->activepath = $this->_getActivePath(); // Save path data $this->pathdata[$this->activepath][$this->count] = array('name' => $name, 'attribs' => $attribs); } /** * XML character data handler * * @param resource XML Parser resource * @param string XML node data * * @return void * @access private */ function _characterData($parser, $data) { // Reset node count if ($this->activenode != $this->pathdata[$this->activepath][$this->count]['name']) { $this->count = 0; } // Save path data $this->pathdata[$this->activepath][$this->count]['content'] .= $data; // Get the handler for this event $handler = $this->_getEventHandler('characterdata'); // If a handler is defined call it if ($handler) { if (is_array($handler)) { $handler[0]->$handler[1]($data); } else { $handler($data); } } } /** * XML end element handler * * @param resource XML Parser resource * @param string XML Element node name * * @return void * @access private */ function _endElement($parser, $name) { // Get the handler for this event $handler = $this->_getEventHandler('endelement'); // Call Element handler if ($handler) { if (is_array($handler)) { $handler[0]->$handler[1]($name); } else { $handler($name); } } // Reset the active path $this->activepath = $this->_getActivePath(); // Get handler for the active path $handler = $this->_getEventHandler($this->activepath); // Call path handler if ($handler) { if (is_array($handler)) { // Handler is an object method $handler[0]->$handler[1]($this->pathdata[$this->activepath][$this->count]['name'], $this->pathdata[$this->activepath][$this->count]['attribs'], $this->pathdata[$this->activepath][$this->count]['content']); } else { // Handler is a function $handler($this->pathdata[$this->activepath][$this->count]['name'], $this->pathdata[$this->activepath][$this->count]['attribs'], $this->pathdata[$this->activepath][$this->count]['content']); } } // Decrease depth $this->depth --; } /** * Parse a XML string * * @param string XML data * * @return bool * @access public */ function parse($data, $final = false) { $success = xml_parse($this->parser, trim($data), $final); if ($final && $this->autofree) { xml_parser_free($this->parser); } if (!$success) { return $this->_error(); } return $success; } /** * Parse a XML file * * @param string File location * * @return bool * @access public */ function parseFile($file) { if (!($fp = fopen($file, "rb"))) { } while ($sData = fread($fp, 4096)) { if (!xml_parse($this->parser, $sData, feof($fp))) { $this->_error(); return false; } } if ($this->autofree) { xml_parser_free($this->parser); } return true; } } // XML_Parser ?>