ConLite/conlite/classes/class.xmlparser.php

566 Zeilen
15 KiB
PHP

<?php
/**
* Project:
* Contenido Content Management System
*
* Description:
* Contenido XML Parser
*
* Requirements:
* @con_php_req 5.0
*
*
* @package Contenido_XML
* @version 1.0.9
* @author Jan Lengowski
* @copyright four for business AG <www.4fb.de>
* @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 = '
* <?xml version="1.0"?>
* <foo>
* <bar>some text</bar>
* <bar>another text</bar>
* </foo>';
*
* function myHandler($name, $attribs, $content)
* {
* echo "<b style='color:red'>HIT</b>: [ <b>$name</b> ] [ $content ]<br/>";
* }
*
* $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 <Jan.Lengowski@4fb.de>
* @copyright four for business AG <www.4fb.de>
* @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
?>