Blame | Letzte Änderung | Log anzeigen | RSS feed
<?php/*** This file contains the class XML_Query2XML and all exception classes.** PHP version 5** @category XML* @package XML_Query2XML* @author Lukas Feiler <lukas.feiler@lukasfeiler.com>* @copyright 2006 Lukas Feiler* @license http://www.gnu.org/copyleft/lesser.html LGPL Version 2.1* @version CVS: $Id: Query2XML.php 309897 2011-04-02 17:36:42Z lukasfeiler $* @link http://pear.php.net/package/XML_Query2XML*//*** PEAR_Exception is used as the parent for XML_Query2XML_Exception.*/require_once 'PEAR/Exception.php';/*** Create XML data from SQL queries.** XML_Query2XML heavily uses exceptions and therefore requires PHP5.* Additionally one of the following database abstraction layers is* required: PDO (compiled-in by default since PHP 5.1), PEAR DB,* PEAR MDB2, ADOdb.** The two most important public methods this class provides are:** <b>{@link XML_Query2XML::getFlatXML()}</b>* Transforms your SQL query into flat XML data.** <b>{@link XML_Query2XML::getXML()}</b>* Very powerful and flexible method that can produce whatever XML data you want. It* was specifically written to also handle LEFT JOINS.** They both return an instance of the class DOMDocument provided by PHP5's* built-in DOM API.** A typical usage of XML_Query2XML looks like this:* <code>* <?php* require_once 'XML/Query2XML.php';* $query2xml = XML_Query2XML::factory(MDB2::factory($dsn));* $dom = $query2xml->getXML($sql, $options);* header('Content-Type: application/xml');* print $dom->saveXML();* ?>* </code>** Please read the <b>{@tutorial XML_Query2XML.pkg tutorial}</b> for* detailed usage examples and more documentation.** @category XML* @package XML_Query2XML* @author Lukas Feiler <lukas.feiler@lukasfeiler.com>* @copyright 2006 Lukas Feiler* @license http://www.gnu.org/copyleft/lesser.html LGPL Version 2.1* @version Release: 1.7.2* @link http://pear.php.net/package/XML_Query2XML*/class XML_Query2XML{/*** Primary driver.* @var XML_Query2XML_Driver A subclass of XML_Query2XML_Driver.*/private $_driver;/*** An instance of PEAR Log* @var mixed An object that has a method with the signature log(String $msg);* preferably PEAR Log.* @see enableDebugLog* @see disableDebugLog*/private $_debugLogger;/*** Whether debug logging is to be performed* @var boolean* @see enableDebugLog* @see disableDebugLog*/private $_debug = false;/*** Whether profiling is to be performed* @var boolean* @see startProfiling()* @see stopProfiling()*/private $_profiling = false;/*** The profiling data.* @var array A multi dimensional associative array* @see startProfiling()* @see stopProfiling()* @see _debugStartQuery()* @see _debugStopQuery()* @see _stopDBProfiling()*/private $_profile = array();/*** An associative array of global options.* @var array An associative array* @see setGlobalOption()*/private $_globalOptions = array('hidden_container_prefix' => '__');/*** An associative array that will contain an element for each prefix.* The prefix is used as the element key. Each array element consists* of an indexed array containing a file path and a class name.* @var array An associative multidimensional array.* @see registerPrefix()* @see unregisterPrefix()* @see unregisterAllPrefixes()* @see _buildCommandChain()*/private $_prefixes = array('?' => array('XML/Query2XML/Data/Condition/NonEmpty.php','XML_Query2XML_Data_Condition_NonEmpty'),'&' => array('XML/Query2XML/Data/Processor/Unserialize.php','XML_Query2XML_Data_Processor_Unserialize'),'=' => array('XML/Query2XML/Data/Processor/CDATA.php','XML_Query2XML_Data_Processor_CDATA'),'^' => array('XML/Query2XML/Data/Processor/Base64.php','XML_Query2XML_Data_Processor_Base64'),':' => array('XML/Query2XML/Data/Source/Static.php','XML_Query2XML_Data_Source_Static'),'#' => array('XML/Query2XML/Data/Source/PHPCallback.php','XML_Query2XML_Data_Source_PHPCallback'),'~' => array('XML/Query2XML/Data/Source/XPath.php','XML_Query2XML_Data_Source_XPath'));/*** Constructor** @param mixed $backend A subclass of XML_Query2XML_Driver or* an instance of PEAR DB, PEAR MDB2, ADOdb,* PDO, Net_LDAP2 or Net_LDAP.*/private function __construct($backend){if ($backend instanceof XML_Query2XML_Driver) {$this->_driver = $backend;} else {$this->_driver = XML_Query2XML_Driver::factory($backend);}}/*** Factory method.* As first argument pass an instance of PDO, PEAR DB, PEAR MDB2, ADOdb,* Net_LDAP or an instance of any class that extends XML_Query2XML_Driver:* <code>* <?php* require_once 'XML/Query2XML.php';* $query2xml = XML_Query2XML::factory(* new PDO('mysql://root@localhost/Query2XML_Tests')* );* ?>* </code>** <code>* <?php* require_once 'XML/Query2XML.php';* require_once 'DB.php';* $query2xml = XML_Query2XML::factory(* DB::connect('mysql://root@localhost/Query2XML_Tests')* );* ?>* </code>** <code>* <?php* require_once 'XML/Query2XML.php';* require_once 'MDB2.php';* $query2xml = XML_Query2XML::factory(* MDB2::factory('mysql://root@localhost/Query2XML_Tests')* );* ?>* </code>** <code>* <?php* require_once 'XML/Query2XML.php';* require_once 'adodb/adodb.inc.php';* $adodb = ADONewConnection('mysql');* $adodb->Connect('localhost', 'root', '', 'Query2XML_Tests');* $query2xml = XML_Query2XML::factory($adodb);* ?>* </code>** @param mixed $backend An instance of PEAR DB, PEAR MDB2, ADOdb, PDO,* Net_LDAP or a subclass of XML_Query2XML_Driver.** @return XML_Query2XML A new instance of XML_Query2XML* @throws XML_Query2XML_DriverException If $backend already is a PEAR_Error.* @throws XML_Query2XML_ConfigException If $backend is not an instance of a* child class of DB_common, MDB2_Driver_Common, ADOConnection* PDO, Net_LDAP or XML_Query2XML_Driver.*/public static function factory($backend){return new XML_Query2XML($backend);}/*** Register a prefix that can be used in all value specifications.** @param string $prefix The prefix name. This must be a single chracter.* @param string $className The name of the Data Class. This class has* to extend XML_Query2XML_Data.* @param string $filePath The path to the file that contains the Command* class. This argument is optional.** @return void* @throws XML_Query2XML_ConfigException Thrown if $prefix is not a string* or has a length other than 1.*/public function registerPrefix($prefix, $className, $filePath = ''){if (!is_string($prefix) || strlen($prefix) != 1) {throw new XML_Query2XML_ConfigException('Prefix name has to be a single character');}$this->_prefixes[$prefix] = array($filePath,$className);}/*** Unregister a prefix.** @param string $prefix The prefix name.** @return void*/public function unregisterPrefix($prefix){unset($this->_prefixes[$prefix]);}/*** Unregister all prefixes.** @return void*/public function unregisterAllPrefixes(){$this->_prefixes = array();}/*** Set a global option.* Currently the following global options are available:** hidden_container_prefix: The prefix to use for container elements that are* to be removed before the DOMDocument before it is returned by* {@link XML_Query2XML::getXML()}. This has to be a non-empty string.* The default value is '__'.** @param string $option The name of the option* @param mixed $value The option value** @return void* @throws XML_Query2XML_ConfigException If the configuration option* does not exist or if the value is invalid for that option*/public function setGlobalOption($option, $value){switch ($option) {case 'hidden_container_prefix':if (is_string($value) && strlen($value) > 0) {// unit test: setGlobalOption/hidden_container_prefix.phpt$this->_globalOptions[$option] = $value;} else {/** unit test: setGlobalOption/* configException_hidden_container_prefix_wrongTypeObject.phpt* configException_hidden_container_prefix_wrongTypeEmptyStr.phpt*/throw new XML_Query2XML_ConfigException('The value for the hidden_container_prefix option '. 'has to be a non-empty string');}break;default:// unit tests: setGlobalOption/configException_noSuchOption.phptthrow new XML_Query2XML_ConfigException('No such global option: ' . $option);}}/*** Returns the current value for a global option.* See {@link XML_Query2XML::setGlobalOption()} for a list* of available options.** @param string $option The name of the option** @return mixed The option's value* @throws XML_Query2XML_ConfigException If the option does not exist*/public function getGlobalOption($option){if (!isset($this->_globalOptions[$option])) {// unit test: getGlobalOption/configException_noSuchOption.phptthrow new XML_Query2XML_ConfigException('No such global option: ' . $option);}// unit test: getGlobalOption/hidden_container_prefix.phptreturn $this->_globalOptions[$option];}/*** Enable the logging of debug messages.* This will include all queries sent to the database.* Example:* <code>* <?php* require_once 'Log.php';* require_once 'XML/Query2XML.php';* $query2xml = XML_Query2XML::factory(MDB2::connect($dsn));* $debugLogger = Log::factory('file', 'out.log', 'XML_Query2XML');* $query2xml->enableDebugLog($debugLogger);* ?>* </code>* Please see {@link http://pear.php.net/package/Log} for details on PEAR Log.** @param mixed $log Most likely an instance of PEAR Log but any object* that provides a method named 'log' is accepted.** @return void*/public function enableDebugLog($log){// unit test: enableDebugLog/enableDebugLog.phpt$this->_debugLogger = $log;$this->_debug = true;}/*** Disable the logging of debug messages** @return void*/public function disableDebugLog(){// unit test: disableDebugLog/disableDebugLog.phpt$this->_debug = false;}/*** Start profiling.** @return void*/public function startProfiling(){// unit tests: startProfile/startProfile.phpt$this->_profiling = true;$this->_profile = array('queries' => array(),'start' => microtime(1),'stop' => 0,'duration' => 0,'dbStop' => 0,'dbDuration' => 0);}/*** Stop profiling.** @return void*/public function stopProfiling(){// unit test: stopProfile/stopProfile.phpt$this->_profiling = false;if (isset($this->_profile['start']) && $this->_profile['stop'] == 0) {$this->_profile['stop'] = microtime(1);$this->_profile['duration'] =$this->_profile['stop'] - $this->_profile['start'];}}/*** Returns all raw profiling data.* In 99.9% of all cases you will want to use getProfile().** @see getProfile()* @return array*/public function getRawProfile(){// unit test: getRawProfile/getRawProfile.phpt$this->stopProfiling();return $this->_profile;}/*** Returns the profile as a single multi line string.** @return string The profiling data.*/public function getProfile(){// unit test: getProfile/getProfile.phpt$this->stopProfiling();if (count($this->_profile) === 0) {return '';}$ret = 'COUNT AVG_DURATION DURATION_SUM SQL' . "\n";foreach ($this->_profile['queries'] as $sql => $value) {$durationSum = 0.0;$durationCount = 0;$runTimes =& $this->_profile['queries'][$sql]['runTimes'];foreach ($runTimes as $runTime) {$durationSum += ($runTime['stop'] - $runTime['start']);++$durationCount;}if ($durationCount == 0) {// so that division does not fail$durationCount = 1;}$durationAverage = $durationSum / $durationCount;$ret .= str_pad($this->_profile['queries'][$sql]['count'], 5). ' '. substr($durationAverage, 0, 12). ' '. substr($durationSum, 0, 12). ' '. $sql . "\n";}$ret .= "\n";$ret .= 'TOTAL_DURATION: ' . $this->_profile['duration'] . "\n";$ret .= 'DB_DURATION: ' . $this->_profile['dbDuration'] . "\n";return $ret;}/*** Calls {@link XML_Query2XML::stopProfiling()} and then clears the profiling* data by resetting a private property.** @return void*/public function clearProfile(){// unit test: clearProfile/clearProfile.phpt$this->stopProfiling();$this->_profile = array();}/*** Transforms the data retrieved by a single SQL query into flat XML data.** This method will return a new instance of DOMDocument. The column names* will be used as element names.** Example:* <code>* <?php* require_once 'XML/Query2XML.php';* $query2xml = XML_Query2XML::factory(MDB2::connect($dsn));* $dom = $query2xml->getFlatXML(* 'SELECT * FROM artist',* 'music_library',* 'artist'* );* ?>* </code>** @param string $sql The query string.* @param string $rootTagName The name of the root tag; this argument is optional* (default: 'root').* @param string $rowTagName The name of the tag used for each row; this* argument is optional (default: 'row').** @return DOMDocument A new instance of DOMDocument.* @throws XML_Query2XML_Exception This is the base class for the exception* types XML_Query2XML_DBException and* XML_Query2XML_XMLException. By catching* XML_Query2XML_Exception you can catch all* exceptions this method will ever throw.* @throws XML_Query2XML_DBException If a database error occurrs.* @throws XML_Query2XML_XMLException If an XML error occurrs - most likely* $rootTagName or $rowTagName is not a valid* element name.*/public function getFlatXML($sql, $rootTagName = 'root', $rowTagName = 'row'){/** unit tests: getFlatXML/*.phpt*/$dom = self::_createDOMDocument();$rootTag = self::_addNewDOMChild($dom, $rootTagName, 'getFlatXML');$records = $this->_getAllRecords(array('query' => $sql), 'getFlatXML', $sql);foreach ($records as $record) {$rowTag = self::_addNewDOMChild($rootTag, $rowTagName, 'getFlatXML');foreach ($record as $field => $value) {self::_addNewDOMChild($rowTag,$field,'getFlatXML',self::_utf8encode($value));}}return $dom;}/*** Transforms your SQL data retrieved by one or more queries into complex and* highly configurable XML data.** This method will return a new instance of DOMDocument.* Please see the <b>{@tutorial XML_Query2XML.pkg tutorial}</b> for details.** @param mixed $sql A string an array or the boolean value false.* @param array $options Options for the creation of the XML data stored in an* associative, potentially mutli-dimensional array* (please see the tutorial).** @return DOMDocument The XML data as a new instance of DOMDocument.* @throws XML_Query2XML_Exception This is the base class for the exception types* XML_Query2XML_DBException, XML_Query2XML_XMLException* and XML_Query2XML_ConfigException. By catching* XML_Query2XML_Exception you can catch all exceptions* this method will ever throw.* @throws XML_Query2XML_DBException If a database error occurrs.* @throws XML_Query2XML_XMLException If an XML error occurrs - most likely* an invalid XML element name.* @throws XML_Query2XML_ConfigException If some configuration options passed* as second argument are invalid or missing.*/public function getXML($sql, $options){/** unit tests: getXML/*.phpt*/// the default root tag name is 'root'if (isset($options['rootTag'])) {$rootTagName = $options['rootTag'];} else {$rootTagName = 'root';}$dom = self::_createDOMDocument();$rootTag = self::_addNewDOMChild($dom, $rootTagName, '[rootTag]');$options['sql'] = $sql;if ($options['sql'] === false) {$options['sql'] = '';}$this->_preprocessOptions($options);/* Used to store the information which element has been created* for which ID column value.*/$tree = array();if ($sql === false) {$records = array(array()); // one empty record} else {$records = $this->_applySqlOptionsToRecord($options,$emptyRecord = array());}foreach ($records as $key => $record) {$tag = $this->_getNestedXMLRecord($records[$key], $options, $dom, $tree);/* _getNestedXMLRecord() returns false if an element already existed for* the current ID column value.*/if ($tag !== false) {$rootTag->appendChild($tag);}}$this->_stopDBProfiling();self::_removeContainers($dom,$this->getGlobalOption('hidden_container_prefix'));return $dom;}/*** Perform pre-processing on $options.* This is a recursive method; it will call itself for every complex element* specification and every complex attribute specification found.** @param array &$options An associative array* @param string $context Indecates whether an element or an attribute is* to be processed.** @return void* @throws XML_Query2XML_ConfigException If a mandatory option is missing* or any option is defined incorrectly.*/private function _preprocessOptions(&$options, $context = 'elements'){if (!isset($options['--q2x--path'])) {// things to do only at the root level$options['--q2x--path'] = '';if (!isset($options['rowTag'])) {$options['rowTag'] = 'row';}if (!isset($options['idColumn'])) {/** unit test: _preprocessOptions/* throwConfigException_idcolumnOptionMissing.phpt*/throw new XML_Query2XML_ConfigException('The configuration option "idColumn" '. 'is missing on the root level.');}}foreach (array('encoder', 'mapper') as $option) {if (isset($options[$option])) {if (is_string($options[$option]) &&strpos($options[$option], '::') !== false) {$options[$option] = explode('::', $options[$option]);}if ($options[$option] !== false&&!($option == 'encoder' && $options[$option] === null)&&!($option == 'mapper' && $options[$option] == false)&&!is_callable($options[$option], false, $callableName)) {/** Only check whether $options['encoder'] is callable if it's not* set to:* - false (don't use an encoder)* - null (use self::_utf8encode()).** unit test: _preprocessOptions/* throwConfigException_encoderNotCallableStaticMethod1.phpt* throwConfigException_encoderNotCallableStaticMethod2.phpt* throwConfigException_encoderNotCallableNonstaticMethod.phpt* throwConfigException_encoderNotCallableFunction.phpt*** Only check whether $options['mapper'] is callable if* - $options['mapper'] != false** unit tests: _preprocessOptions/* throwConfigException_mapperNotCallableStaticMethod1.phpt* throwConfigException_mapperNotCallableStaticMethod2.phpt* throwConfigException_mapperNotCallableNonstaticMethod.phpt* throwConfigException_mapperNotCallableFunction.phpt*/throw new XML_Query2XML_ConfigException($options['--q2x--path'] . '[' . $option . ']: The '. 'method/function "' . $callableName . '" is not callable.');}} else {$options[$option] = null;}}if ($context == 'elements') {foreach (array('elements', 'attributes') as $option) {if (isset($options[$option])) {if (!is_array($options[$option])) {/** unit test: _preprocessOptions/* throwConfigException_attributesOptionWrongType.phpt* throwConfigException_elementsOptionWrongType.phpt*/throw new XML_Query2XML_ConfigException($options['--q2x--path'] . '[' . $option . ']: '. 'array expected, ' . gettype($options[$option]). ' given.');}foreach ($options[$option] as $key => $columnStr) {$configPath = $options['--q2x--path'] . '[' . $option. '][' . $key . ']';if (is_string($columnStr)) {$options[$option][$key] =$this->_buildCommandChain($columnStr, $configPath);if (is_numeric($key) &&is_object($options[$option][$key])) {/** unit test: _preprocessOptions/* throwConfigException_prefix_noArrayKey.phpt*/throw new XML_Query2XML_ConfigException($configPath . ': the element name has to be '. 'specified as the array key when prefixes '. 'are used within the value specification');}} elseif (is_array($columnStr)) {$options[$option][$key]['--q2x--path'] = $configPath;// encoder option used by elements as well as attributesif (!array_key_exists('encoder',$options[$option][$key])) {$options[$option][$key]['encoder'] =$options['encoder'];}if ($option == 'elements') {// these options are only used by elementsif (!isset($options[$option][$key]['rootTag']) ||$options[$option][$key]['rootTag'] == '') {/** If rootTag is not set or an empty string:* create a hidden root tag*/$options[$option][$key]['rootTag'] =$this->getGlobalOption('hidden_container_prefix') . $key;}if (!isset($options[$option][$key]['rowTag'])) {$options[$option][$key]['rowTag'] = $key;}foreach (array('mapper', 'idColumn') as $option2) {if (!array_key_exists($option2,$options[$option][$key])) {$options[$option][$key][$option2] =$options[$option2];}}}$this->_preprocessOptions($options[$option][$key],$option);} elseif (self::_isCallback($columnStr)) {if (is_numeric($key)) {/** unit test: _preprocessOptions/* throwConfigException_callbackInterface_* noArrayKey.phpt*/throw new XML_Query2XML_ConfigException($configPath . ': the element name has to be '. 'specified as the array key when the value '. 'is specified using an instance of '. 'XML_Query2XML_Callback.');}} else {/** $columnStr is neither a string, an array or an* instance of XML_Query2XML_Callback.** unit tests:* _getNestedXMLRecord/* throwConfigException_attributeSpecWrongType.phpt* _preprocessOptions/* throwConfigException_callbackInterface_* complexAttributeSpec.phpt* simpleAttributeSpec.phpt* simpleElementSpec.phpt*/throw new XML_Query2XML_ConfigException($configPath . ': array, string or instance of'. ' XML_Query2XML_Callback expected, '. gettype($columnStr). ' given.');}}}} // end of foreach (array('elements', 'attributes'))} else {// $context == 'attributes'if (!isset($options['value'])) {/** the option "value" is mandatory* unit test: _preprocessOptions/* throwConfigException_valueOptionMissing.phpt*/throw new XML_Query2XML_ConfigException($options['--q2x--path'] . '[value]: Mandatory option "value" '. 'missing from the complex attribute specification.');}}$opt = array('value', 'condition', 'dynamicRowTag', 'idColumn');foreach ($opt as $option) {if (isset($options[$option])) {if (is_string($options[$option])) {$options[$option] = $this->_buildCommandChain($options[$option],$options['--q2x--path'] . '[value]');} elseif (!self::_isCallback($options[$option]) &&!($option == 'idColumn' && $options[$option] === false)) {/** unit tests:* _preprocessOptions/* throwConfigException_callbackInterface_* complexElementSpec.phpt* condition.phpt* idColumn.phpt*/throw new XML_Query2XML_ConfigException($options['--q2x--path'] . '[' . $option . ']: string or'. ' instance of XML_Query2XML_Callback expected, '. gettype($options[$option]). ' given.');}}}if (isset($options['query'])) {$options['sql'] = $options['query'];}if (isset($options['sql'])) {// we will pre-process $options['sql_options'] firstif (isset($options['query_options'])) {$options['sql_options'] = $options['query_options'];}if (!isset($options['sql_options'])) {$options['sql_options'] = array();}$sql_options = array('cached', 'single_record', 'merge', 'merge_master', 'merge_selective');foreach ($sql_options as $option) {if (!isset($options['sql_options'][$option])) {$options['sql_options'][$option] = false;}}if (isset($options['sql_options']['uncached'])) {$options['sql_options']['cached'] =!$options['sql_options']['uncached'];}if ($options['sql_options']['cached']) {if (!is_array($options['sql'])) {$options['sql'] = array('query' => $options['sql']);}if (isset($options['sql']['driver'])) {$driver = $options['sql']['driver'];} else {$driver = $this->_driver;}if (!class_exists('XML_Query2XML_Driver_Caching') ||!($driver instanceof XML_Query2XML_Driver_Caching)) {include_once 'XML/Query2XML/Driver/Caching.php';$options['sql']['driver'] = new XML_Query2XML_Driver_Caching($driver);}}if ($options['sql_options']['merge_selective'] !== false &&!is_array($options['sql_options']['merge_selective'])) {/** unit test: _preprocessOptions/* throwConfigException_mergeselectiveOptionWrongType.phpt*/throw new XML_Query2XML_ConfigException($options['--q2x--path'] . '[sql_options][merge_selective]: '. 'array expected, '. gettype($options['sql_options']['merge_selective']) . ' given.');}// end of pre-processing of $options['sql_options']if (is_array($options['sql']) &&isset($options['sql']['driver']) &&$options['sql']['driver'] instanceof XML_Query2XML_Driver) {$query = $options['sql']['driver']->preprocessQuery($options['sql'],$options['--q2x--path'] . '[sql]');} else {$query = $this->_driver->preprocessQuery($options['sql'],$options['--q2x--path'] . '[sql]');}$options['--q2x--query_statement'] = $query;if (is_array($options['sql']) &&isset($options['sql']['driver']) &&!($options['sql']['driver'] instanceof XML_Query2XML_Driver)) {/** unit test: _preprocessOptions* throwConfigException_sqlOptionWrongType.phpt*/throw new XML_Query2XML_ConfigException($options['--q2x--path'] . '[sql][driver]: '. 'instance of XML_Query2XML_Driver expected, '. gettype($options['sql']['driver']) . ' given.');}if (is_array($options['sql'])) {if (isset($options['sql']['data'])) {if (is_array($options['sql']['data'])) {foreach ($options['sql']['data'] as $key => $data) {if (is_string($data)) {$options['sql']['data'][$key] =$this->_buildCommandChain($options['sql']['data'][$key],$options['--q2x--path']. '[sql][data][' . $key . ']');} elseif (!self::_isCallback($data)) {/** unit tests: _preprocessOptions/* throwConfigException_callbackInterface_data.phpt*/throw new XML_Query2XML_ConfigException($options['--q2x--path'] . '[sql][data][' . $key. ']: string or'. ' instance of XML_Query2XML_Callback expected,'. ' ' . gettype($options['sql']['data'][$key]). ' given.');}}} else {/** unit test: _preprocessOptions/* throwConfigException_dataOptionWrongType.phpt*/throw new XML_Query2XML_ConfigException($options['--q2x--path'] . '[sql][data]: array expected, '. gettype($options['sql']['data']) . ' given.');}}}} // end of if (isset($options['sql'])}/*** Private recursive method that creates the nested XML elements from a record.** getXML calls this method for every row in the initial result set.* The $tree argument deserves some more explanation. All DOMNodes are stored* in $tree the way they appear in the XML document. The same hirachy needs to be* built so that we can know if a DOMNode that corresponds to a column ID of 2 is* already a child node of a certain XML element. Let's have a look at an example* to clarify this:* <code>* <music_library>* <artist>* <artistid>1</artistid>* <albums>* <album>* <albumid>1</albumid>* </album>* <album>* <albumid>2</albumid>* </album>* </albums>* </artist>* <artist>* <artistid>3</artistid>* <albums />* </artist>* </music_library>* </code>* would be represended in the $tree array as something like this:* <code>* array (* [1] => array (* [tag] => DOMElement Object* [elements] => array (* [albums] => array (* [1] => array (* [tag] => DOMElement Object* )* [2] => array (* [tag] => DOMElement Object* )* )* )* )* [2] => array (* [tag] => DOMElement Object* [elements] => array* (* [albums] => array ()* )* )* )* </code>* The numbers in the square brackets are column ID values.** @param array $record An associative array representing a record;* column names must be used as keys.* @param array &$options An array containing the options for this nested* element; this will be a subset of the array* originally passed to getXML().* @param DOMDocument $dom An instance of DOMDocument.* @param array &$tree An associative multi-dimensional array, that is* used to store the information which tag has* already been created for a certain ID column* value. It's format is:* Array(* "$id1" => Array(* 'tag' => DOMElement,* 'elements' => Array(* "$id2" => Array(* 'tag' => DOMElement,* 'elements' => Array( ... )* ),* "$id3" => ...* )* )* )** @return mixed The XML element's representation as a new instance of* DOMNode or the boolean value false (meaning no* new tag was created).* @throws XML_Query2XML_DBException Bubbles up through this method if thrown by* - _processComplexElementSpecification()* @throws XML_Query2XML_XMLException Bubbles up through this method if thrown by* - _createDOMElement()* - _setDOMAttribute* - _appendTextChildNode()* - _addNewDOMChild()* - _addDOMChildren()* - _processComplexElementSpecification()* - _expandShortcuts()* - _executeEncoder()* @throws XML_Query2XML_ConfigException Thrown if* - $options['idColumn'] is not set* - $options['elements'] is set but not an array* - $options['attributes'] is set but not an array* Bubbles up through this method if thrown by* - _applyColumnStringToRecord()* - _processComplexElementSpecification()* - _expandShortcuts()* @throws XML_Query2XML_Exception Bubbles up through this method if thrown by* - _expandShortcuts()* - _applyColumnStringToRecord()*/private function _getNestedXMLRecord($record, &$options, $dom, &$tree){// the default row tag name is 'row'if (isset($options['dynamicRowTag'])) {$rowTagName = $this->_applyColumnStringToRecord($options['dynamicRowTag'],$record,$options['--q2x--path'] . '[dynamicRowTag]');} else {$rowTagName = $options['rowTag'];}if ($options['idColumn'] === false) {static $uniqueIdCounter = 0;$id = ++$uniqueIdCounter;} else {$id = $this->_applyColumnStringToRecord($options['idColumn'],$record,$options['--q2x--path'] . '[idColumn]');if ($id === null) {// the ID column is NULLreturn false;} elseif (is_object($id) || is_array($id)) {/** unit test: _getNestedXMLRecord/* throwConfigException_idcolumnOptionWrongTypeArray.phpt* throwConfigException_idcolumnOptionWrongTypeObject.phpt*/throw new XML_Query2XML_ConfigException($options['--q2x--path'] . '[idColumn]: Must evaluate to a '. 'value that is not an object or an array.');}}/* Is there already an identical tag (identity being determined by the* value of the ID-column)?*/if (isset($tree[$id])) {if (isset($options['elements'])) {foreach ($options['elements'] as $tagName => $column) {if (is_array($column)) {$this->_processComplexElementSpecification($record,$options['elements'][$tagName],$tree[$id],$tagName);}}}/** We return false because $tree[$id]['tag'] is already* a child of the parent element.*/return false;} else {$tree[$id] = array();if (isset($options['value'])) {$parsedValue = $this->_applyColumnStringToRecord($options['value'],$record,$options['--q2x--path'] . '[value]');if (!$this->_evaluateCondition($parsedValue, $options['value'])) {// this element is to be skippedreturn false;}}if (isset($options['condition'])) {$continue = $this->_applyColumnStringToRecord($options['condition'],$record,$options['--q2x--path'] . '[condition]');if (!$continue) {// this element is to be skippedreturn false;}}$tree[$id]['tag'] = self::_createDOMElement($dom,$rowTagName,$options['--q2x--path'] . '[rowTag/dynamicRowTag]');$tag = $tree[$id]['tag'];// add attributesif (isset($options['attributes'])) {if (!isset($options['processed'])) {$options['attributes'] = self::_expandShortcuts($options['attributes'],$record,$options['mapper'],$options['--q2x--path'] . '[attributes]');}foreach ($options['attributes'] as $attributeName => $column) {if (is_array($column)) {// complex attribute specification$this->_processComplexAttributeSpecification($attributeName, $record, $column, $tree[$id]['tag']);} else {// simple attribute specifications$attributeValue = $this->_applyColumnStringToRecord($column,$record,$options['--q2x--path']. '[attributes][' . $attributeName . ']');if ($this->_evaluateCondition($attributeValue, $column)) {self::_setDOMAttribute($tree[$id]['tag'],$attributeName,self::_executeEncoder($attributeValue,$options),$options['--q2x--path']. '[attributes][' . $attributeName . ']');}}}}if (isset($options['value'])) {if ($parsedValue instanceof DOMNode || is_array($parsedValue)) {/** The value returned from _applyColumnStringToRecord() and* stored in $parsedValue is an instance of DOMNode or an* array of DOMNode instances. _addDOMChildren() will handle* both.*/self::_addDOMChildren($tree[$id]['tag'],$parsedValue,$options['--q2x--path'] . '[value]',true);} else {if ($parsedValue !== false && !is_null($parsedValue)) {self::_appendTextChildNode($tree[$id]['tag'],self::_executeEncoder($parsedValue,$options),$options['--q2x--path'] . '[value]');}}}// add child elementsif (isset($options['elements'])) {if (!isset($options['processed'])) {$options['elements'] = self::_expandShortcuts($options['elements'],$record,$options['mapper'],$options['--q2x--path'] . '[elements]');}foreach ($options['elements'] as $tagName => $column) {if (is_array($column)) {// complex element specification$this->_processComplexElementSpecification($record,$options['elements'][$tagName],$tree[$id],$tagName);} else {// simple element specification$tagValue = $this->_applyColumnStringToRecord($column,$record,$options['--q2x--path'] . '[elements][' . $tagName . ']');if ($this->_evaluateCondition($tagValue, $column)) {if ($tagValue instanceof DOMNode ||is_array($tagValue)) {/** The value returned from* _applyColumnStringToRecord() and stored in* $tagValue is an instance of DOMNode or an array* of DOMNode instances. self::_addDOMChildren()* will handle both.*/self::_addDOMChildren(self::_addNewDOMChild($tree[$id]['tag'],$tagName,$options['--q2x--path']. '[elements][' . $tagName . ']'),$tagValue,$options['--q2x--path']. '[elements][' . $tagName . ']',true);} else {self::_addNewDOMChild($tree[$id]['tag'],$tagName,$options['--q2x--path']. '[elements][' . $tagName . ']',self::_executeEncoder($tagValue,$options));}}}}}// some things only need to be done once$options['processed'] = true;/** We return $tree[$id]['tag'] because it needs to be added to it's* parent; this is to be handled by the method that called* _getNestedXMLRecord().*/return $tree[$id]['tag'];}}/*** Private method that will expand asterisk characters in an array* of simple element specifications.** This method gets called to handle arrays specified using the 'elements'* or the 'attributes' option. An element specification that contains an* asterisk will be duplicated for each column present in $record.* Please see the {@tutorial XML_Query2XML.pkg tutorial} for details.** @param Array &$elements An array of simple element specifications.* @param Array &$record An associative array that represents a single* record.* @param mixed $mapper A valid argument for call_user_func(), a full method* method name (e.g. "MyMapperClass::map") or a value* that == false for no special mapping at all.* @param string $configPath The config path; used for exception messages.** @return Array The extended array.* @throws XML_Query2XML_ConfigException If only the column part but not the* explicitly defined tagName part contains an asterisk.* @throws XML_Query2XML_Exception Will bubble up if it is thrown by* _mapSQLIdentifierToXMLName(). This should never* happen as _getNestedXMLRecord() already checks if* $mapper is callable.* @throws XML_Query2XML_XMLException Will bubble up if it is thrown by* _mapSQLIdentifierToXMLName() which will happen if the* $mapper function called, throws any exception.*/private function _expandShortcuts(&$elements, &$record, $mapper, $configPath){$newElements = array();foreach ($elements as $tagName => $column) {if (is_numeric($tagName)) {$tagName = $column;}if (!is_array($column) && strpos($tagName, '*') !== false) {// expand all occurences of '*' to all column namesforeach ($record as $columnName => $value) {$newTagName = str_replace('*', $columnName, $tagName);if (is_string($column)) {$newColumn = str_replace('*', $columnName, $column);} elseif (class_exists('XML_Query2XML_Data') &&$column instanceof XML_Query2XML_Data) {$newColumn = clone $column;$callback = $newColumn->getFirstPreProcessor();if (class_exists('XML_Query2XML_Data_Source') &&$callback instanceof XML_Query2XML_Data_Source) {$callback->replaceAsterisks($columnName);}} else {$newColumn =& $column;}// do the mapping$newTagName = self::_mapSQLIdentifierToXMLName($newTagName,$mapper,$configPath . '[' . $tagName . ']');if (!isset($newElements[$newTagName])) {// only if the tagName hasn't already been used$newElements[$newTagName] = $newColumn;}}} else {/** Complex element specifications will always be dealt with here.* We don't want any mapping or handling of the asterisk shortcut* to be done for complex element specifications.*/if (!is_array($column)) {// do the mapping but not for complex element specifications$tagName = self::_mapSQLIdentifierToXMLName($tagName,$mapper,$configPath . '[' . $tagName . ']');}/** explicit specification without an asterisk;* this always overrules an expanded asterisk*/unset($newElements[$tagName]);$newElements[$tagName] = $column;}}return $newElements;}/*** Maps an SQL identifier to an XML name using the supplied $mapper.** @param string $sqlIdentifier The SQL identifier as a string.* @param mixed $mapper A valid argument for call_user_func(), a full* method method name (e.g. "MyMapperClass::map")* or a value that == false for no special mapping* at all.* @param string $configPath The config path; used for exception messages.** @return string The mapped XML name.* @throws XML_Query2XML_Exception If $mapper is not callable. This should never* happen as _getNestedXMLRecord() already checks* if $mapper is callable.* @throws XML_Query2XML_XMLException If the $mapper function called, throws any* exception.*/private function _mapSQLIdentifierToXMLName($sqlIdentifier, $mapper, $configPath){if (!$mapper) {// no mapper was defined$xmlName = $sqlIdentifier;} else {if (is_callable($mapper, false, $callableName)) {try {$xmlName = call_user_func($mapper, $sqlIdentifier);} catch (Exception $e) {/** This will also catch XML_Query2XML_ISO9075Mapper_Exception* if $mapper was "XML_Query2XML_ISO9075Mapper::map".* unit test:* _mapSQLIdentifierToXMLName/throwXMLException.phpt*/throw new XML_Query2XML_XMLException($configPath . ': Could not map "' . $sqlIdentifier. '" to an XML name using the mapper '. $callableName . ': ' . $e->getMessage());}} else {/** This should never happen as _preprocessOptions() already* checks if $mapper is callable. Therefore no unit tests* can be provided for this exception.*/throw new XML_Query2XML_ConfigException($configPath . ': The mapper "' . $callableName. '" is not callable.');}}return $xmlName;}/*** Private method that processes a complex element specification* for {@link XML_Query2XML::_getNestedXMLRecord()}.** @param array &$record The current record.* @param array &$options The current options.* @param array &$tree Associative multi-dimensional array, that is used to* store which tags have already been created* @param string $tagName The element's name.** @return void* @throws XML_Query2XML_XMLException This exception will bubble up* if it is thrown by _getNestedXMLRecord(),* _applySqlOptionsToRecord() or _addDOMChildren().* @throws XML_Query2XML_DBException This exception will bubble up* if it is thrown by _applySqlOptionsToRecord()* or _getNestedXMLRecord().* @throws XML_Query2XML_ConfigException This exception will bubble up* if it is thrown by _applySqlOptionsToRecord()* or _getNestedXMLRecord().* @throws XML_Query2XML_Exception This exception will bubble up if it* is thrown by _getNestedXMLRecord().*/private function _processComplexElementSpecification(&$record, &$options, &$tree,$tagName){$tag = $tree['tag'];if (!isset($tree['elements'])) {$tree['elements'] = array();}if (!isset($tree['elements'][$tagName])) {$tree['elements'][$tagName] = array();$tree['elements'][$tagName]['rootTag'] = self::_addNewDOMChild($tag,$options['rootTag'],$options['--q2x--path'] . '[rootTag]');}$records =& $this->_applySqlOptionsToRecord($options, $record);for ($i = 0; $i < count($records); $i++) {self::_addDOMChildren($tree['elements'][$tagName]['rootTag'],$this->_getNestedXMLRecord($records[$i],$options,$tag->ownerDocument,$tree['elements'][$tagName]),$options['--q2x--path']);}}/*** Private method that processes a complex attribute specification* for {@link XML_Query2XML::_getNestedXMLRecord()}.** A complex attribute specification consists of an associative array* with the keys 'value' (mandatory), 'condition', 'sql' and 'sql_options'.** @param string $attributeName The name of the attribute as it was specified* using the array key of the complex attribute* specification.* @param array &$record The current record.* @param array &$options The complex attribute specification itself.* @param DOMNode $tag The DOMNode to which the attribute is to be* added.** @return void* @throws XML_Query2XML_XMLException This exception will bubble up* if it is thrown by _setDOMAttribute(),* _applyColumnStringToRecord(),* _applySqlOptionsToRecord() or _executeEncoder().* @throws XML_Query2XML_DBException This exception will bubble up* if it is thrown by _applySqlOptionsToRecord().* @throws XML_Query2XML_ConfigException This exception will bubble up* if it is thrown by _applySqlOptionsToRecord() or* _applyColumnStringToRecord(). It will also be thrown* by this method if $options['value'] is not set.*/private function _processComplexAttributeSpecification($attributeName, &$record,&$options, $tag){if (isset($options['condition'])) {$continue = $this->_applyColumnStringToRecord($options['condition'],$record,$options['--q2x--path'] . '[condition]');if (!$continue) {// this element is to be skippedreturn;}}// only fetching a single record makes sense for a single attribute$options['sql_options']['single_record'] = true;$records = $this->_applySqlOptionsToRecord($options, $record);if (count($records) == 0) {/** $options['sql'] was set but the query did not return any records.* Therefore this attribute is to be skipped.*/return;}$attributeRecord = $records[0];$attributeValue = $this->_applyColumnStringToRecord($options['value'],$attributeRecord,$options['--q2x--path'] . '[value]');if ($this->_evaluateCondition($attributeValue, $options['value'])) {self::_setDOMAttribute($tag,$attributeName,self::_executeEncoder($attributeValue, $options),$options['--q2x--path'] . '[value]');}}/*** Private method to apply the givenen sql option to a record.** This method handles the sql options 'single_record',* 'merge', 'merge_master' and 'merge_selective'. Please see the* {@tutorial XML_Query2XML.pkg tutorial} for details.** @param array &$options An associative multidimensional array of options.* @param array &$record The current record as an associative array.** @return array An indexed array of records that are themselves* represented as associative arrays.* @throws XML_Query2XML_ConfigException This exception is thrown if* - a column specified in merge_selective does not exist* in the result set* - it bubbles up from _applyColumnStringToRecord()* @throws XML_Query2XML_DBException This exception will bubble up* if it is thrown by _getAllRecords().* @throws XML_Query2XML_XMLException It will bubble up if it is thrown* by _applyColumnStringToRecord().*/private function _applySqlOptionsToRecord(&$options, &$record){if (!isset($options['sql'])) {return array($record);}$single_record = $options['sql_options']['single_record'];$merge = $options['sql_options']['merge'];$merge_master = $options['sql_options']['merge_master'];$merge_selective = $options['sql_options']['merge_selective'];$sql = $options['sql'];if (is_array($sql)) {if (isset($sql['data'])) {foreach ($sql['data'] as $key => $columnStr) {$sql['data'][$key] = $this->_applyColumnStringToRecord($columnStr,$record,$options['--q2x--path'] . '[sql][data][' . $key . ']');}}}$sqlConfigPath = $options['--q2x--path'] . '[sql]';$records =& $this->_getAllRecords($sql,$sqlConfigPath,$options['--q2x--query_statement']);if ($single_record && isset($records[0])) {$records = array($records[0]);}if (is_array($merge_selective)) {// selective mergeif ($merge_master) {// current records are masterfor ($ii = 0; $ii < count($merge_selective); $ii++) {for ($i = 0; $i < count($records); $i++) {if (!array_key_exists($merge_selective[$ii], $record)) {/* Selected field does not exist in the parent record* (passed as argumnet $record)* unit test: _applySqlOptionsToRecord/* throwConfigException_mergeMasterTrue.phpt*/throw new XML_Query2XML_ConfigException($options['--q2x--path'] . '[sql_options]'. '[merge_selective]['. $ii . ']: The column "'. $merge_selective[$ii] . '" '. 'was not found in the result set.');}if (!array_key_exists($merge_selective[$ii], $records[$i])) {// we are the master, so only if it does not yet exist$records[$i][$merge_selective[$ii]] =$record[$merge_selective[$ii]];}}}} else {// parent record is masterfor ($ii = 0; $ii < count($merge_selective); $ii++) {for ($i = 0; $i < count($records); $i++) {if (!array_key_exists($merge_selective[$ii], $record)) {/* Selected field does not exist in the parent record* (passed as argumnet $record)* unit test: _applySqlOptionsToRecord/* throwConfigException_mergeMasterFalse.phpt*/throw new XML_Query2XML_ConfigException($options['--q2x--path'] . '[sql_options]'. '[merge_selective]['. $ii . ']: The column "'. $merge_selective[$ii] . '" '. 'was not found in the result set.');}// parent is master!$records[$i][$merge_selective[$ii]] =$record[$merge_selective[$ii]];}}}} elseif ($merge) {// regular mergeif ($merge_master) {for ($i = 0; $i < count($records); $i++) {$records[$i] = array_merge($record, $records[$i]);}} else {for ($i = 0; $i < count($records); $i++) {$records[$i] = array_merge($records[$i], $record);}}}return $records;}/*** Private method to apply a column string to a record.* Please see the tutorial for details on the different column strings.** @param string $columnStr A valid column name or an instance of a class* implementing XML_Query2XML_Callback.* @param array &$record The record as an associative array.* @param string $configPath The config path; used for exception messages.** @return mixed A value that can be cast to a string or an instance of DOMNode.* @throws XML_Query2XML_ConfigException Thrown if $columnStr is not* a string or an instance of XML_Query2XML_Callback or if* $record[$columnStr] does not exist (and $columnStr has* no special prefix).* @throws XML_Query2XML_XMLException Thrown if the '&' prefix was used* but the data was not unserializeable, i.e. not valid XML data.*/private function _applyColumnStringToRecord($columnStr, &$record, $configPath){if (self::_isCallback($columnStr)) {$value = $columnStr->execute($record);} elseif (is_string($columnStr)) {if (array_key_exists($columnStr, $record)) {$value = $record[$columnStr];} else {/** unit test:* _applyColumnStringToRecord/throwConfigException_element1.phpt* _applyColumnStringToRecord/throwConfigException_element2.phpt* _applyColumnStringToRecord/throwConfigException_idcolumn.phpt*/throw new XML_Query2XML_ConfigException($configPath . ': The column "' . $columnStr. '" was not found in the result set.');}} else {// should never be reachedthrow new XML_Query2XML_ConfigException($configPath . ': string or instance of XML_Query2XML_Callback'. ' expected, ' . gettype($columnStr) . ' given.');}return $value;}/*** Returns whether $value is to be included in the output.* If $spec is a string an is prefixed by a question mark this method will* return false if $value is null or is a string with a length of zero. In* any other case, this method will return the true.** @param string $value The value.* @param mixed $spec The value specification. This can be a string* or an instance of XML_Query2XML_Callback.** @return boolean Whether $value is to be included in the output.*/private function _evaluateCondition($value, $spec){return !class_exists('XML_Query2XML_Data_Condition') ||!$spec instanceof XML_Query2XML_Data_Condition ||$spec->evaluateCondition($value);}/*** Private method to fetch all records from a result set.** @param mixed $sql The SQL query as a string or an array.* @param string $configPath The config path; used for exception messages.* @param string $queryStatement The query as a string; it will be used for* logging and profiling.** @return array An array of records. Each record itself will be an* associative array.*/private function &_getAllRecords($sql, $configPath, $queryStatement){// $queryStatement will be used for profilingif ($this->_profiling || $this->_debug) {$loggingQuery = $queryStatement;if (is_array($sql) && isset($sql['data']) && is_array($sql['data'])) {$loggingQuery .= '; DATA:' . implode(',', $sql['data']);}$this->_debugStartQuery($loggingQuery, $queryStatement);}if (is_array($sql) && isset($sql['driver'])) {$driver = $sql['driver'];} else {$driver = $this->_driver;}$records = $driver->getAllRecords($sql, $configPath);$this->_debugStopQuery($queryStatement);return $records;}/*** Initializes a query's profile (only used if profiling is turned on).** @param mixed &$sql The SQL query as a string or an array.** @return void* @see startProfiling()*/private function _initQueryProfile(&$sql){if (!isset($this->_profile['queries'][$sql])) {$this->_profile['queries'][$sql] = array('count' => 0,'runTimes' => array());}}/*** Starts the debugging and profiling of the query passed as argument.** @param string $loggingQuery The query statement as it will be logged.* @param string $profilingQuery The query statement as it will be used for* profiling.** @return void*/private function _debugStartQuery($loggingQuery, $profilingQuery){$this->_debug('QUERY: ' . $loggingQuery);if ($this->_profiling) {$this->_initQueryProfile($profilingQuery);++$this->_profile['queries'][$profilingQuery]['count'];$this->_profile['queries'][$profilingQuery]['runTimes'][] = array('start' => microtime(true),'stop' => 0);}}/*** Ends the debugging and profiling of the query passed as argument.** @param string $profilingQuery The query statement as it will be used for* profiling.** @return void*/private function _debugStopQuery($profilingQuery){$this->_debug('DONE');if ($this->_profiling) {$this->_initQueryProfile($profilingQuery);$lastIndex =count($this->_profile['queries'][$profilingQuery]['runTimes']) - 1;$this->_profile['queries'][$profilingQuery]['runTimes'][$lastIndex]['stop'] =microtime(true);}}/*** Stops the DB profiling.* This will set $this->_profile['dbDuration'].** @return void*/private function _stopDBProfiling(){if ($this->_profiling && isset($this->_profile['start'])) {$this->_profile['dbStop'] = microtime(1);$this->_profile['dbDuration'] =$this->_profile['dbStop'] - $this->_profile['start'];}}/*** Private method used to log debug messages.* This method will do no logging if $this->_debug is set to false.** @param string $msg The message to log.** @return void* @see _debugLogger* @see _debug*/private function _debug($msg){if ($this->_debug) {$this->_debugLogger->log($msg);}}/*** Returns whether $object is an instance of XML_Query2XML_Callback.** @param mixed $object The variable to check.** @return boolean*/private static function _isCallback($object){return is_object($object) &&interface_exists('XML_Query2XML_Callback') &&$object instanceof XML_Query2XML_Callback;}/*** Parse specifications that use the prifixes ?, &, =, ^, :, or #.** This method will produce a number of chained Data Class objects all of* which be an instance of the abstract class XML_Query2XML_Data.** @param string $columnStr The original specification.* @param string $configPath The config path; used for exception messages.** @return mixed An instance of XML_Query2XML_Callback or a column* name as a string.* @throws XML_Query2XML_ConfigException Bubbles up through this method if* thrown by any of the command class* constructors.*/private function _buildCommandChain($columnStr, $configPath){$prefixList = implode('', array_keys($this->_prefixes));if (ltrim($columnStr, $prefixList) == $columnStr) {return $columnStr;}$firstCallback = null;for ($i = 0; $i < strlen($columnStr); $i++) {$prefix = substr($columnStr, $i, 1);if (isset($this->_prefixes[$prefix])) {$columnSubStr = substr($columnStr, $i + 1);$filePath = $this->_prefixes[$prefix][0];$className = $this->_prefixes[$prefix][1];if ($columnSubStr === false) {$columnSubStr = '';}if ($filePath) {include_once $filePath;}if (!in_array('XML_Query2XML_Data',class_parents($className))) {throw new XML_Query2XML_ConfigException($configPath . ': Prefix class ' . $className . ' does ' .'not extend XML_Query2XML_Data.');}if (in_array('XML_Query2XML_Data_Source',class_parents($className))) {// data source prefix$callback = call_user_func_array(array($className, 'create'),array($columnSubStr, $configPath));} else {// data processing prefix$callback = call_user_func_array(array($className, 'create'),array(null, $configPath));if (ltrim($columnSubStr, $prefixList) == $columnSubStr) {// no more prefixes: ColumnValue is the default data sourceinclude_once 'XML/Query2XML/Data/Source/ColumnValue.php';$callback->setPreProcessor(new XML_Query2XML_Data_Source_ColumnValue($columnSubStr,$configPath));}}if (is_null($firstCallback)) {$firstCallback = $callback;} else {if ($callback instanceof XML_Query2XML_Data_Condition &&!($firstCallback instanceof XML_Query2XML_Data_Condition)) {throw new XML_Query2XML_ConfigException($configPath . ': conditional prefixes always have to '. 'go first.');}$firstCallback->getFirstPreProcessor()->setPreProcessor($callback);}if ($firstCallback->getFirstPreProcessor()instanceof XML_Query2XML_Data_Source) {// there can only be one data sourcebreak;}} else {break;}}if (is_null($firstCallback)) {return $columnStr;} else {return $firstCallback;}}/*** Creates a new instance of DOMDocument.* '1.0' is passed as first argument and 'UTF-8' as second to the* DOMDocument constructor.** @return DOMDocument The new instance.*/private static function _createDOMDocument(){return new DOMDocument('1.0', 'UTF-8');}/*** Create and then add a new child element.** @param DOMNode $element The parent DOMNode the new DOM element should be* appended to.* @param string $name The tag name of the new element.* @param string $configPath The config path; used for exception messages.* @param string $value The value of a child text node. This argument is* optional. The default is the boolean value false,* which means that no child text node will be* appended.** @return DOMNode The newly created DOMNode instance that was appended* to $element.* @throws XML_Query2XML_XMLException This exception will bubble up if it is* thrown by _createDOMElement().*/private static function _addNewDOMChild(DOMNode $element, $name, $configPath,$value = false){if ($element instanceof DOMDocument) {$dom = $element;} else {$dom = $element->ownerDocument;}$child = self::_createDOMElement($dom, $name, $configPath, $value);$element->appendChild($child);return $child;}/*** Helper method to create a new instance of DOMNode** @param DOMDocument $dom An instance of DOMDocument. It's* createElement() method is used to create the* new DOMNode instance.* @param string $name The tag name of the new element.* @param string $configPath The config path; used for exception messages.* @param string $value The value of a child text node. This argument* is optional. The default is the boolean value* false, which means that no child text node will* be appended.** @return DOMNode An instance of DOMNode.* @throws XML_Query2XML_XMLException If $name is an invalid XML identifier.* Also it will bubble up if it is thrown by* _appendTextChildNode().*/private static function _createDOMElement(DOMDocument $dom, $name, $configPath,$value = false){try {$element = $dom->createElement($name);} catch(DOMException $e) {/** unit tests:* _createDOMElement/throwXMLException_elementInvalid1.phpt* _createDOMElement/throwXMLException_elementInvalid2.phpt* _createDOMElement/throwXMLException_roottagOptionInvalid1.phpt* _createDOMElement/throwXMLException_roottagOptionInvalid2.phpt* _createDOMElement/throwXMLException_rowtagOptionInvalid.phpt*/throw new XML_Query2XML_XMLException($configPath . ': "' . $name . '" is an invalid XML element name: '. $e->getMessage(),$e);}self::_appendTextChildNode($element, $value, $configPath);return $element;}/*** Append a new child text node to $element.* $value must already be UTF8-encoded; this is to be handled* by self::_executeEncoder() and $options['encoder'].** This method will not create and append a child text node* if $value === false || is_null($value).** @param DOMNode $element An instance of DOMNode* @param string $value The value of the text node.* @param string $configPath The config path; used for exception messages.** @return void* @throws XML_Query2XML_XMLException Any lower-level DOMException will* wrapped and re-thrown as a XML_Query2XML_XMLException. This* will happen if $value cannot be UTF8-encoded for some reason.* It will also be thrown if $value is an object or an array* (and can therefore not be converted into a string).*/private static function _appendTextChildNode(DOMNode $element,$value,$configPath){if ($value === false || is_null($value)) {return;} elseif (is_object($value) || is_array($value)) {/** Objects and arrays cannot be cast* to a string without an error.** unit test:* _appendTextChildNode/throwXMLException.phpt*/throw new XML_Query2XML_XMLException($configPath . ': A value of the type ' . gettype($value). ' cannot be used for a text node.');}$dom = $element->ownerDocument;try {$element->appendChild($dom->createTextNode($value));} catch(DOMException $e) {// this should never happen as $value is UTF-8 encodedthrow new XML_Query2XML_XMLException($configPath . ': "' . $value . '" is not a vaild text node: '. $e->getMessage(),$e);}}/*** Set the attribute $name with a value of $value for $element.* $value must already be UTF8-encoded; this is to be handled* by self::_executeEncoder() and $options['encoder'].** @param DOMNode $element An instance of DOMNode* @param string $name The name of the attribute to set.* @param string $value The value of the attribute to set.* @param string $configPath The config path; used for exception messages.** @return void* @throws XML_Query2XML_XMLException Any lower-level DOMException will be* wrapped and re-thrown as a XML_Query2XML_XMLException. This* will happen if $name is not a valid attribute name. It will* also be thrown if $value is an object or an array (and can* therefore not be converted into a string).*/private static function _setDOMAttribute(DOMNode $element,$name,$value,$configPath){if (is_object($value) || is_array($value)) {/** Objects and arrays cannot be cast* to a string without an error.** unit test:* _setDOMAttribute/throwXMLException.phpt*/throw new XML_Query2XML_XMLException($configPath . ': A value of the type ' . gettype($value). ' cannot be used for an attribute value.');}try {$element->setAttribute($name, $value);} catch(DOMException $e) {// no unit test available for this onethrow new XML_Query2XML_XMLException($configPath . ': "' . $name . '" is an invalid XML attribute name: '. $e->getMessage(),$e);}}/*** Adds one or more child nodes to an existing DOMNode instance.** @param DOMNode $base An instance of DOMNode.* @param mixed $children An array of DOMNode instances or* just a single DOMNode instance.* Boolean values of false are always ignored.* @param string $configPath The config path; used for exception messages.* @param boolean $import Whether DOMDocument::importNode() should be called* for $children. This is necessary if the instance(s)* passed as $children was/were created using a* different DOMDocument instance. This argument is* optional. The default is false.** @return void* @throws XML_Query2XML_XMLException If one of the specified children* is not one of the following: an instance of DOMNode,* the boolean value false, or an array containing* these two.*/private static function _addDOMChildren(DOMNode $base,$children,$configPath,$import = false){if ($children === false) {// don't do anythingreturn;} elseif ($children instanceof DOMNode) {// $children is a single complex childif ($import) {$children = $base->ownerDocument->importNode($children, true);}$base->appendChild($children);} elseif (is_array($children)) {for ($i = 0; $i < count($children); $i++) {if ($children[$i] === false) {// don't do anything} elseif ($children[$i] instanceof DOMNode) {if ($import) {$children[$i] = $base->ownerDocument->importNode($children[$i],true);}$base->appendChild($children[$i]);} else {/** unit tests:* _addDOMChildren/throwXMLException_arrayWithObject.phpt* _addDOMChildren/throwXMLException_arrayWithString.phpt* _addDOMChildren/throwXMLException_arrayWithInt.phpt* _addDOMChildren/throwXMLException_arrayWithBool.phpt* _addDOMChildren/throwXMLException_arrayWithDouble.phpt*/throw new XML_Query2XML_XMLException($configPath . ': DOMNode, false or an array of the two '. 'expected, but ' . gettype($children[$i]) . ' given '. '(hint: check your callback).');}}} else {/** This should never happen because _addDOMChildren() is only called* for arrays and instances of DOMNode.*/throw new XML_Query2XML_XMLException($configPath . ': DOMNode, false or an array of the two '. 'expected, but ' . gettype($children) . ' given '. '(hint: check your callback).');}}/*** Remove all container elements created by XML_Query2XML to ensure that all* elements are correctly ordered.** This is a recursive method. This method calls* {@link XML_Query2XML::_replaceParentWithChildren()}. For the concept of* container elements please see the {@tutorial XML_Query2XML.pkg tutorial}.** @param DOMNode $element An instance of DOMNode.* @param string $hiddenContainerPrefix The containers that will be removed* all start with this string.** @return void*/private static function _removeContainers($element, $hiddenContainerPrefix){$xpath = new DOMXPath($element);$containers = $xpath->query('//*[starts-with(name(),\'' . $hiddenContainerPrefix . '\')]');foreach ($containers as $container) {if (!is_null($container->parentNode)) {self::_replaceParentWithChildren($container);}}}/*** Replace a certain node with its child nodes.** @param DOMNode $parent An instance of DOMNode.** @return void*/private static function _replaceParentWithChildren(DOMNode $parent){$child = $parent->firstChild;while ($child) {$nextChild = $child->nextSibling;$parent->removeChild($child);$parent->parentNode->insertBefore($child, $parent);$child = $nextChild;}$parent->parentNode->removeChild($parent);}/*** Calls an encoder for XML node and attribute values* $options['encoder'] can be one of the following:* - null: self::_utf8encode() will be used* - false: no encoding will be performed* - callback: a string or an array as defined by the* callback pseudo-type; please see* http://www.php.net/manual/en/* language.pseudo-types.php#language.types.callback** @param string $str The string to encode* @param array $options An associative array with $options['encoder'] set.** @return void* @throws XML_Query2XML_XMLException If the $options['encoder'] is a callback* function that threw an exception.*/private static function _executeEncoder($str, $options){if (!is_string($str) || $options['encoder'] === false) {return $str;}if ($options['encoder'] === null) {return self::_utf8encode($str);}try {return call_user_func($options['encoder'], $str);} catch (Exception $e) {/** unit test:* _executeEncoder/throwXMLException.phpt*/throw new XML_Query2XML_XMLException($options['--q2x--path'] . '[encoder]: Could not encode '. '"' . $str . '": ' . $e->getMessage());}}/*** UTF-8 encode $str using mb_conver_encoding or if that is not* present, utf8_encode.** @param string $str The string to encode** @return String The UTF-8 encoded version of $str*/private static function _utf8encode($str){if (function_exists('mb_convert_encoding')) {$str = mb_convert_encoding($str, 'UTF-8');} else {$str = utf8_encode($str);}return $str;}}/*** Parent class for ALL exceptions thrown by this package.* By catching XML_Query2XML_Exception you will catch all exceptions* thrown by XML_Query2XML.** @category XML* @package XML_Query2XML* @author Lukas Feiler <lukas.feiler@lukasfeiler.com>* @license http://www.gnu.org/copyleft/lesser.html LGPL Version 2.1* @link http://pear.php.net/package/XML_Query2XML*/class XML_Query2XML_Exception extends PEAR_Exception{/*** Constructor method** @param string $message The error message.* @param Exception $exception The Exception that caused this exception* to be thrown. This argument is optional.*/public function __construct($message, $exception = null){parent::__construct($message, $exception);}}/*** Exception for driver errors** @category XML* @package XML_Query2XML* @author Lukas Feiler <lukas.feiler@lukasfeiler.com>* @license http://www.gnu.org/copyleft/lesser.html LGPL Version 2.1* @link http://pear.php.net/package/XML_Query2XML* @since Release 1.6.0RC1*/class XML_Query2XML_DriverException extends XML_Query2XML_Exception{/*** Constructor** @param string $message The error message.*/public function __construct($message){parent::__construct($message);}}/*** Exception for database errors** @category XML* @package XML_Query2XML* @author Lukas Feiler <lukas.feiler@lukasfeiler.com>* @license http://www.gnu.org/copyleft/lesser.html LGPL Version 2.1* @link http://pear.php.net/package/XML_Query2XML*/class XML_Query2XML_DBException extends XML_Query2XML_DriverException{/*** Constructor** @param string $message The error message.*/public function __construct($message){parent::__construct($message);}}/*** Exception for XML errors* In most cases this exception will be thrown if a DOMException occurs.** @category XML* @package XML_Query2XML* @author Lukas Feiler <lukas.feiler@lukasfeiler.com>* @license http://www.gnu.org/copyleft/lesser.html LGPL Version 2.1* @link http://pear.php.net/package/XML_Query2XML*/class XML_Query2XML_XMLException extends XML_Query2XML_Exception{/*** Constructor** @param string $message The error message.* @param DOMException $exception The DOMException that caused this exception* to be thrown. This argument is optional.*/public function __construct($message, DOMException $exception = null){parent::__construct($message, $exception);}}/*** Exception that handles configuration errors.** This exception handels errors in the $options array passed to* XML_Query2XML::getXML() and wrong arguments passed to the constructor via* XML_Query2XML::factory().** @category XML* @package XML_Query2XML* @author Lukas Feiler <lukas.feiler@lukasfeiler.com>* @license http://www.gnu.org/copyleft/lesser.html LGPL Version 2.1* @link http://pear.php.net/package/XML_Query2XML* @see XML_Query2XML::getXML()*/class XML_Query2XML_ConfigException extends XML_Query2XML_Exception{/*** Constructor method** @param string $message A detailed error message.*/public function __construct($message){parent::__construct($message);}}/*** Abstract driver class.** usage:* <code>* $driver = XML_Query2XML_Driver::factory($backend);* </code>** @category XML* @package XML_Query2XML* @author Lukas Feiler <lukas.feiler@lukasfeiler.com>* @license http://www.gnu.org/copyleft/lesser.html LGPL Version 2.1* @version Release: 1.7.2* @link http://pear.php.net/package/XML_Query2XML* @since Release 1.5.0RC1*/abstract class XML_Query2XML_Driver{/*** This method, when implemented executes the query passed as the* first argument and returns all records from the result set.** The format of the first argument depends on the driver being used.** @param mixed $sql The SQL query as a string or an array.* @param string $configPath The config path; used for exception messages.** @return array An array of records. Each record itself will be an* associative array.* @throws XML_Query2XML_DriverException If some driver related error occures.*/abstract public function getAllRecords($sql, $configPath);/*** Pre-processes a query specification and returns a string representation* of the query.** The returned string will be used for logging purposes. It* does not need to be valid SQL.** If $query is a string, it will be changed to array('query' => $query).** @param mixed &$query A string or an array containing the element 'query'.* @param string $configPath The config path; used for exception messages.** @return string The query statement as a string.* @throws XML_Query2XML_ConfigException If $query is an array but does not* contain the element 'query'.*/public function preprocessQuery(&$query, $configPath){if (is_string($query)) {$query = array('query' => $query);} elseif (is_array($query)) {if (!isset($query['query'])) {/** unit test: _preprocessOptions/* throwConfigException_queryOptionMissing.phpt*/throw new XML_Query2XML_ConfigException($configPath . ': The configuration option'. ' "query" is missing.');}} else { //neither a string nor an array/** unit test: _preprocessOptions/* throwConfigException_sqlOptionWrongType.phpt*/throw new XML_Query2XML_ConfigException($configPath . ': array or string expected, '. gettype($query) . ' given.');}return $query['query'];}/*** Factory method.** @param mixed $backend An instance of MDB2_Driver_Common, PDO, DB_common,* ADOConnection, Net_LDAP2 or Net_LDAP.** @return XML_Query2XML_Driver An instance of a driver class that* extends XML_Query2XML_Driver.* @throws XML_Query2XML_DriverException If $backend already is a PEAR_Error.* @throws XML_Query2XML_ConfigException If $backend is not an instance of a* child class of MDB2_Driver_Common, PDO, DB_common,* ADOConnection, Net_LDAP2 or Net_LDAP.*/public static function factory($backend){if (class_exists('MDB2_Driver_Common') &&$backend instanceof MDB2_Driver_Common) {include_once 'XML/Query2XML/Driver/MDB2.php';return new XML_Query2XML_Driver_MDB2($backend);} elseif (class_exists('PDO') && $backend instanceof PDO) {include_once 'XML/Query2XML/Driver/PDO.php';return new XML_Query2XML_Driver_PDO($backend);} elseif (class_exists('DB_common') && $backend instanceof DB_common) {include_once 'XML/Query2XML/Driver/DB.php';return new XML_Query2XML_Driver_DB($backend);} elseif (class_exists('ADOConnection') &&$backend instanceof ADOConnection) {include_once 'XML/Query2XML/Driver/ADOdb.php';return new XML_Query2XML_Driver_ADOdb($backend);} elseif (class_exists('Net_LDAP') && $backend instanceof Net_LDAP) {include_once 'XML/Query2XML/Driver/LDAP.php';return new XML_Query2XML_Driver_LDAP($backend);} elseif (class_exists('Net_LDAP2') && $backend instanceof Net_LDAP2) {include_once 'XML/Query2XML/Driver/LDAP2.php';return new XML_Query2XML_Driver_LDAP2($backend);} elseif (class_exists('PEAR_Error') && $backend instanceof PEAR_Error) {//unit tests: NoDBLayer/factory/throwDBException.phptthrow new XML_Query2XML_DriverException('Driver error: ' . $backend->toString());} else {//unit test: NoDBLayer/factory/throwConfigException.phptthrow new XML_Query2XML_ConfigException('Argument passed to the XML_Query2XML constructor is not an '. 'instance of DB_common, MDB2_Driver_Common, ADOConnection'. ', PDO, Net_LDAP or Net_LDAP2.');}}}?>