Source for file Query2XML.php

Documentation is available at Query2XML.php

  1. <?php
  2. /**
  3.  * This file contains the class XML_Query2XML and all exception classes.
  4.  *
  5.  * PHP version 5
  6.  *
  7.  * @category  XML
  8.  * @package   XML_Query2XML
  9.  * @author    Lukas Feiler <lukas.feiler@lukasfeiler.com>
  10.  * @copyright 2006 Lukas Feiler
  11.  * @license   http://www.gnu.org/copyleft/lesser.html  LGPL Version 2.1
  12.  * @version   CVS: $Id: Query2XML.php 309897 2011-04-02 17:36:42Z lukasfeiler $
  13.  * @link      http://pear.php.net/package/XML_Query2XML
  14.  */
  15.  
  16. /**
  17.  * PEAR_Exception is used as the parent for XML_Query2XML_Exception.
  18.  */
  19. require_once 'PEAR/Exception.php';
  20.  
  21. /**
  22.  * Create XML data from SQL queries.
  23.  *
  24.  * XML_Query2XML heavily uses exceptions and therefore requires PHP5.
  25.  * Additionally one of the following database abstraction layers is
  26.  * required: PDO (compiled-in by default since PHP 5.1), PEAR DB,
  27.  * PEAR MDB2, ADOdb.
  28.  *
  29.  * The two most important public methods this class provides are:
  30.  *
  31.  * <b>{@link XML_Query2XML::getFlatXML()}</b>
  32.  * Transforms your SQL query into flat XML data.
  33.  *
  34.  * <b>{@link XML_Query2XML::getXML()}</b>
  35.  * Very powerful and flexible method that can produce whatever XML data you want. It
  36.  * was specifically written to also handle LEFT JOINS.
  37.  *
  38.  * They both return an instance of the class DOMDocument provided by PHP5's
  39.  * built-in DOM API.
  40.  *
  41.  * A typical usage of XML_Query2XML looks like this:
  42.  * <code>
  43.  * <?php
  44.  * require_once 'XML/Query2XML.php';
  45.  * $query2xml = XML_Query2XML::factory(MDB2::factory($dsn));
  46.  * $dom = $query2xml->getXML($sql, $options);
  47.  * header('Content-Type: application/xml');
  48.  * print $dom->saveXML();
  49.  * ?>
  50.  * </code>
  51.  *
  52.  * Please read the <b>{@tutorial XML_Query2XML.pkg tutorial}</b> for
  53.  * detailed usage examples and more documentation.
  54.  * 
  55.  * @category  XML
  56.  * @package   XML_Query2XML
  57.  * @author    Lukas Feiler <lukas.feiler@lukasfeiler.com>
  58.  * @copyright 2006 Lukas Feiler
  59.  * @license   http://www.gnu.org/copyleft/lesser.html  LGPL Version 2.1
  60.  * @version   Release: 1.7.2
  61.  * @link      http://pear.php.net/package/XML_Query2XML
  62.  */
  63. {
  64.     /**
  65.      * Primary driver.
  66.      * @var XML_Query2XML_Driver A subclass of XML_Query2XML_Driver.
  67.      */
  68.     private $_driver;
  69.     
  70.     /**
  71.      * An instance of PEAR Log
  72.      * @var mixed An object that has a method with the signature log(String $msg);
  73.      *             preferably PEAR Log.
  74.      * @see enableDebugLog
  75.      * @see disableDebugLog
  76.      */
  77.     private $_debugLogger;
  78.     
  79.     /**
  80.      * Whether debug logging is to be performed
  81.      * @var boolean 
  82.      * @see enableDebugLog
  83.      * @see disableDebugLog
  84.      */
  85.     private $_debug false;
  86.     
  87.     /**
  88.      * Whether profiling is to be performed
  89.      * @var boolean 
  90.      * @see startProfiling()
  91.      * @see stopProfiling()
  92.      */
  93.     private $_profiling false;
  94.     
  95.     /**
  96.      * The profiling data.
  97.      * @var array A multi dimensional associative array
  98.      * @see startProfiling()
  99.      * @see stopProfiling()
  100.      * @see _debugStartQuery()
  101.      * @see _debugStopQuery()
  102.      * @see _stopDBProfiling()
  103.      */
  104.     private $_profile array();
  105.     
  106.     /**
  107.      * An associative array of global options.
  108.      * @var array An associative array
  109.      * @see setGlobalOption()
  110.      */
  111.     private $_globalOptions array(
  112.         'hidden_container_prefix' => '__'
  113.     );
  114.     
  115.     /**
  116.      * An associative array that will contain an element for each prefix.
  117.      * The prefix is used as the element key. Each array element consists
  118.      * of an indexed array containing a file path and a class name.
  119.      * @var array An associative multidimensional array.
  120.      * @see registerPrefix()
  121.      * @see unregisterPrefix()
  122.      * @see unregisterAllPrefixes()
  123.      * @see _buildCommandChain()
  124.      */
  125.     private $_prefixes array(
  126.         '?' => array(
  127.             'XML/Query2XML/Data/Condition/NonEmpty.php',
  128.             'XML_Query2XML_Data_Condition_NonEmpty'
  129.         ),
  130.         '&' => array(
  131.             'XML/Query2XML/Data/Processor/Unserialize.php',
  132.             'XML_Query2XML_Data_Processor_Unserialize'
  133.         ),
  134.         '=' => array(
  135.             'XML/Query2XML/Data/Processor/CDATA.php',
  136.             'XML_Query2XML_Data_Processor_CDATA'
  137.         ),
  138.         '^' => array(
  139.             'XML/Query2XML/Data/Processor/Base64.php',
  140.             'XML_Query2XML_Data_Processor_Base64'
  141.         ),
  142.         ':' => array(
  143.             'XML/Query2XML/Data/Source/Static.php',
  144.             'XML_Query2XML_Data_Source_Static'
  145.         ),
  146.         '#' => array(
  147.             'XML/Query2XML/Data/Source/PHPCallback.php',
  148.             'XML_Query2XML_Data_Source_PHPCallback'
  149.         ),
  150.         '~' => array(
  151.             'XML/Query2XML/Data/Source/XPath.php',
  152.             'XML_Query2XML_Data_Source_XPath'
  153.         )
  154.     );
  155.     
  156.     /**
  157.      * Constructor
  158.      *
  159.      * @param mixed $backend A subclass of XML_Query2XML_Driver or
  160.      *                        an instance of PEAR DB, PEAR MDB2, ADOdb,
  161.      *                        PDO, Net_LDAP2 or Net_LDAP.
  162.      */
  163.     private function __construct($backend)
  164.     {
  165.         if ($backend instanceof XML_Query2XML_Driver{
  166.             $this->_driver $backend;
  167.         else {
  168.             $this->_driver XML_Query2XML_Driver::factory($backend);
  169.         }
  170.     }
  171.     
  172.     /**
  173.      * Factory method.
  174.      * As first argument pass an instance of PDO, PEAR DB, PEAR MDB2, ADOdb,
  175.      * Net_LDAP or an instance of any class that extends XML_Query2XML_Driver:
  176.      * <code>
  177.      * <?php
  178.      * require_once 'XML/Query2XML.php';
  179.      * $query2xml = XML_Query2XML::factory(
  180.      *   new PDO('mysql://root@localhost/Query2XML_Tests')
  181.      * );
  182.      * ?>
  183.      * </code>
  184.      *
  185.      * <code>
  186.      * <?php
  187.      * require_once 'XML/Query2XML.php';
  188.      * require_once 'DB.php';
  189.      * $query2xml = XML_Query2XML::factory(
  190.      *   DB::connect('mysql://root@localhost/Query2XML_Tests')
  191.      * );
  192.      * ?>
  193.      * </code>
  194.      * 
  195.      * <code>
  196.      * <?php
  197.      * require_once 'XML/Query2XML.php';
  198.      * require_once 'MDB2.php';
  199.      * $query2xml = XML_Query2XML::factory(
  200.      *   MDB2::factory('mysql://root@localhost/Query2XML_Tests')
  201.      * );
  202.      * ?>
  203.      * </code>
  204.      * 
  205.      * <code>
  206.      * <?php
  207.      * require_once 'XML/Query2XML.php';
  208.      * require_once 'adodb/adodb.inc.php';
  209.      * $adodb = ADONewConnection('mysql');
  210.      * $adodb->Connect('localhost', 'root', '', 'Query2XML_Tests');
  211.      * $query2xml = XML_Query2XML::factory($adodb);
  212.      * ?>
  213.      * </code>
  214.      *
  215.      * @param mixed $backend An instance of PEAR DB, PEAR MDB2, ADOdb, PDO,
  216.      *                        Net_LDAP or a subclass of XML_Query2XML_Driver.
  217.      *
  218.      * @return XML_Query2XML A new instance of XML_Query2XML
  219.      * @throws XML_Query2XML_DriverException If $backend already is a PEAR_Error.
  220.      * @throws XML_Query2XML_ConfigException If $backend is not an instance of a
  221.      *                   child class of DB_common, MDB2_Driver_Common, ADOConnection
  222.      *                   PDO, Net_LDAP or XML_Query2XML_Driver.
  223.      */
  224.     public static function factory($backend)
  225.     {
  226.         return new XML_Query2XML($backend);
  227.     }
  228.     
  229.     /**
  230.      * Register a prefix that can be used in all value specifications.
  231.      *
  232.      * @param string $prefix    The prefix name. This must be a single chracter.
  233.      * @param string $className The name of the Data Class. This class has
  234.      *                           to extend XML_Query2XML_Data.
  235.      * @param string $filePath  The path to the file that contains the Command
  236.      *                           class. This argument is optional.
  237.      *
  238.      * @return void 
  239.      * @throws XML_Query2XML_ConfigException Thrown if $prefix is not a string
  240.      *                                        or has a length other than 1.
  241.      */
  242.     public function registerPrefix($prefix$className$filePath '')
  243.     {
  244.         if (!is_string($prefix|| strlen($prefix!= 1{
  245.             throw new XML_Query2XML_ConfigException(
  246.                 'Prefix name has to be a single character'
  247.             );
  248.         }
  249.         $this->_prefixes[$prefixarray(
  250.             $filePath,
  251.             $className
  252.         );
  253.     }
  254.     
  255.     /**
  256.      * Unregister a prefix.
  257.      *
  258.      * @param string $prefix The prefix name.
  259.      *
  260.      * @return void 
  261.      */
  262.     public function unregisterPrefix($prefix)
  263.     {
  264.         unset($this->_prefixes[$prefix]);
  265.     }
  266.     
  267.     /**
  268.      * Unregister all prefixes.
  269.      *
  270.      * @return void 
  271.      */
  272.     public function unregisterAllPrefixes()
  273.     {
  274.         $this->_prefixes array();
  275.     }
  276.     
  277.     /**
  278.      * Set a global option.
  279.      * Currently the following global options are available:
  280.      *
  281.      * hidden_container_prefix: The prefix to use for container elements that are
  282.      *   to be removed before the DOMDocument before it is returned by
  283.      *   {@link XML_Query2XML::getXML()}. This has to be a non-empty string.
  284.      *   The default value is '__'.
  285.      *
  286.      * @param string $option The name of the option
  287.      * @param mixed  $value  The option value
  288.      *
  289.      * @return void 
  290.      * @throws XML_Query2XML_ConfigException If the configuration option
  291.      *          does not exist or if the value is invalid for that option
  292.      */
  293.     public function setGlobalOption($option$value)
  294.     {
  295.         switch ($option{
  296.         case 'hidden_container_prefix':
  297.             if (is_string($value&& strlen($value0{
  298.                 // unit test: setGlobalOption/hidden_container_prefix.phpt
  299.                 $this->_globalOptions[$option$value;
  300.             else {
  301.                 /*
  302.                  * unit test: setGlobalOption/
  303.                  * configException_hidden_container_prefix_wrongTypeObject.phpt
  304.                  * configException_hidden_container_prefix_wrongTypeEmptyStr.phpt
  305.                  */
  306.                 throw new XML_Query2XML_ConfigException(
  307.                     'The value for the hidden_container_prefix option '
  308.                     . 'has to be a non-empty string'
  309.                 );
  310.             }
  311.             break;
  312.  
  313.         default:
  314.             // unit tests: setGlobalOption/configException_noSuchOption.phpt
  315.             throw new XML_Query2XML_ConfigException(
  316.                 'No such global option: ' $option
  317.             );
  318.         }
  319.     }
  320.     
  321.     /**
  322.      * Returns the current value for a global option.
  323.      * See {@link XML_Query2XML::setGlobalOption()} for a list
  324.      * of available options.
  325.      *
  326.      * @param string $option The name of the option
  327.      *
  328.      * @return mixed The option's value
  329.      * @throws XML_Query2XML_ConfigException If the option does not exist
  330.      */
  331.     public function getGlobalOption($option)
  332.     {
  333.         if (!isset($this->_globalOptions[$option])) {
  334.             // unit test: getGlobalOption/configException_noSuchOption.phpt
  335.             throw new XML_Query2XML_ConfigException(
  336.                 'No such global option: ' $option
  337.             );
  338.         }
  339.         // unit test: getGlobalOption/hidden_container_prefix.phpt
  340.         return $this->_globalOptions[$option];
  341.     }
  342.     
  343.     /**
  344.      * Enable the logging of debug messages.
  345.      * This will include all queries sent to the database.
  346.      * Example:
  347.      * <code>
  348.      * <?php
  349.      * require_once 'Log.php';
  350.      * require_once 'XML/Query2XML.php';
  351.      * $query2xml = XML_Query2XML::factory(MDB2::connect($dsn));
  352.      * $debugLogger = Log::factory('file', 'out.log', 'XML_Query2XML');
  353.      * $query2xml->enableDebugLog($debugLogger);
  354.      * ?>
  355.      * </code>
  356.      * Please see {@link http://pear.php.net/package/Log} for details on PEAR Log.
  357.      *
  358.      * @param mixed $log Most likely an instance of PEAR Log but any object
  359.      *                    that provides a method named 'log' is accepted.
  360.      *
  361.      * @return void 
  362.      */
  363.     public function enableDebugLog($log)
  364.     {
  365.         // unit test: enableDebugLog/enableDebugLog.phpt
  366.         $this->_debugLogger $log;
  367.         $this->_debug       true;
  368.     }
  369.     
  370.     /**
  371.      * Disable the logging of debug messages
  372.      *
  373.      * @return void 
  374.      */
  375.     public function disableDebugLog()
  376.     {
  377.         // unit test: disableDebugLog/disableDebugLog.phpt
  378.         $this->_debug false;
  379.     }
  380.     
  381.     /**
  382.      * Start profiling.
  383.      *
  384.      * @return void 
  385.      */
  386.     public function startProfiling()
  387.     {
  388.         // unit tests: startProfile/startProfile.phpt
  389.         $this->_profiling true;
  390.         $this->_profile   array(
  391.             'queries'    => array(),
  392.             'start'      => microtime(1),
  393.             'stop'       => 0,
  394.             'duration'   => 0,
  395.             'dbStop'     => 0,
  396.             'dbDuration' => 0
  397.         );
  398.     }
  399.     
  400.     /**
  401.      * Stop profiling.
  402.      *
  403.      * @return void 
  404.      */
  405.     public function stopProfiling()
  406.     {
  407.         // unit test: stopProfile/stopProfile.phpt
  408.         $this->_profiling false;
  409.         if (isset($this->_profile['start']&& $this->_profile['stop'== 0{
  410.             $this->_profile['stop']     microtime(1);
  411.             $this->_profile['duration'=
  412.                 $this->_profile['stop'$this->_profile['start'];
  413.         }
  414.     }
  415.     
  416.     /**
  417.      * Returns all raw profiling data.
  418.      * In 99.9% of all cases you will want to use getProfile().
  419.      *
  420.      * @see getProfile()
  421.      * @return array 
  422.      */
  423.     public function getRawProfile()
  424.     {
  425.         // unit test: getRawProfile/getRawProfile.phpt
  426.         $this->stopProfiling();
  427.         return $this->_profile;
  428.     }
  429.     
  430.     /**
  431.      * Returns the profile as a single multi line string.
  432.      *
  433.      * @return string The profiling data.
  434.      */
  435.     public function getProfile()
  436.     {
  437.         // unit test: getProfile/getProfile.phpt
  438.         $this->stopProfiling();
  439.         if (count($this->_profile=== 0{
  440.             return '';
  441.         }
  442.         $ret 'COUNT AVG_DURATION DURATION_SUM SQL' "\n";
  443.         foreach ($this->_profile['queries'as $sql => $value{
  444.             $durationSum   0.0;
  445.             $durationCount 0;
  446.             $runTimes      =$this->_profile['queries'][$sql]['runTimes'];
  447.             foreach ($runTimes as $runTime{
  448.                 $durationSum += ($runTime['stop'$runTime['start']);
  449.                 ++$durationCount;
  450.             }
  451.             if ($durationCount == 0{
  452.                 // so that division does not fail
  453.                 $durationCount 1;
  454.             }
  455.             $durationAverage $durationSum $durationCount;
  456.             
  457.             $ret .= str_pad($this->_profile['queries'][$sql]['count']5)
  458.                   . ' '
  459.                   . substr($durationAverage012)' '
  460.                   . substr($durationSum012)' '
  461.                   . $sql "\n";
  462.         }
  463.         $ret .= "\n";
  464.         $ret .= 'TOTAL_DURATION: ' $this->_profile['duration'"\n";
  465.         $ret .= 'DB_DURATION:    ' $this->_profile['dbDuration'"\n";
  466.         return $ret;
  467.     }
  468.     
  469.     /**
  470.      * Calls {@link XML_Query2XML::stopProfiling()} and then clears the profiling
  471.      * data by resetting a private property.
  472.      *
  473.      * @return void 
  474.      */
  475.     public function clearProfile()
  476.     {
  477.         // unit test: clearProfile/clearProfile.phpt
  478.         $this->stopProfiling();
  479.         $this->_profile array();
  480.     }
  481.     
  482.     /**
  483.      * Transforms the data retrieved by a single SQL query into flat XML data.
  484.      *
  485.      * This method will return a new instance of DOMDocument. The column names
  486.      * will be used as element names.
  487.      *
  488.      * Example:
  489.      * <code>
  490.      * <?php
  491.      * require_once 'XML/Query2XML.php';
  492.      * $query2xml = XML_Query2XML::factory(MDB2::connect($dsn));
  493.      * $dom = $query2xml->getFlatXML(
  494.      *   'SELECT * FROM artist',
  495.      *   'music_library',
  496.      *   'artist'
  497.      * );
  498.      * ?>
  499.      * </code>
  500.      *
  501.      * @param string $sql         The query string.
  502.      * @param string $rootTagName The name of the root tag; this argument is optional
  503.      *                             (default: 'root').
  504.      * @param string $rowTagName  The name of the tag used for each row; this
  505.      *                             argument is optional (default: 'row').
  506.      *
  507.      * @return DOMDocument        A new instance of DOMDocument.
  508.      * @throws XML_Query2XML_Exception This is the base class for the exception
  509.      *                             types XML_Query2XML_DBException and
  510.      *                             XML_Query2XML_XMLException. By catching
  511.      *                             XML_Query2XML_Exception you can catch all
  512.      *                             exceptions this method will ever throw.
  513.      * @throws XML_Query2XML_DBException If a database error occurrs.
  514.      * @throws XML_Query2XML_XMLException If an XML error occurrs - most likely
  515.      *                             $rootTagName or $rowTagName is not a valid
  516.      *                             element name.
  517.      */
  518.     public function getFlatXML($sql$rootTagName 'root'$rowTagName 'row')
  519.     {
  520.         /*
  521.          * unit tests: getFlatXML/*.phpt
  522.          */
  523.         $dom     self::_createDOMDocument();
  524.         $rootTag self::_addNewDOMChild($dom$rootTagName'getFlatXML');
  525.         $records $this->_getAllRecords(array('query' => $sql)'getFlatXML'$sql);
  526.         foreach ($records as $record{
  527.             $rowTag self::_addNewDOMChild($rootTag$rowTagName'getFlatXML');
  528.             foreach ($record as $field => $value{
  529.                 self::_addNewDOMChild(
  530.                     $rowTag,
  531.                     $field,
  532.                     'getFlatXML',
  533.                     self::_utf8encode($value)
  534.                 );
  535.             }
  536.         }
  537.         return $dom;
  538.     }
  539.     
  540.     /**
  541.      * Transforms your SQL data retrieved by one or more queries into complex and
  542.      * highly configurable XML data.
  543.      *
  544.      * This method will return a new instance of DOMDocument.
  545.      * Please see the <b>{@tutorial XML_Query2XML.pkg tutorial}</b> for details.
  546.      * 
  547.      * @param mixed $sql     A string an array or the boolean value false.
  548.      * @param array $options Options for the creation of the XML data stored in an
  549.      *                        associative, potentially mutli-dimensional array
  550.      *                        (please see the tutorial).
  551.      *
  552.      * @return DOMDocument   The XML data as a new instance of DOMDocument.
  553.      * @throws XML_Query2XML_Exception This is the base class for the exception types
  554.      *                        XML_Query2XML_DBException, XML_Query2XML_XMLException
  555.      *                        and XML_Query2XML_ConfigException. By catching
  556.      *                        XML_Query2XML_Exception you can catch all exceptions
  557.      *                        this method will ever throw.
  558.      * @throws XML_Query2XML_DBException If a database error occurrs.
  559.      * @throws XML_Query2XML_XMLException If an XML error occurrs - most likely
  560.      *                        an invalid XML element name.
  561.      * @throws XML_Query2XML_ConfigException If some configuration options passed
  562.      *                        as second argument are invalid or missing.
  563.      */
  564.     public function getXML($sql$options)
  565.     {
  566.         /*
  567.         * unit tests: getXML/*.phpt
  568.         */
  569.         
  570.         // the default root tag name is 'root'
  571.         if (isset($options['rootTag'])) {
  572.             $rootTagName $options['rootTag'];
  573.         else {
  574.             $rootTagName 'root';
  575.         }
  576.         
  577.         $dom     self::_createDOMDocument();
  578.         $rootTag self::_addNewDOMChild($dom$rootTagName'[rootTag]');
  579.         
  580.         $options['sql'$sql;
  581.         
  582.         if ($options['sql'=== false{
  583.             $options['sql''';
  584.         }
  585.         $this->_preprocessOptions($options);
  586.         
  587.         /* Used to store the information which element has been created
  588.         *  for which ID column value.
  589.         */
  590.         $tree array();
  591.         
  592.         if ($sql === false{
  593.             $records array(array())// one empty record
  594.         else {
  595.             $records $this->_applySqlOptionsToRecord(
  596.                 $options,
  597.                 $emptyRecord array()
  598.             );
  599.         }
  600.         
  601.         foreach ($records as $key => $record{
  602.             $tag $this->_getNestedXMLRecord($records[$key]$options$dom$tree);
  603.             
  604.             /* _getNestedXMLRecord() returns false if an element already existed for
  605.             *  the current ID column value.
  606.             */
  607.             if ($tag !== false{
  608.                 $rootTag->appendChild($tag);
  609.             }
  610.         }
  611.         
  612.         $this->_stopDBProfiling();
  613.         
  614.         self::_removeContainers(
  615.             $dom,
  616.             $this->getGlobalOption('hidden_container_prefix')
  617.         );
  618.         return $dom;
  619.     }
  620.     
  621.     /**
  622.      * Perform pre-processing on $options.
  623.      * This is a recursive method; it will call itself for every complex element
  624.      * specification and every complex attribute specification found.
  625.      *
  626.      * @param array  &$options An associative array
  627.      * @param string $context  Indecates whether an element or an attribute is
  628.      *                          to be processed.
  629.      *
  630.      * @return void 
  631.      * @throws XML_Query2XML_ConfigException If a mandatory option is missing
  632.      *                        or any option is defined incorrectly.
  633.      */
  634.     private function _preprocessOptions(&$options$context 'elements')
  635.     {
  636.         if (!isset($options['--q2x--path'])) {
  637.             // things to do only at the root level
  638.             $options['--q2x--path''';
  639.             
  640.             if (!isset($options['rowTag'])) {
  641.                 $options['rowTag''row';
  642.             }
  643.             
  644.             if (!isset($options['idColumn'])) {
  645.                 /*
  646.                 * unit test: _preprocessOptions/
  647.                 *  throwConfigException_idcolumnOptionMissing.phpt
  648.                 */
  649.                 throw new XML_Query2XML_ConfigException(
  650.                     'The configuration option "idColumn" '
  651.                     . 'is missing on the root level.'
  652.                 );
  653.             }
  654.         }
  655.         
  656.         foreach (array('encoder''mapper'as $option{
  657.             if (isset($options[$option])) {
  658.                 if (
  659.                     is_string($options[$option]&&
  660.                     strpos($options[$option]'::'!== false
  661.                 {
  662.                     $options[$optionexplode('::'$options[$option]);
  663.                 }
  664.                 if (
  665.                     $options[$option!== false
  666.                     &&
  667.                     !($option == 'encoder' && $options[$option=== null)
  668.                     &&
  669.                     !($option == 'mapper' && $options[$option== false)
  670.                     &&
  671.                     !is_callable($options[$option]false$callableName)
  672.                 {
  673.                     /*
  674.                     * Only check whether $options['encoder'] is callable if it's not
  675.                     * set to:
  676.                     * - false (don't use an encoder)
  677.                     * - null (use self::_utf8encode()).
  678.                     *
  679.                     * unit test: _preprocessOptions/
  680.                     *  throwConfigException_encoderNotCallableStaticMethod1.phpt
  681.                     *  throwConfigException_encoderNotCallableStaticMethod2.phpt
  682.                     *  throwConfigException_encoderNotCallableNonstaticMethod.phpt
  683.                     *  throwConfigException_encoderNotCallableFunction.phpt
  684.                     *
  685.                     *
  686.                     * Only check whether $options['mapper'] is callable if
  687.                     * - $options['mapper'] != false
  688.                     *
  689.                     * unit tests: _preprocessOptions/
  690.                     *  throwConfigException_mapperNotCallableStaticMethod1.phpt
  691.                     *  throwConfigException_mapperNotCallableStaticMethod2.phpt
  692.                     *  throwConfigException_mapperNotCallableNonstaticMethod.phpt
  693.                     *  throwConfigException_mapperNotCallableFunction.phpt
  694.                     */
  695.                     throw new XML_Query2XML_ConfigException(
  696.                         $options['--q2x--path''[' $option ']: The '
  697.                         . 'method/function "' $callableName '" is not callable.'
  698.                     );
  699.                 }
  700.             else {
  701.                 $options[$optionnull;
  702.             }
  703.         }
  704.         
  705.         if ($context == 'elements'{
  706.             foreach (array('elements''attributes'as $option{
  707.                 if (isset($options[$option])) {
  708.                     if (!is_array($options[$option])) {
  709.                         /*
  710.                         * unit test: _preprocessOptions/
  711.                         *  throwConfigException_attributesOptionWrongType.phpt
  712.                         *  throwConfigException_elementsOptionWrongType.phpt
  713.                         */
  714.                         throw new XML_Query2XML_ConfigException(
  715.                             $options['--q2x--path''[' $option ']: '
  716.                             . 'array expected, ' gettype($options[$option])
  717.                             . ' given.'
  718.                         );
  719.                     }
  720.                     foreach ($options[$optionas $key => $columnStr{
  721.                         $configPath $options['--q2x--path''[' $option
  722.                                       . '][' $key ']';
  723.                         if (is_string($columnStr)) {
  724.                             $options[$option][$key=
  725.                                 $this->_buildCommandChain($columnStr$configPath);
  726.                             if (
  727.                                 is_numeric($key&&
  728.                                 is_object($options[$option][$key])
  729.                             {
  730.                                 /*
  731.                                  * unit test: _preprocessOptions/
  732.                                  *  throwConfigException_prefix_noArrayKey.phpt
  733.                                  */
  734.                                 throw new XML_Query2XML_ConfigException(
  735.                                     $configPath ': the element name has to be '
  736.                                     . 'specified as the array key when prefixes '
  737.                                     . 'are used within the value specification'
  738.                                 );
  739.                             }
  740.                         elseif (is_array($columnStr)) {
  741.                             $options[$option][$key]['--q2x--path'$configPath;
  742.                             
  743.                             // encoder option used by elements as well as attributes
  744.                             if (
  745.                                 !array_key_exists(
  746.                                     'encoder',
  747.                                     $options[$option][$key]
  748.                                  )
  749.                             {
  750.                                 $options[$option][$key]['encoder'=
  751.                                     $options['encoder'];
  752.                             }
  753.                             if ($option == 'elements'{
  754.                                 // these options are only used by elements
  755.                                 if (
  756.                                     !isset($options[$option][$key]['rootTag']||
  757.                                     $options[$option][$key]['rootTag'== ''
  758.                                 {
  759.                                     /*
  760.                                      * If rootTag is not set or an empty string:
  761.                                      * create a hidden root tag
  762.                                      */
  763.                                     $options[$option][$key]['rootTag'
  764.                                         $this->getGlobalOption(
  765.                                             'hidden_container_prefix'
  766.                                         $key;
  767.                                 }
  768.                                 if (!isset($options[$option][$key]['rowTag'])) {
  769.                                     $options[$option][$key]['rowTag'$key;
  770.                                 }
  771.         
  772.                                 foreach (array('mapper''idColumn'as $option2{
  773.                                     if (
  774.                                         !array_key_exists(
  775.                                             $option2,
  776.                                             $options[$option][$key]
  777.                                         )
  778.                                     {
  779.                                         $options[$option][$key][$option2=
  780.                                             $options[$option2];
  781.                                     }
  782.                                 }
  783.                             }
  784.                             $this->_preprocessOptions(
  785.                                 $options[$option][$key],
  786.                                 $option
  787.                             );
  788.                         elseif (self::_isCallback($columnStr)) {
  789.                             if (is_numeric($key)) {
  790.                                 /*
  791.                                  * unit test: _preprocessOptions/
  792.                                  *  throwConfigException_callbackInterface_
  793.                                  *  noArrayKey.phpt
  794.                                  */
  795.                                 throw new XML_Query2XML_ConfigException(
  796.                                     $configPath ': the element name has to be '
  797.                                     . 'specified as the array key when the value '
  798.                                     . 'is specified using an instance of '
  799.                                     . 'XML_Query2XML_Callback.'
  800.                                 );
  801.                             }
  802.                         else {
  803.                             /*
  804.                              * $columnStr is neither a string, an array or an
  805.                              * instance of XML_Query2XML_Callback.
  806.                              *
  807.                              * unit tests:
  808.                              *  _getNestedXMLRecord/
  809.                              *   throwConfigException_attributeSpecWrongType.phpt
  810.                              *  _preprocessOptions/
  811.                              *   throwConfigException_callbackInterface_
  812.                              *    complexAttributeSpec.phpt
  813.                              *    simpleAttributeSpec.phpt
  814.                              *    simpleElementSpec.phpt
  815.                              */
  816.                             throw new XML_Query2XML_ConfigException(
  817.                                 $configPath ': array, string or instance of'
  818.                                 . ' XML_Query2XML_Callback expected, '
  819.                                 . gettype($columnStr)
  820.                                 . ' given.'
  821.                             );
  822.                         }
  823.                     }
  824.                 }
  825.             // end of foreach (array('elements', 'attributes'))
  826.         else {
  827.             // $context == 'attributes'
  828.             if (!isset($options['value'])) {
  829.                 /*
  830.                 * the option "value" is mandatory
  831.                 * unit test: _preprocessOptions/
  832.                 *  throwConfigException_valueOptionMissing.phpt
  833.                 */
  834.                 throw new XML_Query2XML_ConfigException(
  835.                     $options['--q2x--path''[value]: Mandatory option "value" '
  836.                     . 'missing from the complex attribute specification.'
  837.                 );
  838.             }
  839.         }
  840.         
  841.         $opt array('value''condition''dynamicRowTag''idColumn');
  842.         foreach ($opt as $option{
  843.             if (isset($options[$option])) {
  844.                 if (is_string($options[$option])) {
  845.                     $options[$option$this->_buildCommandChain(
  846.                         $options[$option],
  847.                         $options['--q2x--path''[value]'
  848.                     );
  849.                 elseif (
  850.                     !self::_isCallback($options[$option]&&
  851.                     !($option == 'idColumn' && $options[$option=== false)
  852.                 {
  853.                     /*
  854.                     * unit tests:
  855.                     *  _preprocessOptions/
  856.                     *   throwConfigException_callbackInterface_
  857.                     *    complexElementSpec.phpt
  858.                     *    condition.phpt
  859.                     *    idColumn.phpt
  860.                     */
  861.                     throw new XML_Query2XML_ConfigException(
  862.                         $options['--q2x--path''[' $option ']: string or'
  863.                         . ' instance of XML_Query2XML_Callback expected, '
  864.                         . gettype($options[$option])
  865.                         . ' given.'
  866.                     );
  867.                 }
  868.             }
  869.         }
  870.         
  871.         if (isset($options['query'])) {
  872.             $options['sql'$options['query'];
  873.         }
  874.         if (isset($options['sql'])) {
  875.             
  876.             // we will pre-process $options['sql_options'] first
  877.             if (isset($options['query_options'])) {
  878.                 $options['sql_options'$options['query_options'];
  879.             }
  880.             if (!isset($options['sql_options'])) {
  881.                 $options['sql_options'array();
  882.             }
  883.             $sql_options array(
  884.                 'cached''single_record''merge''merge_master''merge_selective'
  885.             );
  886.             foreach ($sql_options as $option{
  887.                 if (!isset($options['sql_options'][$option])) {
  888.                     $options['sql_options'][$optionfalse;
  889.                 }
  890.             }
  891.             if (isset($options['sql_options']['uncached'])) {
  892.                 $options['sql_options']['cached'=
  893.                     !$options['sql_options']['uncached'];
  894.             }
  895.             
  896.             if ($options['sql_options']['cached']{
  897.                 if (!is_array($options['sql'])) {
  898.                     $options['sql'array('query' => $options['sql']);
  899.                 }
  900.                 if (isset($options['sql']['driver'])) {
  901.                     $driver $options['sql']['driver'];
  902.                 else {
  903.                     $driver $this->_driver;
  904.                 }
  905.                 if (
  906.                     !class_exists('XML_Query2XML_Driver_Caching'||
  907.                     !($driver instanceof XML_Query2XML_Driver_Caching)
  908.                 {
  909.                     include_once 'XML/Query2XML/Driver/Caching.php';
  910.                     $options['sql']['driver'new XML_Query2XML_Driver_Caching(
  911.                         $driver
  912.                     );
  913.                 }
  914.             }
  915.             
  916.             if (
  917.                 $options['sql_options']['merge_selective'!== false &&
  918.                 !is_array($options['sql_options']['merge_selective'])
  919.             {
  920.                 /*
  921.                 * unit test: _preprocessOptions/
  922.                 *  throwConfigException_mergeselectiveOptionWrongType.phpt
  923.                 */
  924.                 throw new XML_Query2XML_ConfigException(
  925.                     $options['--q2x--path''[sql_options][merge_selective]: '
  926.                     . 'array expected, '
  927.                     . gettype($options['sql_options']['merge_selective']' given.'
  928.                 );
  929.             }
  930.             // end of pre-processing of $options['sql_options']
  931.             
  932.             if (
  933.                 is_array($options['sql']&& 
  934.                 isset($options['sql']['driver']&&
  935.                 $options['sql']['driver'instanceof XML_Query2XML_Driver
  936.             {
  937.                 $query $options['sql']['driver']->preprocessQuery(
  938.                     $options['sql'],
  939.                     $options['--q2x--path''[sql]'
  940.                 );
  941.             else {
  942.                 $query $this->_driver->preprocessQuery(
  943.                     $options['sql'],
  944.                     $options['--q2x--path''[sql]'
  945.                 );
  946.             }
  947.             $options['--q2x--query_statement'$query;
  948.             if (
  949.                 is_array($options['sql']&& 
  950.                 isset($options['sql']['driver']&&
  951.                 !($options['sql']['driver'instanceof XML_Query2XML_Driver)
  952.             {
  953.                 /*
  954.                  * unit test: _preprocessOptions
  955.                  *  throwConfigException_sqlOptionWrongType.phpt
  956.                  */
  957.                 throw new XML_Query2XML_ConfigException(
  958.                     $options['--q2x--path''[sql][driver]: '
  959.                     . 'instance of XML_Query2XML_Driver expected, '
  960.                     . gettype($options['sql']['driver']' given.'
  961.                 );
  962.             }
  963.             
  964.             if (is_array($options['sql'])) {
  965.                 if (isset($options['sql']['data'])) {
  966.                     if (is_array($options['sql']['data'])) {
  967.                         foreach ($options['sql']['data'as $key => $data{
  968.                             if (is_string($data)) {
  969.                                 $options['sql']['data'][$key=
  970.                                     $this->_buildCommandChain(
  971.                                         $options['sql']['data'][$key],
  972.                                         $options['--q2x--path']
  973.                                             . '[sql][data][' $key ']'
  974.                                     );
  975.                             elseif (!self::_isCallback($data)) {
  976.                                 /*
  977.                                 * unit tests: _preprocessOptions/
  978.                                 *   throwConfigException_callbackInterface_data.phpt
  979.                                 */
  980.                                 throw new XML_Query2XML_ConfigException(
  981.                                     $options['--q2x--path''[sql][data][' $key
  982.                                     . ']: string or'
  983.                                     . ' instance of XML_Query2XML_Callback expected,'
  984.                                     . ' ' gettype($options['sql']['data'][$key])
  985.                                     . ' given.'
  986.                                 );
  987.                             }
  988.                         }
  989.                     else {
  990.                         /*
  991.                         * unit test: _preprocessOptions/
  992.                         *  throwConfigException_dataOptionWrongType.phpt
  993.                         */
  994.                         throw new XML_Query2XML_ConfigException(
  995.                             $options['--q2x--path''[sql][data]: array expected, '
  996.                             . gettype($options['sql']['data']' given.'
  997.                         );
  998.                     }
  999.                 }
  1000.             }
  1001.         // end of if (isset($options['sql'])
  1002.     }
  1003.     
  1004.     /**
  1005.      * Private recursive method that creates the nested XML elements from a record.
  1006.      *
  1007.      * getXML calls this method for every row in the initial result set.
  1008.      * The $tree argument deserves some more explanation. All DOMNodes are stored
  1009.      * in $tree the way they appear in the XML document. The same hirachy needs to be
  1010.      * built so that we can know if a DOMNode that corresponds to a column ID of 2 is
  1011.      * already a child node of a certain XML element. Let's have a look at an example
  1012.      * to clarify this:
  1013.      * <code>
  1014.      * <music_library>
  1015.      *   <artist>
  1016.      *     <artistid>1</artistid>
  1017.      *     <albums>
  1018.      *       <album>
  1019.      *         <albumid>1</albumid>
  1020.      *       </album>
  1021.      *       <album>
  1022.      *         <albumid>2</albumid>
  1023.      *       </album>
  1024.      *     </albums>
  1025.      *   </artist>
  1026.      *   <artist>
  1027.      *     <artistid>3</artistid>
  1028.      *     <albums />
  1029.      *   </artist>
  1030.      * </music_library>
  1031.      * </code>
  1032.      * would be represended in the $tree array as something like this:
  1033.      * <code>
  1034.      * array (
  1035.      *   [1] => array (
  1036.      *     [tag] => DOMElement Object
  1037.      *     [elements] => array (
  1038.      *       [albums] => array (
  1039.      *         [1] => array (
  1040.      *           [tag] => DOMElement Object
  1041.      *         )
  1042.      *         [2] => array (
  1043.      *           [tag] => DOMElement Object
  1044.      *         )
  1045.      *       )
  1046.      *     )
  1047.      *   )
  1048.      *   [2] => array (
  1049.      *     [tag] => DOMElement Object
  1050.      *     [elements] => array
  1051.      *     (
  1052.      *       [albums] => array ()
  1053.      *     )
  1054.      *   )
  1055.      * )
  1056.      * </code>
  1057.      * The numbers in the square brackets are column ID values.
  1058.      *
  1059.      * @param array       $record   An associative array representing a record;
  1060.      *                               column names must be used as keys.
  1061.      * @param array       &$options An array containing the options for this nested
  1062.      *                               element; this will be a subset of the array
  1063.      *                               originally passed to getXML().
  1064.      * @param DOMDocument $dom      An instance of DOMDocument.
  1065.      * @param array       &$tree    An associative multi-dimensional array, that is
  1066.      *                               used to store the information which tag has
  1067.      *                               already been created for a certain ID column
  1068.      *                               value. It's format is:
  1069.      *                               Array(
  1070.      *                                 "$id1" => Array(
  1071.      *                                   'tag' => DOMElement,
  1072.      *                                   'elements' => Array(
  1073.      *                                     "$id2" => Array(
  1074.      *                                       'tag' => DOMElement,
  1075.      *                                       'elements' => Array( ... )
  1076.      *                                     ),
  1077.      *                                     "$id3" => ...
  1078.      *                                   )
  1079.      *                                 )
  1080.      *                               )
  1081.      *
  1082.      * @return mixed           The XML element's representation as a new instance of
  1083.      *                          DOMNode or the boolean value false (meaning no
  1084.      *                          new tag was created).
  1085.      * @throws XML_Query2XML_DBException  Bubbles up through this method if thrown by
  1086.      *                          - _processComplexElementSpecification()
  1087.      * @throws XML_Query2XML_XMLException Bubbles up through this method if thrown by
  1088.      *                          - _createDOMElement()
  1089.      *                          - _setDOMAttribute
  1090.      *                          - _appendTextChildNode()
  1091.      *                          - _addNewDOMChild()
  1092.      *                          - _addDOMChildren()
  1093.      *                          - _processComplexElementSpecification()
  1094.      *                          - _expandShortcuts()
  1095.      *                          - _executeEncoder()
  1096.      * @throws XML_Query2XML_ConfigException  Thrown if
  1097.      *                          - $options['idColumn'] is not set
  1098.      *                          - $options['elements'] is set but not an array
  1099.      *                          - $options['attributes'] is set but not an array
  1100.      *                          Bubbles up through this method if thrown by
  1101.      *                          - _applyColumnStringToRecord()
  1102.      *                          - _processComplexElementSpecification()
  1103.      *                          - _expandShortcuts()
  1104.      * @throws XML_Query2XML_Exception  Bubbles up through this method if thrown by
  1105.      *                          - _expandShortcuts()
  1106.      *                          - _applyColumnStringToRecord()
  1107.      */
  1108.     private function _getNestedXMLRecord($record&$options$dom&$tree)
  1109.     {
  1110.         // the default row tag name is 'row'
  1111.         if (isset($options['dynamicRowTag'])) {
  1112.             $rowTagName $this->_applyColumnStringToRecord(
  1113.                 $options['dynamicRowTag'],
  1114.                 $record,
  1115.                 $options['--q2x--path''[dynamicRowTag]'
  1116.             );
  1117.         else {
  1118.             $rowTagName $options['rowTag'];
  1119.         }
  1120.         
  1121.         if ($options['idColumn'=== false{
  1122.             static $uniqueIdCounter 0;
  1123.             $id = ++$uniqueIdCounter;
  1124.         else {
  1125.             $id $this->_applyColumnStringToRecord(
  1126.                 $options['idColumn'],
  1127.                 $record,
  1128.                 $options['--q2x--path''[idColumn]'
  1129.             );
  1130.         
  1131.             if ($id === null{
  1132.                 // the ID column is NULL
  1133.                 return false;
  1134.             elseif (is_object($id|| is_array($id)) {
  1135.                 /*
  1136.                 * unit test: _getNestedXMLRecord/
  1137.                 *   throwConfigException_idcolumnOptionWrongTypeArray.phpt
  1138.                 *   throwConfigException_idcolumnOptionWrongTypeObject.phpt
  1139.                 */
  1140.                 throw new XML_Query2XML_ConfigException(
  1141.                     $options['--q2x--path''[idColumn]: Must evaluate to a '
  1142.                     . 'value that is not an object or an array.'
  1143.                 );
  1144.             }
  1145.         }
  1146.         
  1147.         /* Is there already an identical tag (identity being determined by the
  1148.         *  value of the ID-column)?
  1149.         */
  1150.         if (isset($tree[$id])) {
  1151.             if (isset($options['elements'])) {
  1152.                 foreach ($options['elements'as $tagName => $column{
  1153.                     if (is_array($column)) {
  1154.                         $this->_processComplexElementSpecification(
  1155.                             $record,
  1156.                             $options['elements'][$tagName],
  1157.                             $tree[$id],
  1158.                             $tagName
  1159.                         );
  1160.                     }
  1161.                 }
  1162.             }
  1163.             /*
  1164.             * We return false because $tree[$id]['tag'] is already
  1165.             * a child of the parent element.
  1166.             */
  1167.             return false;
  1168.         else {
  1169.             $tree[$idarray();
  1170.             
  1171.             if (isset($options['value'])) {
  1172.                 $parsedValue $this->_applyColumnStringToRecord(
  1173.                     $options['value'],
  1174.                     $record,
  1175.                     $options['--q2x--path''[value]'
  1176.                 );
  1177.                 if (!$this->_evaluateCondition($parsedValue$options['value'])) {
  1178.                     // this element is to be skipped
  1179.                     return false;
  1180.                 }
  1181.             }
  1182.             if (isset($options['condition'])) {
  1183.                 $continue $this->_applyColumnStringToRecord(
  1184.                     $options['condition'],
  1185.                     $record,
  1186.                     $options['--q2x--path''[condition]'
  1187.                 );
  1188.                 if (!$continue{
  1189.                     // this element is to be skipped
  1190.                     return false;
  1191.                 }
  1192.             }
  1193.             $tree[$id]['tag'self::_createDOMElement(
  1194.                 $dom,
  1195.                 $rowTagName,
  1196.                 $options['--q2x--path''[rowTag/dynamicRowTag]'
  1197.             );
  1198.  
  1199.             $tag $tree[$id]['tag'];
  1200.             
  1201.             // add attributes
  1202.             if (isset($options['attributes'])) {
  1203.                 if (!isset($options['processed'])) {
  1204.                     $options['attributes'self::_expandShortcuts(
  1205.                         $options['attributes'],
  1206.                         $record,
  1207.                         $options['mapper'],
  1208.                         $options['--q2x--path''[attributes]'
  1209.                     );
  1210.                 }
  1211.                 foreach ($options['attributes'as $attributeName => $column{
  1212.                     if (is_array($column)) {
  1213.                         // complex attribute specification
  1214.                         $this->_processComplexAttributeSpecification(
  1215.                             $attributeName$record$column$tree[$id]['tag']
  1216.                         );
  1217.                     else {
  1218.                         // simple attribute specifications
  1219.                         $attributeValue $this->_applyColumnStringToRecord(
  1220.                             $column,
  1221.                             $record,
  1222.                             $options['--q2x--path']
  1223.                             . '[attributes][' $attributeName ']'
  1224.                         );
  1225.                         if ($this->_evaluateCondition($attributeValue$column)) {
  1226.                             self::_setDOMAttribute(
  1227.                                 $tree[$id]['tag'],
  1228.                                 $attributeName,
  1229.                                 self::_executeEncoder(
  1230.                                     $attributeValue,
  1231.                                     $options
  1232.                                 ),
  1233.                                 $options['--q2x--path']
  1234.                                 . '[attributes][' $attributeName ']'
  1235.                             );
  1236.                         }
  1237.                     }
  1238.                 }
  1239.             }
  1240.             if (isset($options['value'])) {
  1241.                 if ($parsedValue instanceof DOMNode || is_array($parsedValue)) {
  1242.                     /*
  1243.                     * The value returned from _applyColumnStringToRecord() and
  1244.                     * stored in $parsedValue is an instance of DOMNode or an
  1245.                     * array of DOMNode instances. _addDOMChildren() will handle
  1246.                     * both.
  1247.                     */
  1248.                     self::_addDOMChildren(
  1249.                         $tree[$id]['tag'],
  1250.                         $parsedValue,
  1251.                         $options['--q2x--path''[value]',
  1252.                         true
  1253.                     );
  1254.                 else {
  1255.                     if ($parsedValue !== false && !is_null($parsedValue)) {
  1256.                         self::_appendTextChildNode(
  1257.                             $tree[$id]['tag'],
  1258.                             self::_executeEncoder(
  1259.                                 $parsedValue,
  1260.                                 $options
  1261.                             ),
  1262.                             $options['--q2x--path''[value]'
  1263.                         );
  1264.                     }
  1265.                 }
  1266.             }
  1267.             
  1268.             // add child elements
  1269.             if (isset($options['elements'])) {
  1270.                 if (!isset($options['processed'])) {
  1271.                     $options['elements'self::_expandShortcuts(
  1272.                         $options['elements'],
  1273.                         $record,
  1274.                         $options['mapper'],
  1275.                         $options['--q2x--path''[elements]'
  1276.                     );
  1277.                 }
  1278.                 foreach ($options['elements'as $tagName => $column{
  1279.                     if (is_array($column)) {
  1280.                         // complex element specification
  1281.                         $this->_processComplexElementSpecification(
  1282.                             $record,
  1283.                             $options['elements'][$tagName],
  1284.                             $tree[$id],
  1285.                             $tagName
  1286.                         );
  1287.                     else {
  1288.                         // simple element specification
  1289.                         $tagValue $this->_applyColumnStringToRecord(
  1290.                             $column,
  1291.                             $record,
  1292.                             $options['--q2x--path''[elements][' $tagName ']'
  1293.                         );
  1294.                         if ($this->_evaluateCondition($tagValue$column)) {
  1295.                             if (
  1296.                                 $tagValue instanceof DOMNode ||
  1297.                                 is_array($tagValue)
  1298.                             {
  1299.                                 /*
  1300.                                 * The value returned from
  1301.                                 * _applyColumnStringToRecord() and stored in
  1302.                                 * $tagValue is an instance of DOMNode or an array
  1303.                                 * of DOMNode instances. self::_addDOMChildren()
  1304.                                 * will handle both.
  1305.                                 */
  1306.                                 self::_addDOMChildren(
  1307.                                     self::_addNewDOMChild(
  1308.                                         $tree[$id]['tag'],
  1309.                                         $tagName,
  1310.                                         $options['--q2x--path']
  1311.                                         . '[elements][' $tagName ']'
  1312.                                     ),
  1313.                                     $tagValue,
  1314.                                     $options['--q2x--path']
  1315.                                     . '[elements][' $tagName ']',
  1316.                                     true
  1317.                                 );
  1318.                             else {
  1319.                                 self::_addNewDOMChild(
  1320.                                     $tree[$id]['tag'],
  1321.                                     $tagName,
  1322.                                     $options['--q2x--path']
  1323.                                     . '[elements][' $tagName ']',
  1324.                                     self::_executeEncoder(
  1325.                                         $tagValue,
  1326.                                         $options
  1327.                                     )
  1328.                                 );
  1329.                             }
  1330.                         }
  1331.                     }
  1332.                 }
  1333.             }
  1334.             
  1335.             // some things only need to be done once
  1336.             $options['processed'true;
  1337.             
  1338.             /*
  1339.             *  We return $tree[$id]['tag'] because it needs to be added to it's
  1340.             *  parent; this is to be handled by the method that called
  1341.             *  _getNestedXMLRecord().
  1342.             */
  1343.             return $tree[$id]['tag'];
  1344.         }
  1345.     }
  1346.     
  1347.     /**
  1348.      * Private method that will expand asterisk characters in an array
  1349.      * of simple element specifications.
  1350.      *
  1351.      * This method gets called to handle arrays specified using the 'elements'
  1352.      * or the 'attributes' option. An element specification that contains an
  1353.      * asterisk will be duplicated for each column present in $record.
  1354.      * Please see the {@tutorial XML_Query2XML.pkg tutorial} for details.
  1355.      *
  1356.      * @param Array  &$elements  An array of simple element specifications.
  1357.      * @param Array  &$record    An associative array that represents a single
  1358.      *                            record.
  1359.      * @param mixed  $mapper     A valid argument for call_user_func(), a full method
  1360.      *                            method name (e.g. "MyMapperClass::map") or a value
  1361.      *                            that == false for no special mapping at all.
  1362.      * @param string $configPath The config path; used for exception messages.
  1363.      *
  1364.      * @return Array The extended array.
  1365.      * @throws XML_Query2XML_ConfigException If only the column part but not the
  1366.      *                         explicitly defined tagName part contains an asterisk.
  1367.      * @throws XML_Query2XML_Exception Will bubble up if it is thrown by
  1368.      *                         _mapSQLIdentifierToXMLName(). This should never
  1369.      *                         happen as _getNestedXMLRecord() already checks if
  1370.      *                         $mapper is callable.
  1371.      * @throws XML_Query2XML_XMLException Will bubble up if it is thrown by
  1372.      *                         _mapSQLIdentifierToXMLName() which will happen if the
  1373.      *                         $mapper function called, throws any exception.
  1374.      */
  1375.     private function _expandShortcuts(&$elements&$record$mapper$configPath)
  1376.     {
  1377.         $newElements array();
  1378.         foreach ($elements as $tagName => $column{
  1379.             if (is_numeric($tagName)) {
  1380.                 $tagName $column;
  1381.             }
  1382.             if (!is_array($column&& strpos($tagName'*'!== false{
  1383.                 // expand all occurences of '*' to all column names
  1384.                 foreach ($record as $columnName => $value{
  1385.                     $newTagName str_replace('*'$columnName$tagName);
  1386.                     if (is_string($column)) {
  1387.                         $newColumn str_replace('*'$columnName$column);
  1388.                     elseif (
  1389.                         class_exists('XML_Query2XML_Data'&&
  1390.                         $column instanceof XML_Query2XML_Data
  1391.                     {
  1392.                         $newColumn clone $column;
  1393.                         $callback  $newColumn->getFirstPreProcessor();
  1394.                         if (
  1395.                             class_exists('XML_Query2XML_Data_Source'&&
  1396.                             $callback instanceof XML_Query2XML_Data_Source
  1397.                         {
  1398.                             $callback->replaceAsterisks($columnName);
  1399.                         }
  1400.                     else {
  1401.                         $newColumn =$column;
  1402.                     }
  1403.                     // do the mapping
  1404.                     $newTagName self::_mapSQLIdentifierToXMLName(
  1405.                         $newTagName,
  1406.                         $mapper,
  1407.                         $configPath '[' $tagName ']'
  1408.                     );
  1409.                     if (!isset($newElements[$newTagName])) {
  1410.                         // only if the tagName hasn't already been used
  1411.                         $newElements[$newTagName$newColumn;
  1412.                     }
  1413.                 }
  1414.             else {
  1415.                 /*
  1416.                 * Complex element specifications will always be dealt with here.
  1417.                 * We don't want any mapping or handling of the asterisk shortcut
  1418.                 * to be done for complex element specifications.
  1419.                 */
  1420.             
  1421.                 if (!is_array($column)) {
  1422.                     // do the mapping but not for complex element specifications
  1423.                     $tagName self::_mapSQLIdentifierToXMLName(
  1424.                         $tagName,
  1425.                         $mapper,
  1426.                         $configPath '[' $tagName ']'
  1427.                     );
  1428.                 }
  1429.                     
  1430.                 /*
  1431.                  * explicit specification without an asterisk;
  1432.                  * this always overrules an expanded asterisk
  1433.                  */
  1434.                 unset($newElements[$tagName]);
  1435.                 $newElements[$tagName$column;
  1436.             }
  1437.         }
  1438.         return $newElements;
  1439.     }
  1440.     
  1441.     /**
  1442.      * Maps an SQL identifier to an XML name using the supplied $mapper.
  1443.      *
  1444.      * @param string $sqlIdentifier The SQL identifier as a string.
  1445.      * @param mixed  $mapper        A valid argument for call_user_func(), a full
  1446.      *                               method method name (e.g. "MyMapperClass::map")
  1447.      *                               or a value that == false for no special mapping
  1448.      *                               at all.
  1449.      * @param string $configPath    The config path; used for exception messages.
  1450.      *
  1451.      * @return string The mapped XML name.
  1452.      * @throws XML_Query2XML_Exception If $mapper is not callable. This should never
  1453.      *                               happen as _getNestedXMLRecord() already checks
  1454.      *                               if $mapper is callable.
  1455.      * @throws XML_Query2XML_XMLException If the $mapper function called, throws any
  1456.      *                               exception.
  1457.      */
  1458.     private function _mapSQLIdentifierToXMLName($sqlIdentifier$mapper$configPath)
  1459.     {
  1460.         if (!$mapper{
  1461.             // no mapper was defined
  1462.             $xmlName $sqlIdentifier;
  1463.         else {
  1464.             if (is_callable($mapperfalse$callableName)) {
  1465.                 try {
  1466.                     $xmlName call_user_func($mapper$sqlIdentifier);
  1467.                 catch (Exception $e{
  1468.                     /*
  1469.                     * This will also catch XML_Query2XML_ISO9075Mapper_Exception
  1470.                     * if $mapper was "XML_Query2XML_ISO9075Mapper::map".
  1471.                     * unit test:
  1472.                     *  _mapSQLIdentifierToXMLName/throwXMLException.phpt
  1473.                     */
  1474.                     throw new XML_Query2XML_XMLException(
  1475.                         $configPath ': Could not map "' $sqlIdentifier
  1476.                         . '" to an XML name using the mapper '
  1477.                         . $callableName ': ' $e->getMessage()
  1478.                     );
  1479.                 }
  1480.             else {
  1481.                 /*
  1482.                 * This should never happen as _preprocessOptions() already
  1483.                 * checks if $mapper is callable. Therefore no unit tests
  1484.                 * can be provided for this exception.
  1485.                 */
  1486.                 throw new XML_Query2XML_ConfigException(
  1487.                     $configPath ': The mapper "' $callableName
  1488.                     . '" is not callable.'
  1489.                 );
  1490.             }
  1491.         }
  1492.         return $xmlName;
  1493.     }
  1494.     
  1495.     /**
  1496.      * Private method that processes a complex element specification
  1497.      * for {@link XML_Query2XML::_getNestedXMLRecord()}.
  1498.      *
  1499.      * @param array  &$record  The current record.
  1500.      * @param array  &$options The current options.
  1501.      * @param array  &$tree    Associative multi-dimensional array, that is used to
  1502.      *                          store which tags have already been created
  1503.      * @param string $tagName  The element's name.
  1504.      *
  1505.      * @return void 
  1506.      * @throws XML_Query2XML_XMLException This exception will bubble up
  1507.      *                         if it is thrown by _getNestedXMLRecord(),
  1508.      *                         _applySqlOptionsToRecord() or _addDOMChildren().
  1509.      * @throws XML_Query2XML_DBException  This exception will bubble up
  1510.      *                         if it is thrown by _applySqlOptionsToRecord()
  1511.      *                         or _getNestedXMLRecord().
  1512.      * @throws XML_Query2XML_ConfigException This exception will bubble up
  1513.      *                         if it is thrown by _applySqlOptionsToRecord()
  1514.      *                         or _getNestedXMLRecord().
  1515.      * @throws XML_Query2XML_Exception  This exception will bubble up if it
  1516.      *                         is thrown by _getNestedXMLRecord().
  1517.      */
  1518.     private function _processComplexElementSpecification(&$record&$options&$tree,
  1519.         $tagName)
  1520.     {
  1521.         $tag $tree['tag'];
  1522.         if (!isset($tree['elements'])) {
  1523.             $tree['elements'array();
  1524.         }
  1525.         if (!isset($tree['elements'][$tagName])) {
  1526.             $tree['elements'][$tagName]            array();
  1527.             $tree['elements'][$tagName]['rootTag'self::_addNewDOMChild(
  1528.                 $tag,
  1529.                 $options['rootTag'],
  1530.                 $options['--q2x--path''[rootTag]'
  1531.             );
  1532.         }
  1533.         
  1534.         $records =$this->_applySqlOptionsToRecord($options$record);
  1535.         
  1536.         for ($i 0$i count($records)$i++{
  1537.             self::_addDOMChildren(
  1538.                 $tree['elements'][$tagName]['rootTag'],
  1539.                 $this->_getNestedXMLRecord(
  1540.                     $records[$i],
  1541.                     $options,
  1542.                     $tag->ownerDocument,
  1543.                     $tree['elements'][$tagName]
  1544.                 ),
  1545.                 $options['--q2x--path']
  1546.             );
  1547.         }
  1548.     }
  1549.     
  1550.     /**
  1551.      * Private method that processes a complex attribute specification
  1552.      * for {@link XML_Query2XML::_getNestedXMLRecord()}.
  1553.      *
  1554.      * A complex attribute specification consists of an associative array
  1555.      * with the keys 'value' (mandatory), 'condition', 'sql' and 'sql_options'.
  1556.      *
  1557.      * @param string  $attributeName The name of the attribute as it was specified
  1558.      *                                using the array key of the complex attribute
  1559.      *                                specification.
  1560.      * @param array   &$record       The current record.
  1561.      * @param array   &$options      The complex attribute specification itself.
  1562.      * @param DOMNode $tag           The DOMNode to which the attribute is to be
  1563.      *                                added.
  1564.      *
  1565.      * @return void 
  1566.      * @throws XML_Query2XML_XMLException This exception will bubble up
  1567.      *                           if it is thrown by _setDOMAttribute(),
  1568.      *                           _applyColumnStringToRecord(),
  1569.      *                           _applySqlOptionsToRecord() or _executeEncoder().
  1570.      * @throws XML_Query2XML_DBException  This exception will bubble up
  1571.      *                           if it is thrown by _applySqlOptionsToRecord().
  1572.      * @throws XML_Query2XML_ConfigException This exception will bubble up
  1573.      *                           if it is thrown by _applySqlOptionsToRecord() or
  1574.      *                           _applyColumnStringToRecord(). It will also be thrown
  1575.      *                           by this method if $options['value'] is not set.
  1576.      */
  1577.     private function _processComplexAttributeSpecification($attributeName&$record,
  1578.         &$options$tag)
  1579.     {
  1580.         if (isset($options['condition'])) {
  1581.             $continue $this->_applyColumnStringToRecord(
  1582.                 $options['condition'],
  1583.                 $record,
  1584.                 $options['--q2x--path''[condition]'
  1585.             );
  1586.             if (!$continue{
  1587.                 // this element is to be skipped
  1588.                 return;
  1589.             }
  1590.         }
  1591.         
  1592.         // only fetching a single record makes sense for a single attribute
  1593.         $options['sql_options']['single_record'true;
  1594.         
  1595.         $records $this->_applySqlOptionsToRecord($options$record);
  1596.         if (count($records== 0{
  1597.             /*
  1598.             * $options['sql'] was set but the query did not return any records.
  1599.             * Therefore this attribute is to be skipped.
  1600.             */
  1601.             return;
  1602.         }
  1603.         $attributeRecord $records[0];
  1604.         
  1605.         $attributeValue $this->_applyColumnStringToRecord(
  1606.             $options['value'],
  1607.             $attributeRecord,
  1608.             $options['--q2x--path''[value]'
  1609.         );
  1610.         if ($this->_evaluateCondition($attributeValue$options['value'])) {
  1611.             self::_setDOMAttribute(
  1612.                 $tag,
  1613.                 $attributeName,
  1614.                 self::_executeEncoder($attributeValue$options),
  1615.                 $options['--q2x--path''[value]'
  1616.             );
  1617.         }
  1618.     }
  1619.                     
  1620.     /**
  1621.      * Private method to apply the givenen sql option to a record.
  1622.      *
  1623.      * This method handles the sql options 'single_record',
  1624.      * 'merge', 'merge_master' and 'merge_selective'. Please see the
  1625.      * {@tutorial XML_Query2XML.pkg tutorial} for details.
  1626.      * 
  1627.      * @param array &$options An associative multidimensional array of options.
  1628.      * @param array &$record  The current record as an associative array.
  1629.      *
  1630.      * @return array          An indexed array of records that are themselves
  1631.      *                         represented as associative arrays.
  1632.      * @throws XML_Query2XML_ConfigException This exception is thrown if
  1633.      *                         - a column specified in merge_selective does not exist
  1634.      *                           in the result set
  1635.      *                         - it bubbles up from _applyColumnStringToRecord()
  1636.      * @throws XML_Query2XML_DBException This exception will bubble up
  1637.      *                         if it is thrown by _getAllRecords().
  1638.      * @throws XML_Query2XML_XMLException It will bubble up if it is thrown
  1639.      *                         by _applyColumnStringToRecord().
  1640.      */
  1641.     private function _applySqlOptionsToRecord(&$options&$record)
  1642.     {
  1643.         if (!isset($options['sql'])) {
  1644.             return array($record);
  1645.         }
  1646.         
  1647.         $single_record   $options['sql_options']['single_record'];
  1648.         $merge           $options['sql_options']['merge'];
  1649.         $merge_master    $options['sql_options']['merge_master'];
  1650.         $merge_selective $options['sql_options']['merge_selective'];
  1651.  
  1652.         $sql $options['sql'];
  1653.         if (is_array($sql)) {
  1654.             if (isset($sql['data'])) {
  1655.                 foreach ($sql['data'as $key => $columnStr{
  1656.                     $sql['data'][$key$this->_applyColumnStringToRecord(
  1657.                         $columnStr,
  1658.                         $record,
  1659.                         $options['--q2x--path''[sql][data][' $key ']'
  1660.                     );
  1661.                 }
  1662.             }
  1663.         }
  1664.         $sqlConfigPath $options['--q2x--path''[sql]';
  1665.         
  1666.         $records =$this->_getAllRecords(
  1667.             $sql,
  1668.             $sqlConfigPath,
  1669.             $options['--q2x--query_statement']
  1670.         );
  1671.         if ($single_record && isset($records[0])) {
  1672.             $records array($records[0]);
  1673.         }
  1674.         
  1675.         if (is_array($merge_selective)) {
  1676.             // selective merge
  1677.             if ($merge_master{
  1678.                 // current records are master
  1679.                 for ($ii 0$ii count($merge_selective)$ii++{
  1680.                     for ($i 0$i count($records)$i++{
  1681.                         if (!array_key_exists($merge_selective[$ii]$record)) {
  1682.                             /* Selected field does not exist in the parent record
  1683.                             * (passed as argumnet $record)
  1684.                             * unit test: _applySqlOptionsToRecord/
  1685.                             *  throwConfigException_mergeMasterTrue.phpt
  1686.                             */
  1687.                             throw new XML_Query2XML_ConfigException(
  1688.                                 $options['--q2x--path''[sql_options]'
  1689.                                 . '[merge_selective]['$ii ']: The column "'
  1690.                                 . $merge_selective[$ii'" '
  1691.                                 . 'was not found in the result set.'
  1692.                             );
  1693.                         }
  1694.                         if (!array_key_exists($merge_selective[$ii]$records[$i])) {
  1695.                             // we are the master, so only if it does not yet exist
  1696.                             $records[$i][$merge_selective[$ii]] =
  1697.                                 $record[$merge_selective[$ii]];
  1698.                         }
  1699.                     }
  1700.                 }
  1701.             else {
  1702.                 // parent record is master
  1703.                 for ($ii 0$ii count($merge_selective)$ii++{
  1704.                     for ($i 0$i count($records)$i++{
  1705.                         if (!array_key_exists($merge_selective[$ii]$record)) {
  1706.                             /* Selected field does not exist in the parent record
  1707.                             *  (passed as argumnet $record)
  1708.                             *  unit test: _applySqlOptionsToRecord/
  1709.                             *   throwConfigException_mergeMasterFalse.phpt
  1710.                             */
  1711.                             throw new XML_Query2XML_ConfigException(
  1712.                                 $options['--q2x--path''[sql_options]'
  1713.                                 . '[merge_selective]['$ii ']: The column "'
  1714.                                 . $merge_selective[$ii'" '
  1715.                                 . 'was not found in the result set.'
  1716.                             );
  1717.                         }
  1718.                         // parent is master!
  1719.                         $records[$i][$merge_selective[$ii]] =
  1720.                             $record[$merge_selective[$ii]];
  1721.                     }
  1722.                 }
  1723.             }
  1724.         elseif ($merge{
  1725.             // regular merge
  1726.             if ($merge_master{
  1727.                 for ($i 0$i count($records)$i++{
  1728.                     $records[$iarray_merge($record$records[$i]);
  1729.                 
  1730.             else {
  1731.                 for ($i 0$i count($records)$i++{
  1732.                     $records[$iarray_merge($records[$i]$record);
  1733.                 }
  1734.             }
  1735.         }
  1736.         return $records;
  1737.     }
  1738.     
  1739.     /**
  1740.      * Private method to apply a column string to a record.
  1741.      * Please see the tutorial for details on the different column strings.
  1742.      *
  1743.      * @param string $columnStr  A valid column name or an instance of a class
  1744.      *                            implementing XML_Query2XML_Callback.
  1745.      * @param array  &$record    The record as an associative array.
  1746.      * @param string $configPath The config path; used for exception messages.
  1747.      *
  1748.      * @return mixed A value that can be cast to a string or an instance of DOMNode.
  1749.      * @throws XML_Query2XML_ConfigException  Thrown if $columnStr is not
  1750.      *                a string or an instance of XML_Query2XML_Callback or if
  1751.      *                $record[$columnStr] does not exist (and $columnStr has
  1752.      *                no special prefix).
  1753.      * @throws XML_Query2XML_XMLException     Thrown if the '&' prefix was used
  1754.      *                but the data was not unserializeable, i.e. not valid XML data.
  1755.      */
  1756.     private function _applyColumnStringToRecord($columnStr&$record$configPath)
  1757.     {
  1758.         if (self::_isCallback($columnStr)) {
  1759.             $value $columnStr->execute($record);
  1760.         elseif (is_string($columnStr)) {
  1761.             if (array_key_exists($columnStr$record)) {
  1762.                 $value $record[$columnStr];
  1763.             else {
  1764.                 /*
  1765.                 * unit test:
  1766.                 *  _applyColumnStringToRecord/throwConfigException_element1.phpt
  1767.                 *  _applyColumnStringToRecord/throwConfigException_element2.phpt
  1768.                 *  _applyColumnStringToRecord/throwConfigException_idcolumn.phpt
  1769.                 */
  1770.                 throw new XML_Query2XML_ConfigException(
  1771.                     $configPath ': The column "' $columnStr
  1772.                     . '" was not found in the result set.'
  1773.                 );
  1774.                 
  1775.             }
  1776.         else {
  1777.             // should never be reached
  1778.             throw new XML_Query2XML_ConfigException(
  1779.                 $configPath ': string or instance of XML_Query2XML_Callback'
  1780.                 . ' expected, ' gettype($columnStr' given.'
  1781.             );
  1782.         }
  1783.         return $value;
  1784.     }
  1785.     
  1786.     /**
  1787.      * Returns whether $value is to be included in the output.
  1788.      * If $spec is a string an is prefixed by a question mark this method will
  1789.      * return false if $value is null or is a string with a length of zero. In
  1790.      * any other case, this method will return the true.
  1791.      *
  1792.      * @param string $value The value.
  1793.      * @param mixed  $spec  The value specification. This can be a string
  1794.      *                       or an instance of XML_Query2XML_Callback.
  1795.      *
  1796.      * @return boolean Whether $value is to be included in the output.
  1797.      */
  1798.     private function _evaluateCondition($value$spec)
  1799.     {
  1800.         return !class_exists('XML_Query2XML_Data_Condition'||
  1801.                !$spec instanceof XML_Query2XML_Data_Condition ||
  1802.                $spec->evaluateCondition($value);
  1803.     }
  1804.             
  1805.     /**
  1806.      * Private method to fetch all records from a result set.
  1807.      *
  1808.      * @param mixed  $sql            The SQL query as a string or an array.
  1809.      * @param string $configPath     The config path; used for exception messages.
  1810.      * @param string $queryStatement The query as a string; it will be used for
  1811.      *                                logging and profiling.
  1812.      *
  1813.      * @return array An array of records. Each record itself will be an
  1814.      *                    associative array.
  1815.      */
  1816.     private function &_getAllRecords($sql$configPath$queryStatement)
  1817.     {
  1818.         // $queryStatement will be used for profiling
  1819.         if ($this->_profiling || $this->_debug{
  1820.             $loggingQuery $queryStatement;
  1821.             if (is_array($sql&& isset($sql['data']&& is_array($sql['data'])) {
  1822.                 $loggingQuery .= '; DATA:' implode(','$sql['data']);
  1823.             }
  1824.             $this->_debugStartQuery($loggingQuery$queryStatement);
  1825.         }
  1826.         
  1827.         if (is_array($sql&& isset($sql['driver'])) {
  1828.             $driver $sql['driver'];
  1829.         else {
  1830.             $driver $this->_driver;
  1831.         }
  1832.         $records $driver->getAllRecords($sql$configPath);
  1833.         
  1834.         $this->_debugStopQuery($queryStatement);
  1835.         return $records;
  1836.     }
  1837.     
  1838.     /**
  1839.      * Initializes a query's profile (only used if profiling is turned on).
  1840.      *
  1841.      * @param mixed &$sql The SQL query as a string or an array.
  1842.      *
  1843.      * @return void 
  1844.      * @see startProfiling()
  1845.      */
  1846.     private function _initQueryProfile(&$sql)
  1847.     {
  1848.         if (!isset($this->_profile['queries'][$sql])) {
  1849.             $this->_profile['queries'][$sqlarray(
  1850.                 'count' => 0,
  1851.                 'runTimes' => array()
  1852.             );
  1853.         }
  1854.     }
  1855.     
  1856.     /**
  1857.      * Starts the debugging and profiling of the query passed as argument.
  1858.      *
  1859.      * @param string $loggingQuery   The query statement as it will be logged.
  1860.      * @param string $profilingQuery The query statement as it will be used for
  1861.      *                                profiling.
  1862.      *
  1863.      * @return void 
  1864.      */
  1865.     private function _debugStartQuery($loggingQuery$profilingQuery)
  1866.     {
  1867.         $this->_debug('QUERY: ' $loggingQuery);
  1868.         if ($this->_profiling{
  1869.             $this->_initQueryProfile($profilingQuery);
  1870.             ++$this->_profile['queries'][$profilingQuery]['count'];
  1871.             $this->_profile['queries'][$profilingQuery]['runTimes'][array(
  1872.                 'start' => microtime(true),
  1873.                 'stop' => 0
  1874.             );
  1875.         }
  1876.     }
  1877.     
  1878.     /**
  1879.      * Ends the debugging and profiling of the query passed as argument.
  1880.      *
  1881.      * @param string $profilingQuery The query statement as it will be used for
  1882.      *                                profiling.
  1883.      *
  1884.      * @return void 
  1885.      */
  1886.     private function _debugStopQuery($profilingQuery)
  1887.     {
  1888.         $this->_debug('DONE');
  1889.         if ($this->_profiling{
  1890.             $this->_initQueryProfile($profilingQuery);
  1891.             $lastIndex =
  1892.                 count(
  1893.                     $this->_profile['queries'][$profilingQuery]['runTimes']
  1894.                 1;
  1895.             
  1896.             $this->_profile['queries'][$profilingQuery]['runTimes'][$lastIndex]['stop'=
  1897.                 microtime(true);
  1898.         }
  1899.     }
  1900.     
  1901.     /**
  1902.      * Stops the DB profiling.
  1903.      * This will set $this->_profile['dbDuration'].
  1904.      *
  1905.      * @return void 
  1906.      */
  1907.     private function _stopDBProfiling()
  1908.     {
  1909.         if ($this->_profiling && isset($this->_profile['start'])) {
  1910.             $this->_profile['dbStop']     microtime(1);
  1911.             $this->_profile['dbDuration'=
  1912.                 $this->_profile['dbStop'$this->_profile['start'];
  1913.         }
  1914.     }
  1915.     
  1916.     /**
  1917.      * Private method used to log debug messages.
  1918.      * This method will do no logging if $this->_debug is set to false.
  1919.      *
  1920.      * @param string $msg The message to log.
  1921.      *
  1922.      * @return void 
  1923.      * @see _debugLogger
  1924.      * @see _debug
  1925.      */
  1926.     private function _debug($msg)
  1927.     {
  1928.         if ($this->_debug{
  1929.             $this->_debugLogger->log($msg);
  1930.         }
  1931.     }
  1932.     
  1933.     /**
  1934.      * Returns whether $object is an instance of XML_Query2XML_Callback.
  1935.      *
  1936.      * @param mixed $object The variable to check.
  1937.      *
  1938.      * @return boolean 
  1939.      */
  1940.     private static function _isCallback($object)
  1941.     {
  1942.         return is_object($object&&
  1943.                interface_exists('XML_Query2XML_Callback'&&
  1944.                $object instanceof XML_Query2XML_Callback;
  1945.     }
  1946.     
  1947.     /**
  1948.      * Parse specifications that use the prifixes ?, &, =, ^, :,  or #.
  1949.      *
  1950.      * This method will produce a number of chained Data Class objects all of
  1951.      * which be an instance of the abstract class XML_Query2XML_Data.
  1952.      *
  1953.      * @param string $columnStr  The original specification.
  1954.      * @param string $configPath The config path; used for exception messages.
  1955.      *
  1956.      * @return mixed An instance of XML_Query2XML_Callback or a column
  1957.      *                name as a string.
  1958.      * @throws XML_Query2XML_ConfigException Bubbles up through this method if
  1959.      *                                        thrown by any of the command class
  1960.      *                                        constructors.
  1961.      */
  1962.     private function _buildCommandChain($columnStr$configPath)
  1963.     {
  1964.         $prefixList implode(''array_keys($this->_prefixes));
  1965.         if (ltrim($columnStr$prefixList== $columnStr{
  1966.             return $columnStr;
  1967.         }
  1968.         
  1969.         $firstCallback null;
  1970.         for ($i 0$i strlen($columnStr)$i++{
  1971.             $prefix substr($columnStr$i1);
  1972.             if (isset($this->_prefixes[$prefix])) {
  1973.                 $columnSubStr substr($columnStr$i 1);
  1974.                 $filePath     $this->_prefixes[$prefix][0];
  1975.                 $className    $this->_prefixes[$prefix][1];
  1976.                 if ($columnSubStr === false{
  1977.                     $columnSubStr '';
  1978.                 }
  1979.                 
  1980.                 if ($filePath{
  1981.                     include_once $filePath;
  1982.                 }
  1983.                 
  1984.                 if (!in_array(
  1985.                         'XML_Query2XML_Data',
  1986.                         class_parents($className)
  1987.                     )
  1988.                 {
  1989.                     throw new XML_Query2XML_ConfigException(
  1990.                         $configPath ': Prefix class ' $className ' does ' .
  1991.                         'not extend XML_Query2XML_Data.'
  1992.                     );
  1993.                 }
  1994.                 
  1995.                 if (in_array(
  1996.                         'XML_Query2XML_Data_Source',
  1997.                         class_parents($className)
  1998.                     )
  1999.                 {
  2000.                     // data source prefix
  2001.                     $callback call_user_func_array(
  2002.                         array($className'create'),
  2003.                         array($columnSubStr$configPath)
  2004.                     );
  2005.                 else {
  2006.                     // data processing prefix
  2007.                     $callback call_user_func_array(
  2008.                         array($className'create'),
  2009.                         array(null$configPath)
  2010.                     );
  2011.                     
  2012.                     if (ltrim($columnSubStr$prefixList== $columnSubStr{
  2013.                         // no more prefixes: ColumnValue is the default data source
  2014.                         include_once 'XML/Query2XML/Data/Source/ColumnValue.php';
  2015.                         $callback->setPreProcessor(
  2016.                             new XML_Query2XML_Data_Source_ColumnValue(
  2017.                                 $columnSubStr,
  2018.                                 $configPath
  2019.                             )
  2020.                         );
  2021.                     }
  2022.                 }
  2023.                 
  2024.                 if (is_null($firstCallback)) {
  2025.                     $firstCallback $callback;
  2026.                 else {
  2027.                     if (
  2028.                         $callback instanceof XML_Query2XML_Data_Condition &&
  2029.                         !($firstCallback instanceof XML_Query2XML_Data_Condition)
  2030.                     {
  2031.                         throw new XML_Query2XML_ConfigException(
  2032.                             $configPath ': conditional prefixes always have to '
  2033.                             . 'go first.'
  2034.                         );
  2035.                     }
  2036.                     $firstCallback->getFirstPreProcessor()->setPreProcessor(
  2037.                         $callback
  2038.                     );
  2039.                 }
  2040.                 if (
  2041.                     $firstCallback->getFirstPreProcessor()
  2042.                     instanceof XML_Query2XML_Data_Source
  2043.                 {
  2044.                     // there can only be one data source
  2045.                     break;
  2046.                 }
  2047.             else {
  2048.                 break;
  2049.             }
  2050.         }
  2051.         if (is_null($firstCallback)) {
  2052.             return $columnStr;
  2053.         else {
  2054.             return $firstCallback;
  2055.         }
  2056.     }
  2057.     
  2058.     /**
  2059.      * Creates a new instance of DOMDocument.
  2060.      * '1.0' is passed as first argument and 'UTF-8' as second to the
  2061.      * DOMDocument constructor.
  2062.      *
  2063.      * @return DOMDocument The new instance.
  2064.      */
  2065.     private static function _createDOMDocument()
  2066.     {
  2067.         return new DOMDocument('1.0''UTF-8');
  2068.     }
  2069.     
  2070.     /**
  2071.      * Create and then add a new child element.
  2072.      *
  2073.      * @param DOMNode $element    The parent DOMNode the new DOM element should be
  2074.      *                             appended to.
  2075.      * @param string  $name       The tag name of the new element.
  2076.      * @param string  $configPath The config path; used for exception messages.
  2077.      * @param string  $value      The value of a child text node. This argument is
  2078.      *                             optional. The default is the boolean value false,
  2079.      *                             which means that no child text node will be
  2080.      *                             appended.
  2081.      *
  2082.      * @return DOMNode The newly created DOMNode instance that was appended
  2083.      *                  to $element.
  2084.      * @throws XML_Query2XML_XMLException This exception will bubble up if it is
  2085.      *                  thrown by _createDOMElement().
  2086.      */
  2087.     private static function _addNewDOMChild(DOMNode $element$name$configPath,
  2088.         $value false)
  2089.     {
  2090.         if ($element instanceof DOMDocument{
  2091.             $dom $element;
  2092.         else {
  2093.             $dom $element->ownerDocument;
  2094.         }
  2095.         $child self::_createDOMElement($dom$name$configPath$value);
  2096.         $element->appendChild($child);
  2097.         return $child;
  2098.     }
  2099.     
  2100.     /**
  2101.      * Helper method to create a new instance of DOMNode
  2102.      *
  2103.      * @param DOMDocument $dom        An instance of DOMDocument. It's
  2104.      *                                 createElement() method is used to create the
  2105.      *                                 new DOMNode instance.
  2106.      * @param string      $name       The tag name of the new element.
  2107.      * @param string      $configPath The config path; used for exception messages.
  2108.      * @param string      $value      The value of a child text node. This argument
  2109.      *                                 is optional. The default is the boolean value
  2110.      *                                 false, which means that no child text node will
  2111.      *                                 be appended.
  2112.      *
  2113.      * @return DOMNode An instance of DOMNode.
  2114.      * @throws XML_Query2XML_XMLException If $name is an invalid XML identifier.
  2115.      *                                     Also it will bubble up if it is thrown by
  2116.      *                                     _appendTextChildNode().
  2117.      */
  2118.     private static function _createDOMElement(DOMDocument $dom$name$configPath,
  2119.         $value false)
  2120.     {
  2121.         try {
  2122.             $element $dom->createElement($name);
  2123.         catch(DOMException $e{
  2124.             /*
  2125.             * unit tests:
  2126.             *  _createDOMElement/throwXMLException_elementInvalid1.phpt
  2127.             *  _createDOMElement/throwXMLException_elementInvalid2.phpt
  2128.             *  _createDOMElement/throwXMLException_roottagOptionInvalid1.phpt
  2129.             *  _createDOMElement/throwXMLException_roottagOptionInvalid2.phpt
  2130.             *  _createDOMElement/throwXMLException_rowtagOptionInvalid.phpt
  2131.             */
  2132.             throw new XML_Query2XML_XMLException(
  2133.                 $configPath ': "' $name '" is an invalid XML element name: '
  2134.                 . $e->getMessage(),
  2135.                 $e
  2136.             );
  2137.         }
  2138.         self::_appendTextChildNode($element$value$configPath);
  2139.         return $element;
  2140.     }
  2141.     
  2142.     /**
  2143.      * Append a new child text node to $element.
  2144.      * $value must already be UTF8-encoded; this is to be handled
  2145.      * by self::_executeEncoder() and $options['encoder'].
  2146.      *
  2147.      * This method will not create and append a child text node
  2148.      * if $value === false || is_null($value).
  2149.      *
  2150.      * @param DOMNode $element    An instance of DOMNode
  2151.      * @param string  $value      The value of the text node.
  2152.      * @param string  $configPath The config path; used for exception messages.
  2153.      *
  2154.      * @return void 
  2155.      * @throws XML_Query2XML_XMLException Any lower-level DOMException will
  2156.      *                  wrapped and re-thrown as a XML_Query2XML_XMLException. This
  2157.      *                  will happen if $value cannot be UTF8-encoded for some reason.
  2158.      *                  It will also be thrown if $value is an object or an array
  2159.      *                  (and can therefore not be converted into a string).
  2160.      */
  2161.     private static function _appendTextChildNode(DOMNode $element,
  2162.                                                  $value,
  2163.                                                  $configPath)
  2164.     {
  2165.         if ($value === false || is_null($value)) {
  2166.             return;
  2167.         elseif (is_object($value|| is_array($value)) {
  2168.             /*
  2169.             * Objects and arrays cannot be cast
  2170.             * to a string without an error.
  2171.             *
  2172.             * unit test:
  2173.             * _appendTextChildNode/throwXMLException.phpt
  2174.             */
  2175.             throw new XML_Query2XML_XMLException(
  2176.                 $configPath ': A value of the type ' gettype($value)
  2177.                 . ' cannot be used for a text node.'
  2178.             );
  2179.         }
  2180.         $dom $element->ownerDocument;
  2181.         try {
  2182.             $element->appendChild($dom->createTextNode($value));
  2183.         catch(DOMException $e{
  2184.             // this should never happen as $value is UTF-8 encoded
  2185.             throw new XML_Query2XML_XMLException(
  2186.                 $configPath ': "' $value '" is not a vaild text node: '
  2187.                 . $e->getMessage(),
  2188.                 $e
  2189.             );
  2190.         }
  2191.     }
  2192.     
  2193.     /**
  2194.      * Set the attribute $name with a value of $value for $element.
  2195.      * $value must already be UTF8-encoded; this is to be handled
  2196.      * by self::_executeEncoder() and $options['encoder'].
  2197.      *
  2198.      * @param DOMNode $element    An instance of DOMNode
  2199.      * @param string  $name       The name of the attribute to set.
  2200.      * @param string  $value      The value of the attribute to set.
  2201.      * @param string  $configPath The config path; used for exception messages.
  2202.      *
  2203.      * @return void 
  2204.      * @throws XML_Query2XML_XMLException Any lower-level DOMException will be
  2205.      *                  wrapped and re-thrown as a XML_Query2XML_XMLException. This
  2206.      *                  will happen if $name is not a valid attribute name. It will
  2207.      *                  also be thrown if $value is an object or an array (and can
  2208.      *                  therefore not be converted into a string).
  2209.      */
  2210.     private static function _setDOMAttribute(DOMNode $element,
  2211.                                              $name,
  2212.                                              $value,
  2213.                                              $configPath)
  2214.     {
  2215.         if (is_object($value|| is_array($value)) {
  2216.             /*
  2217.             * Objects and arrays cannot be cast
  2218.             * to a string without an error.
  2219.             *
  2220.             * unit test:
  2221.             * _setDOMAttribute/throwXMLException.phpt
  2222.             */
  2223.             throw new XML_Query2XML_XMLException(
  2224.                 $configPath ': A value of the type ' gettype($value)
  2225.                 . ' cannot be used for an attribute value.'
  2226.             );
  2227.         }
  2228.         
  2229.         try {
  2230.             $element->setAttribute($name$value);
  2231.         catch(DOMException $e{
  2232.             // no unit test available for this one
  2233.             throw new XML_Query2XML_XMLException(
  2234.                 $configPath ': "' $name '" is an invalid XML attribute name: '
  2235.                 . $e->getMessage(),
  2236.                 $e
  2237.             );
  2238.         }
  2239.     }
  2240.     
  2241.     /**
  2242.      * Adds one or more child nodes to an existing DOMNode instance.
  2243.      *
  2244.      * @param DOMNode $base       An instance of DOMNode.
  2245.      * @param mixed   $children   An array of DOMNode instances or
  2246.      *                             just a single DOMNode instance.
  2247.      *                             Boolean values of false are always ignored.
  2248.      * @param string  $configPath The config path; used for exception messages.
  2249.      * @param boolean $import     Whether DOMDocument::importNode() should be called
  2250.      *                             for $children. This is necessary if the instance(s)
  2251.      *                             passed as $children was/were created using a
  2252.      *                             different DOMDocument instance. This argument is
  2253.      *                             optional. The default is false.
  2254.      *
  2255.      * @return void 
  2256.      * @throws XML_Query2XML_XMLException If one of the specified children
  2257.      *                          is not one of the following: an instance of DOMNode,
  2258.      *                          the boolean value false, or an array containing
  2259.      *                          these two.
  2260.      */
  2261.     private static function _addDOMChildren(DOMNode $base,
  2262.                                             $children,
  2263.                                             $configPath,
  2264.                                             $import false)
  2265.     {
  2266.         if ($children === false{
  2267.             // don't do anything
  2268.             return;
  2269.         elseif ($children instanceof DOMNode{
  2270.             // $children is a single complex child
  2271.             if ($import{
  2272.                 $children $base->ownerDocument->importNode($childrentrue);
  2273.             }
  2274.             $base->appendChild($children);
  2275.         elseif (is_array($children)) {
  2276.             for ($i 0$i count($children)$i++{
  2277.                 if ($children[$i=== false{
  2278.                     // don't do anything
  2279.                 elseif ($children[$iinstanceof DOMNode{
  2280.                     if ($import{
  2281.                         $children[$i$base->ownerDocument->importNode(
  2282.                             $children[$i],
  2283.                             true
  2284.                         );
  2285.                     }
  2286.                     $base->appendChild($children[$i]);
  2287.                 else {
  2288.                     /*
  2289.                     * unit tests:
  2290.                     * _addDOMChildren/throwXMLException_arrayWithObject.phpt
  2291.                     * _addDOMChildren/throwXMLException_arrayWithString.phpt
  2292.                     * _addDOMChildren/throwXMLException_arrayWithInt.phpt
  2293.                     * _addDOMChildren/throwXMLException_arrayWithBool.phpt
  2294.                     * _addDOMChildren/throwXMLException_arrayWithDouble.phpt
  2295.                     */
  2296.                     throw new XML_Query2XML_XMLException(
  2297.                         $configPath ': DOMNode, false or an array of the two '
  2298.                         . 'expected, but ' gettype($children[$i]' given '
  2299.                         . '(hint: check your callback).'
  2300.                     );
  2301.                 }
  2302.             }
  2303.         else {
  2304.             /*
  2305.              * This should never happen because _addDOMChildren() is only called
  2306.              * for arrays and instances of DOMNode.
  2307.              */
  2308.             throw new XML_Query2XML_XMLException(
  2309.                 $configPath ': DOMNode, false or an array of the two '
  2310.                 . 'expected, but ' gettype($children' given '
  2311.                 . '(hint: check your callback).'
  2312.             );
  2313.         }
  2314.     }
  2315.     
  2316.     /**
  2317.      * Remove all container elements created by XML_Query2XML to ensure that all
  2318.      * elements are correctly ordered.
  2319.      *
  2320.      * This is a recursive method. This method calls
  2321.      * {@link XML_Query2XML::_replaceParentWithChildren()}. For the concept of
  2322.      * container elements please see the {@tutorial XML_Query2XML.pkg tutorial}.
  2323.      *
  2324.      * @param DOMNode $element               An instance of DOMNode.
  2325.      * @param string  $hiddenContainerPrefix The containers that will be removed
  2326.      *                                        all start with this string.
  2327.      *
  2328.      * @return void 
  2329.      */
  2330.     private static function _removeContainers($element$hiddenContainerPrefix)
  2331.     {
  2332.         $xpath      new DOMXPath($element);
  2333.         $containers $xpath->query(
  2334.             '//*[starts-with(name(),\'' $hiddenContainerPrefix '\')]'
  2335.         );
  2336.         foreach ($containers as $container{
  2337.             if (!is_null($container->parentNode)) {
  2338.                 self::_replaceParentWithChildren($container);
  2339.             }
  2340.         }
  2341.     }
  2342.     
  2343.     /**
  2344.      * Replace a certain node with its child nodes.
  2345.      *
  2346.      * @param DOMNode $parent An instance of DOMNode.
  2347.      *
  2348.      * @return void 
  2349.      */
  2350.     private static function _replaceParentWithChildren(DOMNode $parent)
  2351.     {
  2352.         
  2353.         $child $parent->firstChild;
  2354.         while ($child{
  2355.             $nextChild $child->nextSibling;
  2356.             $parent->removeChild($child);
  2357.             $parent->parentNode->insertBefore($child$parent);
  2358.             $child $nextChild;
  2359.         }
  2360.         $parent->parentNode->removeChild($parent);
  2361.     }
  2362.     
  2363.     /**
  2364.      * Calls an encoder for XML node and attribute values
  2365.      * $options['encoder'] can be one of the following:
  2366.      * - null: self::_utf8encode() will be used
  2367.      * - false: no encoding will be performed
  2368.      * - callback: a string or an array as defined by the
  2369.      *   callback pseudo-type; please see
  2370.      *   http://www.php.net/manual/en/
  2371.      *   language.pseudo-types.php#language.types.callback
  2372.      *
  2373.      * @param string $str     The string to encode
  2374.      * @param array  $options An associative array with $options['encoder'] set.
  2375.      *
  2376.      * @return void 
  2377.      * @throws XML_Query2XML_XMLException If the $options['encoder'] is a callback
  2378.      *                                     function that threw an exception.
  2379.      */
  2380.     private static function _executeEncoder($str$options)
  2381.     {
  2382.         if (!is_string($str|| $options['encoder'=== false{
  2383.             return $str;
  2384.         }
  2385.         
  2386.         if ($options['encoder'=== null{
  2387.             return self::_utf8encode($str);
  2388.         }
  2389.         
  2390.         try {
  2391.             return call_user_func($options['encoder']$str);
  2392.         catch (Exception $e{
  2393.             /*
  2394.             * unit test:
  2395.             *  _executeEncoder/throwXMLException.phpt
  2396.             */
  2397.             throw new XML_Query2XML_XMLException(
  2398.                 $options['--q2x--path''[encoder]: Could not encode '
  2399.                 . '"' $str '": ' $e->getMessage()
  2400.             );
  2401.         }
  2402.     }
  2403.     
  2404.     /**
  2405.      * UTF-8 encode $str using mb_conver_encoding or if that is not
  2406.      * present, utf8_encode.
  2407.      *
  2408.      * @param string $str The string to encode
  2409.      *
  2410.      * @return String The UTF-8 encoded version of $str
  2411.      */
  2412.     private static function _utf8encode($str)
  2413.     {
  2414.         if (function_exists('mb_convert_encoding')) {
  2415.             $str mb_convert_encoding($str'UTF-8');
  2416.         else {
  2417.             $str utf8_encode($str);
  2418.         }
  2419.         return $str;
  2420.     }
  2421. }
  2422.  
  2423. /**
  2424.  * Parent class for ALL exceptions thrown by this package.
  2425.  * By catching XML_Query2XML_Exception you will catch all exceptions
  2426.  * thrown by XML_Query2XML.
  2427.  *
  2428.  * @category XML
  2429.  * @package  XML_Query2XML
  2430.  * @author   Lukas Feiler <lukas.feiler@lukasfeiler.com>
  2431.  * @license  http://www.gnu.org/copyleft/lesser.html  LGPL Version 2.1
  2432.  * @link     http://pear.php.net/package/XML_Query2XML
  2433.  */
  2434. class XML_Query2XML_Exception extends PEAR_Exception
  2435. {
  2436.     
  2437.     /**
  2438.      * Constructor method
  2439.      *
  2440.      * @param string    $message   The error message.
  2441.      * @param Exception $exception The Exception that caused this exception
  2442.      *                              to be thrown. This argument is optional.
  2443.      */
  2444.     public function __construct($message$exception null)
  2445.     {
  2446.         parent::__construct($message$exception);
  2447.     }
  2448. }
  2449.  
  2450. /**
  2451.  * Exception for driver errors
  2452.  *
  2453.  * @category XML
  2454.  * @package  XML_Query2XML
  2455.  * @author   Lukas Feiler <lukas.feiler@lukasfeiler.com>
  2456.  * @license  http://www.gnu.org/copyleft/lesser.html  LGPL Version 2.1
  2457.  * @link     http://pear.php.net/package/XML_Query2XML
  2458.  * @since    Release 1.6.0RC1
  2459.  */
  2460. {
  2461.     /**
  2462.      * Constructor
  2463.      *
  2464.      * @param string $message The error message.
  2465.      */
  2466.     public function __construct($message)
  2467.     {
  2468.         parent::__construct($message);
  2469.     }
  2470. }
  2471.  
  2472. /**
  2473.  * Exception for database errors
  2474.  *
  2475.  * @category XML
  2476.  * @package  XML_Query2XML
  2477.  * @author   Lukas Feiler <lukas.feiler@lukasfeiler.com>
  2478.  * @license  http://www.gnu.org/copyleft/lesser.html  LGPL Version 2.1
  2479.  * @link     http://pear.php.net/package/XML_Query2XML
  2480.  */
  2481. {
  2482.     /**
  2483.      * Constructor
  2484.      *
  2485.      * @param string $message The error message.
  2486.      */
  2487.     public function __construct($message)
  2488.     {
  2489.         parent::__construct($message);
  2490.     }
  2491. }
  2492.  
  2493. /**
  2494.  * Exception for XML errors
  2495.  * In most cases this exception will be thrown if a DOMException occurs.
  2496.  *
  2497.  * @category XML
  2498.  * @package  XML_Query2XML
  2499.  * @author   Lukas Feiler <lukas.feiler@lukasfeiler.com>
  2500.  * @license  http://www.gnu.org/copyleft/lesser.html  LGPL Version 2.1
  2501.  * @link     http://pear.php.net/package/XML_Query2XML
  2502.  */
  2503. {
  2504.     /**
  2505.      * Constructor
  2506.      *
  2507.      * @param string       $message   The error message.
  2508.      * @param DOMException $exception The DOMException that caused this exception
  2509.      *                                 to be thrown. This argument is optional.
  2510.      */
  2511.     public function __construct($messageDOMException $exception null)
  2512.     {
  2513.         parent::__construct($message$exception);
  2514.     }
  2515. }
  2516.  
  2517. /**
  2518.  * Exception that handles configuration errors.
  2519.  *
  2520.  * This exception handels errors in the $options array passed to
  2521.  * XML_Query2XML::getXML() and wrong arguments passed to the constructor via
  2522.  * XML_Query2XML::factory().
  2523.  *
  2524.  * @category XML
  2525.  * @package  XML_Query2XML
  2526.  * @author   Lukas Feiler <lukas.feiler@lukasfeiler.com>
  2527.  * @license  http://www.gnu.org/copyleft/lesser.html  LGPL Version 2.1
  2528.  * @link     http://pear.php.net/package/XML_Query2XML
  2529.  * @see      XML_Query2XML::getXML()
  2530.  */
  2531. {
  2532.     /**
  2533.      * Constructor method
  2534.      *
  2535.      * @param string $message A detailed error message.
  2536.      */
  2537.     public function __construct($message)
  2538.     {
  2539.         parent::__construct($message);
  2540.     }
  2541. }
  2542.  
  2543. /**
  2544.  * Abstract driver class.
  2545.  *
  2546.  * usage:
  2547.  * <code>
  2548.  * $driver = XML_Query2XML_Driver::factory($backend);
  2549.  * </code>
  2550.  *
  2551.  * @category XML
  2552.  * @package  XML_Query2XML
  2553.  * @author   Lukas Feiler <lukas.feiler@lukasfeiler.com>
  2554.  * @license  http://www.gnu.org/copyleft/lesser.html  LGPL Version 2.1
  2555.  * @version  Release: 1.7.2
  2556.  * @link     http://pear.php.net/package/XML_Query2XML
  2557.  * @since    Release 1.5.0RC1
  2558.  */
  2559. abstract class XML_Query2XML_Driver
  2560. {
  2561.     /**
  2562.      * This method, when implemented executes the query passed as the
  2563.      * first argument and returns all records from the result set.
  2564.      *
  2565.      * The format of the first argument depends on the driver being used.
  2566.      *
  2567.      * @param mixed  $sql        The SQL query as a string or an array.
  2568.      * @param string $configPath The config path; used for exception messages.
  2569.      *
  2570.      * @return array An array of records. Each record itself will be an
  2571.      *                associative array.
  2572.      * @throws XML_Query2XML_DriverException If some driver related error occures.
  2573.      */
  2574.     abstract public function getAllRecords($sql$configPath);
  2575.     
  2576.     /**
  2577.      * Pre-processes a query specification and returns a string representation
  2578.      * of the query.
  2579.      *
  2580.      * The returned string will be used for logging purposes. It
  2581.      * does not need to be valid SQL.
  2582.      *
  2583.      * If $query is a string, it will be changed to array('query' => $query).
  2584.      *
  2585.      * @param mixed  &$query     A string or an array containing the element 'query'.
  2586.      * @param string $configPath The config path; used for exception messages.
  2587.      *
  2588.      * @return string The query statement as a string.
  2589.      * @throws XML_Query2XML_ConfigException If $query is an array but does not
  2590.      *                                        contain the element 'query'.
  2591.      */
  2592.     public function preprocessQuery(&$query$configPath)
  2593.     {
  2594.         if (is_string($query)) {
  2595.             $query array('query' => $query);
  2596.         elseif (is_array($query)) {
  2597.             if (!isset($query['query'])) {
  2598.                 /*
  2599.                 * unit test: _preprocessOptions/
  2600.                 *  throwConfigException_queryOptionMissing.phpt
  2601.                 */
  2602.                 throw new XML_Query2XML_ConfigException(
  2603.                     $configPath ': The configuration option'
  2604.                     . ' "query" is missing.'
  2605.                 );
  2606.             }
  2607.         else //neither a string nor an array
  2608.             /*
  2609.             * unit test: _preprocessOptions/
  2610.             *  throwConfigException_sqlOptionWrongType.phpt
  2611.             */
  2612.             throw new XML_Query2XML_ConfigException(
  2613.                 $configPath ': array or string expected, '
  2614.                 . gettype($query' given.'
  2615.             );
  2616.         }
  2617.         return $query['query'];
  2618.     }
  2619.     
  2620.     /**
  2621.      * Factory method.
  2622.      *
  2623.      * @param mixed $backend An instance of MDB2_Driver_Common, PDO, DB_common,
  2624.      *                   ADOConnection, Net_LDAP2 or Net_LDAP.
  2625.      *
  2626.      * @return XML_Query2XML_Driver An instance of a driver class that
  2627.      *                   extends XML_Query2XML_Driver.
  2628.      * @throws XML_Query2XML_DriverException If $backend already is a PEAR_Error.
  2629.      * @throws XML_Query2XML_ConfigException If $backend is not an instance of a
  2630.      *                   child class of MDB2_Driver_Common, PDO, DB_common,
  2631.      *                   ADOConnection, Net_LDAP2 or Net_LDAP.
  2632.      */
  2633.     public static function factory($backend)
  2634.     {
  2635.         if (
  2636.             class_exists('MDB2_Driver_Common'&&
  2637.             $backend instanceof MDB2_Driver_Common
  2638.         {
  2639.             include_once 'XML/Query2XML/Driver/MDB2.php';
  2640.             return new XML_Query2XML_Driver_MDB2($backend);
  2641.         elseif (class_exists('PDO'&& $backend instanceof PDO{
  2642.             include_once 'XML/Query2XML/Driver/PDO.php';
  2643.             return new XML_Query2XML_Driver_PDO($backend);
  2644.         elseif (class_exists('DB_common'&& $backend instanceof DB_common{
  2645.             include_once 'XML/Query2XML/Driver/DB.php';
  2646.             return new XML_Query2XML_Driver_DB($backend);
  2647.         elseif (
  2648.             class_exists('ADOConnection'&&
  2649.             $backend instanceof ADOConnection
  2650.         {
  2651.             include_once 'XML/Query2XML/Driver/ADOdb.php';
  2652.             return new XML_Query2XML_Driver_ADOdb($backend);
  2653.         elseif (class_exists('Net_LDAP'&& $backend instanceof Net_LDAP{
  2654.             include_once 'XML/Query2XML/Driver/LDAP.php';
  2655.             return new XML_Query2XML_Driver_LDAP($backend);
  2656.         elseif (class_exists('Net_LDAP2'&& $backend instanceof Net_LDAP2{
  2657.             include_once 'XML/Query2XML/Driver/LDAP2.php';
  2658.             return new XML_Query2XML_Driver_LDAP2($backend);
  2659.         elseif (class_exists('PEAR_Error'&& $backend instanceof PEAR_Error{
  2660.             //unit tests: NoDBLayer/factory/throwDBException.phpt
  2661.             throw new XML_Query2XML_DriverException(
  2662.                 'Driver error: ' $backend->toString()
  2663.             );
  2664.         else {
  2665.             //unit test: NoDBLayer/factory/throwConfigException.phpt
  2666.             throw new XML_Query2XML_ConfigException(
  2667.                 'Argument passed to the XML_Query2XML constructor is not an '
  2668.                 . 'instance of DB_common, MDB2_Driver_Common, ADOConnection'
  2669.                 . ', PDO, Net_LDAP or Net_LDAP2.'
  2670.             );
  2671.         }
  2672.     }
  2673. }
  2674. ?>

Documentation generated on Sun, 03 Apr 2011 13:13:11 +0200 by phpDocumentor 1.4.1