芝麻web文件管理V1.00
编辑当前文件:/home/strato/chroot/opt/RZphp81/includes/XML/Transformer.php
and | // | Kristian Khntopp
. | // +---------------------------------------------------------------------------+ // | This source file is subject to version 3.00 of the PHP License, | // | that is available at http://www.php.net/license/3_0.txt. | // | If you did not receive a copy of the PHP license and are unable to | // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +---------------------------------------------------------------------------+ // // $Id$ // require_once 'XML/Transformer/CallbackRegistry.php'; require_once 'XML/Util.php'; /** * XML Transformations in PHP. * * With this class one can easily bind PHP functionality to XML tags, * thus transforming an XML input tree into another XML tree without * the need for XSLT. * * @author Sebastian Bergmann
* @author Kristian Khntopp
* @copyright Copyright © 2002-2005 Sebastian Bergmann
and Kristian Khntopp
* @license http://www.php.net/license/3_0.txt The PHP License, Version 3.0 * @category XML * @package XML_Transformer */ class XML_Transformer { // {{{ Members /** * @var object * @access private */ var $_callbackRegistry = NULL; /** * If TRUE, XML attribute and element names will be * case-folded. * * @var boolean * @access private * @see $_caseFoldingTo */ var $_caseFolding = FALSE; /** * Can be set to either CASE_UPPER or CASE_LOWER * and sets the target case for the case-folding. * * @var integer * @access private * @see $_caseFolding */ var $_caseFoldingTo = CASE_UPPER; /** * When set to TRUE empty XML tags (
) are * collapsed to their short-tag (
) equivalent. * * @var boolean * @access private */ var $_collapseEmptyTags = FALSE; /** * Collapse mode * * @var int * @access private */ var $_collapseEmptyTagsMode = XML_UTIL_COLLAPSE_ALL; /** * If TRUE, debugging information will be sent to * the error.log. * * @var boolean * @access private * @see $_debugFilter */ var $_debug = FALSE; /** * If not empty, debugging information will only be generated * for XML elements whose names are in this array. * * @var array * @access private * @see $_debug */ var $_debugFilter = array(); /** * Specifies the target to which error messages and * debugging messages are sent. * * @var string * @access private * @see $_debug */ var $_logTarget = 'error_log'; /** * @var array * @access private */ var $_attributesStack = array(); /** * @var array * @access private */ var $_cdataStack = array(''); /** * @var array * @access private */ var $_elementStack = array(); /** * @var integer * @access private */ var $_level = 0; /** * @var string * @access private */ var $_lastProcessed = ''; /** * @var boolean * @access public */ var $_secondPassRequired = FALSE; /** * @var integer * @access private */ var $_depth = 0; // }}} // {{{ function XML_Transformer($parameters = array()) /** * Constructor. * * @param array * @access public */ function XML_Transformer($parameters = array()) { // Parse parameters array. if (isset($parameters['debug'])) { $this->setDebug($parameters['debug']); } $this->_caseFolding = isset($parameters['caseFolding']) ? $parameters['caseFolding'] : FALSE; $this->_collapseEmptyTags = isset($parameters['collapseEmptyTags']) ? $parameters['collapseEmptyTags'] : FALSE; $this->_collapseEmptyTagsMode = isset($parameters['collapseEmptyTagsMode']) ? $parameters['collapseEmptyTagsMode'] : XML_UTIL_COLLAPSE_ALL; $this->_caseFoldingTo = isset($parameters['caseFoldingTo']) ? $parameters['caseFoldingTo'] : CASE_UPPER; $this->_lastProcessed = isset($parameters['lastProcessed']) ? $parameters['lastProcessed'] : ''; $this->_logTarget = isset($parameters['logTarget']) ? $parameters['logTarget'] : 'error_log'; $autoload = isset($parameters['autoload']) ? $parameters['autoload'] : FALSE; $overloadedNamespaces = isset($parameters['overloadedNamespaces']) ? $parameters['overloadedNamespaces'] : array(); $recursiveOperation = isset($parameters['recursiveOperation']) ? $parameters['recursiveOperation'] : TRUE; // Initialize callback registry. if (!isset($parameters['callbackRegistry'])) { $this->_callbackRegistry = new XML_Transformer_CallbackRegistry($recursiveOperation); } else { $this->_callbackRegistry = &$parameters['callbackRegistry']; } foreach ($overloadedNamespaces as $namespacePrefix => $object) { $this->overloadNamespace( $namespacePrefix, $object ); } if ($autoload !== FALSE) { $this->_autoload($autoload); } } // }}} // {{{ function canonicalize($target) /** * Canonicalizes a given attributes array or element name. * * @param mixed * @return mixed * @access public */ function canonicalize($target) { if ($this->_caseFolding) { if (is_string($target)) { return ($this->_caseFoldingTo == CASE_UPPER) ? strtoupper($target) : strtolower($target); } else { return array_change_key_case( $target, $this->_caseFoldingTo ); } } return $target; } // }}} // {{{ function overloadNamespace($namespacePrefix, &$object, $recursiveOperation = '') /** * Overloads an XML Namespace. * * @param string * @param object * @param boolean * @access public */ function overloadNamespace($namespacePrefix, &$object, $recursiveOperation = '') { if (empty($namespacePrefix) || $namespacePrefix == '&MAIN') { $namespacePrefix = '&MAIN'; } else { $namespacePrefix = $this->canonicalize($namespacePrefix); } $result = $this->_callbackRegistry->overloadNamespace( $namespacePrefix, $object, $recursiveOperation ); if ($result === TRUE) { if ($object->secondPassRequired) { $this->_secondPassRequired = TRUE; } // Call initObserver() on the object, if it exists. if (method_exists($object, 'initObserver')) { $object->initObserver( $namespacePrefix, $this ); } } else { $this->sendMessage( $result, $this->_logTarget ); } } // }}} // {{{ function unOverloadNamespace($namespacePrefix) /** * Reverts overloading of a given XML Namespace. * * @param string * @access public */ function unOverloadNamespace($namespacePrefix) { $this->_callbackRegistry->unOverloadNamespace($namespacePrefix); } // }}} // {{{ function isOverloadedNamespace($namespacePrefix) /** * Returns TRUE if a given namespace is overloaded, * FALSE otherwise. * * @param string * @return boolean * @access public */ function isOverloadedNamespace($namespacePrefix) { return $this->_callbackRegistry->isOverloadedNamespace( $this->canonicalize($namespacePrefix) ); } // }}} // {{{ function sendMessage($message, $target = 'error_log') /** * Sends a message to a given target. * * @param string * @param string * @access public */ function sendMessage($message, $target = 'error_log') { switch ($target) { case 'echo': case 'print': { print $message; } break; default: { error_log($message); } } } // }}} // {{{ function setCaseFolding($caseFolding) /** * Sets the XML parser's case-folding option. * * @param boolean * @param integer * @access public */ function setCaseFolding($caseFolding, $caseFoldingTo = CASE_UPPER) { if (is_bool($caseFolding) && ($caseFoldingTo == CASE_LOWER || $caseFoldingTo == CASE_UPPER)) { $this->_caseFolding = $caseFolding; $this->_caseFoldingTo = $caseFoldingTo; } } // }}} // {{{ function setCollapsingOfEmptyTags($collapseEmptyTags, $mode = XML_UTIL_COLLAPSE_ALL) /** * Sets the collapsing of empty tags. * * @param boolean * @param integer * @access public */ function setCollapsingOfEmptyTags($collapseEmptyTags, $mode = XML_UTIL_COLLAPSE_ALL) { if (is_bool($collapseEmptyTags) && ($mode == XML_UTIL_COLLAPSE_ALL || $mode == XML_UTIL_COLLAPSE_XHTML_ONLY)) { $this->_collapseEmptyTags = $collapseEmptyTags; $this->_collapseEmptyTagsMode = $mode; } } // }}} // {{{ function setDebug($debug) /** * Enables or disables debugging information. * * @param mixed * @access public */ function setDebug($debug) { if (is_array($debug)) { $this->_debug = TRUE; $this->_debugFilter = array_flip($debug); } else if (is_bool($debug)) { $this->_debug = $debug; } } // }}} // {{{ function setLogTarget($logTarget) /** * Sets the target to which error messages and * debugging messages are sent. * * @param string * @access public */ function setLogTarget($logTarget) { $this->_logTarget = $logTarget; } // }}} // {{{ function setRecursiveOperation($recursiveOperation) /** * Enables or disables the recursive operation. * * @param boolean * @access public */ function setRecursiveOperation($recursiveOperation) { $this->_callbackRegistry->setRecursiveOperation($recursiveOperation); } // }}} // {{{ function stackdump() /** * Returns a stack dump as a debugging aid. * * @return string * @access public */ function stackdump() { $stackdump = sprintf( "Stackdump (level: %s) follows:\n", $this->_level ); for ($i = $this->_level; $i >= 0; $i--) { $stackdump .= sprintf( "level=%d\nelement=%s:%s\ncdata=%s\n\n", $i, isset($this->_elementStack[$i]) ? $this->_elementStack[$i] : '', isset($this->_attributesStack[$i]) ? XML_Util::attributesToString($this->_attributesStack[$i]) : '', isset($this->_cdataStack[$i]) ? $this->_cdataStack[$i] : '' ); } return $stackdump; } // }}} // {{{ function transform($xml) /** * Transforms a given XML string using the registered * PHP callbacks for overloaded tags. * * @param string * @return string * @access public */ function transform($xml) { // Do not process input when it contains no XML elements. if (strpos($xml, '<') === FALSE) { return $xml; } // Replace all occurrences of the '&' character that are not directly // followed by 'amp;' with the '&' entity. $xml = preg_replace('/&(?!amp;)/i', '&', $xml); // Create XML parser, set parser options. $parser = xml_parser_create(); xml_set_object($parser, $this); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, $this->_caseFolding); // Register SAX callbacks. xml_set_element_handler($parser, '_startElement', '_endElement'); xml_set_character_data_handler($parser, '_characterData'); xml_set_default_handler($parser, '_characterData'); // Parse input. if (!xml_parse($parser, $xml, TRUE)) { $line = xml_get_current_line_number($parser); $errorMessage = sprintf( "Transformer: XML Error: %s at line %d:%d\n", xml_error_string(xml_get_error_code($parser)), $line, xml_get_current_column_number($parser) ); $exml = preg_split('/\n/', $xml); $start = ($line - 3 > 0) ? $line - 3 : 0; $end = ($line + 3 < sizeof($exml)) ? $line + 3 : sizeof($exml); for ($i = $start; $i < $end; $i++) { $errorMessage .= sprintf( "line %d: %s\n", $i+1, $exml[$i] ); } $this->sendMessage( $errorMessage . "\n" . $this->stackdump(), $this->_logTarget ); return ''; } $result = $this->_cdataStack[0]; // Clean up. xml_parser_free($parser); $this->_attributesStack = array(); $this->_cdataStack = array(''); $this->_elementStack = array(); $this->_level = 0; $this->_lastProcessed = ''; // Perform second transformation pass, if required. $secondPassRequired = $this->_secondPassRequired; if ($secondPassRequired) { $this->_depth++; $this->_secondPassRequired = FALSE; $result = $this->transform($result); $this->_depth--; } if ($this->_collapseEmptyTags && $this->_depth == 0) { $result = XML_Util::collapseEmptyTags( $result, $this->_collapseEmptyTagsMode ); } $this->_secondPassRequired = $secondPassRequired; // Return result of the transformation. return $result; } // }}} // {{{ function _startElement($parser, $element, $attributes) /** * SAX callback for 'startElement' event. * * @param resource * @param string * @param array * @access private */ function _startElement($parser, $element, $attributes) { $attributes = $this->canonicalize($attributes); $element = $this->canonicalize($element); $qElement = XML_Util::splitQualifiedName($element, '&MAIN'); $process = $this->_lastProcessed != $element; // Push element's name and attributes onto the stack. $this->_level++; $this->_elementStack[$this->_level] = $element; $this->_attributesStack[$this->_level] = $attributes; if ($this->_checkDebug($element)) { $this->sendMessage( sprintf( 'startElement[%d]: %s %s', $this->_level, $element, XML_Util::attributesToString($attributes) ) ); } if ($process && isset($this->_callbackRegistry->overloadedNamespaces[$qElement['namespace']]['active'])) { // The event is handled by a callback // that is registered for this namespace. $cdata = $this->_callbackRegistry->overloadedNamespaces[$qElement['namespace']]['object']->startElement( $qElement['localPart'], $attributes ); } else { // No callback was registered for this element's // opening tag, copy it. $cdata = sprintf( '<%s%s>', $element, XML_Util::attributesToString($attributes) ); } $this->_cdataStack[$this->_level] = $cdata; } // }}} // {{{ function _endElement($parser, $element) /** * SAX callback for 'endElement' event. * * @param resource * @param string * @access private */ function _endElement($parser, $element) { $cdata = $this->_cdataStack[$this->_level]; $element = $this->canonicalize($element); $qElement = XML_Util::splitQualifiedName($element, '&MAIN'); $process = $this->_lastProcessed != $element; $recursion = FALSE; if ($process && isset($this->_callbackRegistry->overloadedNamespaces[$qElement['namespace']]['active'])) { // The event is handled by a callback // that is registered for this namespace. $result = $this->_callbackRegistry->overloadedNamespaces[$qElement['namespace']]['object']->endElement( $qElement['localPart'], $cdata ); if (is_array($result)) { $cdata = &$result[0]; $reparse = $result[1]; } else { $cdata = &$result; $reparse = TRUE; } $recursion = $reparse && isset($this->_elementStack[$this->_level-1]) && $this->_callbackRegistry->overloadedNamespaces[$qElement['namespace']]['recursiveOperation']; } else { // No callback was registered for this element's // closing tag, copy it. $cdata .= '' . $element . '>'; } if ($recursion) { // Recursively process this transformation's result. if ($this->_checkDebug('&RECURSE')) { $this->sendMessage( sprintf( 'start recursion[%d]: %s', $this->_level, $cdata ) ); } $transformer = new XML_Transformer( array( 'callbackRegistry' => &$this->_callbackRegistry, 'caseFolding' => $this->_caseFolding, 'caseFoldingTo' => $this->_caseFoldingTo, 'lastProcessed' => $element ) ); $cdata = substr($transformer->transform("<_>$cdata"), 3, -4); if ($this->_checkDebug('&RECURSE')) { $this->sendMessage( sprintf( 'end recursion[%d]: %s', $this->_level, $cdata ) ); } } if ($this->_checkDebug($element)) { $this->sendMessage( sprintf( 'endElement[%d]: %s (with cdata=%s)', $this->_level, $element, $this->_cdataStack[$this->_level] ) ); } // Move result of this transformation step to // the parent's CDATA section. $this->_cdataStack[--$this->_level] .= $cdata; } // }}} // {{{ function _characterData($parser, $cdata) /** * SAX callback for 'characterData' event. * * @param resource * @param string * @access private */ function _characterData($parser, $cdata) { if ($this->_checkDebug('&CDATA')) { $this->sendMessage( sprintf( 'cdata [%d]: %s + %s', $this->_level, $this->_cdataStack[$this->_level], $cdata ) ); } $this->_cdataStack[$this->_level] .= $cdata; } // }}} // {{{ function _autoload($namespaces) /** * Loads either all (TRUE) or a selection of namespace * handlers from XML/Transformer/Namespace/. * * @param mixed * @access private */ function _autoload($namespaces) { $path = dirname(__FILE__) . '/Transformer/Namespace/'; if ($namespaces === TRUE) { $namespaces = array(); if ($dir = @opendir($path)) { while (($file = @readdir($dir)) !== FALSE) { if (strstr($file, '.php')) { $namespaces[] = $this->canonicalize( strtolower( substr($file, 0, -4) ) ); } } } } else if (is_string($namespaces)) { $namespaces = array($namespaces); } foreach ($namespaces as $namespace) { if (@include_once($path . $namespace . '.php')) { $className = 'XML_Transformer_Namespace_' . $namespace; $object = new $className; $this->overloadNamespace( !empty($object->defaultNamespacePrefix) ? $object->defaultNamespacePrefix : $namespace, $object ); } } } // }}} // {{{ function _checkDebug($currentElement = '') /** * Checks whether a debug message should be printed * for the current event. * * @param string * @return boolean * @access private */ function _checkDebug($currentElement = '') { if ($this->_debug && (empty($this->_debugFilter) || isset($this->_debugFilter[$currentElement]))) { return TRUE; } else { return FALSE; } } // }}} } /* * vim600: et sw=2 ts=2 fdm=marker * vim<600: et sw=2 ts=2 */ ?>