home *** CD-ROM | disk | FTP | other *** search
/ Enter 2004 June / ENTER.ISO / files / xampp-win32-1.4.5-installer.exe / xampp / adodb-xmlschema.inc.php < prev    next >
Encoding:
PHP Script  |  2004-02-20  |  50.1 KB  |  1,951 lines

  1. <?php
  2. // Copyright (c) 2004 ars Cognita Inc., all rights reserved
  3. /* ******************************************************************************
  4.     Released under both BSD license and Lesser GPL library license. 
  5.      Whenever there is any discrepancy between the two licenses, 
  6.      the BSD license will take precedence. 
  7.     
  8. Redistribution and use in source and binary forms, with or without modification,
  9. are permitted provided that the following conditions are met:
  10.  
  11. Redistributions of source code must retain the above copyright notice, this list
  12.  of conditions and the following disclaimer.
  13. Redistributions in binary form must reproduce the above copyright notice, this l
  14. ist of conditions and the following disclaimer in the documentation and/or other
  15.  materials provided with the distribution.
  16. Neither the name of the ars Cognita, Inc.,  nor the names of its contributors may be used 
  17. to endorse or promote products derived from this software without specific prior
  18. written permission.
  19.  
  20. DISCLAIMER:
  21. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  22. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WA
  23. RRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  24.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIREC
  25. T, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26. LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR P
  27. ROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  28.  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWI
  29. SE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE P
  30. OSSIBILITY OF SUCH DAMAGE.
  31.  
  32. *******************************************************************************/
  33. /**
  34.  * xmlschema is a class that allows the user to quickly and easily
  35.  * build a database on any ADOdb-supported platform using a simple
  36.  * XML schema.
  37.  *
  38.  * Last Editor: $Author: jlim $
  39.  * @author Richard Tango-Lowy & Dan Cech
  40.  * @version $Revision: 1.8 $
  41.  *
  42.  * @package axmls
  43.  * @tutorial getting_started.pkg
  44.  */
  45.  
  46. /**
  47. * Debug on or off
  48. */
  49. if( !defined( 'XMLS_DEBUG' ) ) {
  50.     define( 'XMLS_DEBUG', FALSE );
  51. }
  52.  
  53. /**
  54. * Default prefix key
  55. */
  56. if( !defined( 'XMLS_PREFIX' ) ) {
  57.     define( 'XMLS_PREFIX', '%%P' );
  58. }
  59.  
  60. /**
  61. * Maximum length allowed for object prefix
  62. */
  63. if( !defined( 'XMLS_PREFIX_MAXLEN' ) ) {
  64.     define( 'XMLS_PREFIX_MAXLEN', 10 );
  65. }
  66.  
  67. /**
  68. * Execute SQL inline as it is generated
  69. */
  70. if( !defined( 'XMLS_EXECUTE_INLINE' ) ) {
  71.     define( 'XMLS_EXECUTE_INLINE', FALSE );
  72. }
  73.  
  74. /**
  75. * Continue SQL Execution if an error occurs?
  76. */
  77. if( !defined( 'XMLS_CONTINUE_ON_ERROR' ) ) {
  78.     define( 'XMLS_CONTINUE_ON_ERROR', FALSE );
  79. }
  80.  
  81. /**
  82. * Current Schema Version
  83. */
  84. if( !defined( 'XMLS_SCHEMA_VERSION' ) ) {
  85.     define( 'XMLS_SCHEMA_VERSION', '0.2' );
  86. }
  87.  
  88. /**
  89. * Default Schema Version.  Used for Schemas without an explicit version set.
  90. */
  91. if( !defined( 'XMLS_DEFAULT_SCHEMA_VERSION' ) ) {
  92.     define( 'XMLS_DEFAULT_SCHEMA_VERSION', '0.1' );
  93. }
  94.  
  95. /**
  96. * Default Schema Version.  Used for Schemas without an explicit version set.
  97. */
  98. if( !defined( 'XMLS_DEFAULT_UPGRADE_METHOD' ) ) {
  99.     define( 'XMLS_DEFAULT_UPGRADE_METHOD', 'ALTER' );
  100. }
  101.  
  102. /**
  103. * Include the main ADODB library
  104. */
  105. if( !defined( '_ADODB_LAYER' ) ) {
  106.     require( 'adodb.inc.php' );
  107. }
  108.  
  109. /**
  110. * Abstract DB Object. This class provides basic methods for database objects, such
  111. * as tables and indexes.
  112. *
  113. * @package axmls
  114. * @access private
  115. */
  116. class dbObject {
  117.     
  118.     /**
  119.     * var object Parent
  120.     */
  121.     var $parent;
  122.     
  123.     /**
  124.     * var string current element
  125.     */
  126.     var $currentElement;
  127.     
  128.     /**
  129.     * NOP
  130.     */
  131.     function dbObject( &$parent, $attributes = NULL ) {
  132.         $this->parent =& $parent;
  133.     }
  134.     
  135.     /**
  136.     * XML Callback to process start elements
  137.     *
  138.     * @access private
  139.     */
  140.     function _tag_open( &$parser, $tag, $attributes ) {
  141.         
  142.     }
  143.     
  144.     /**
  145.     * XML Callback to process CDATA elements
  146.     *
  147.     * @access private
  148.     */
  149.     function _tag_cdata( &$parser, $cdata ) {
  150.         
  151.     }
  152.     
  153.     /**
  154.     * XML Callback to process end elements
  155.     *
  156.     * @access private
  157.     */
  158.     function _tag_close( &$parser, $tag ) {
  159.         
  160.     }
  161.     
  162.     function create() {
  163.         return array();
  164.     }
  165.     
  166.     /**
  167.     * Destroys the object
  168.     */
  169.     function destroy() {
  170.         unset( $this );
  171.     }
  172.     
  173.     /**
  174.     * Checks whether the specified RDBMS is supported by the current
  175.     * database object or its ranking ancestor.
  176.     *
  177.     * @param string $platform RDBMS platform name (from ADODB platform list).
  178.     * @return boolean TRUE if RDBMS is supported; otherwise returns FALSE.
  179.     */
  180.     function supportedPlatform( $platform = NULL ) {
  181.         return is_object( $this->parent ) ? $this->parent->supportedPlatform( $platform ) : TRUE;
  182.     }
  183.     
  184.     /**
  185.     * Returns the prefix set by the ranking ancestor of the database object.
  186.     *
  187.     * @param string $name Prefix string.
  188.     * @return string Prefix.
  189.     */
  190.     function prefix( $name = '' ) {
  191.         return is_object( $this->parent ) ? $this->parent->prefix( $name ) : $name;
  192.     }
  193.     
  194.     /**
  195.     * Extracts a field ID from the specified field.
  196.     *
  197.     * @param string $field Field.
  198.     * @return string Field ID.
  199.     */
  200.     function FieldID( $field ) {
  201.         return strtoupper( preg_replace( '/^`(.+)`$/', '$1', $field ) );
  202.     }
  203. }
  204.  
  205. /**
  206. * Creates a table object in ADOdb's datadict format
  207. *
  208. * This class stores information about a database table. As charactaristics
  209. * of the table are loaded from the external source, methods and properties
  210. * of this class are used to build up the table description in ADOdb's
  211. * datadict format.
  212. *
  213. * @package axmls
  214. * @access private
  215. */
  216. class dbTable extends dbObject {
  217.     
  218.     /**
  219.     * @var string Table name
  220.     */
  221.     var $name;
  222.     
  223.     /**
  224.     * @var array Field specifier: Meta-information about each field
  225.     */
  226.     var $fields = array();
  227.     
  228.     /**
  229.     * @var array List of table indexes.
  230.     */
  231.     var $indexes = array();
  232.     
  233.     /**
  234.     * @var array Table options: Table-level options
  235.     */
  236.     var $opts = array();
  237.     
  238.     /**
  239.     * @var string Field index: Keeps track of which field is currently being processed
  240.     */
  241.     var $current_field;
  242.     
  243.     /**
  244.     * @var boolean Mark table for destruction
  245.     * @access private
  246.     */
  247.     var $drop_table;
  248.     
  249.     /**
  250.     * @var boolean Mark field for destruction (not yet implemented)
  251.     * @access private
  252.     */
  253.     var $drop_field = array();
  254.     
  255.     /**
  256.     * Iniitializes a new table object.
  257.     *
  258.     * @param string $prefix DB Object prefix
  259.     * @param array $attributes Array of table attributes.
  260.     */
  261.     function dbTable( &$parent, $attributes = NULL ) {
  262.         $this->parent =& $parent;
  263.         $this->name = $this->prefix($attributes['NAME']);
  264.     }
  265.     
  266.     /**
  267.     * XML Callback to process start elements. Elements currently 
  268.     * processed are: INDEX, DROP, FIELD, KEY, NOTNULL, AUTOINCREMENT & DEFAULT. 
  269.     *
  270.     * @access private
  271.     */
  272.     function _tag_open( &$parser, $tag, $attributes ) {
  273.         $this->currentElement = strtoupper( $tag );
  274.         
  275.         switch( $this->currentElement ) {
  276.             case 'INDEX':
  277.                 xml_set_object( $parser, $this->addIndex( $attributes ) );
  278.                 break;
  279.             case 'DROP':
  280.                 $this->drop();
  281.                 break;
  282.             case 'FIELD':
  283.                 // Add a field
  284.                 $fieldName = $attributes['NAME'];
  285.                 $fieldType = $attributes['TYPE'];
  286.                 $fieldSize = isset( $attributes['SIZE'] ) ? $attributes['SIZE'] : NULL;
  287.                 $fieldOpts = isset( $attributes['OPTS'] ) ? $attributes['OPTS'] : NULL;
  288.                 
  289.                 $this->addField( $fieldName, $fieldType, $fieldSize, $fieldOpts );
  290.                 break;
  291.             case 'KEY':    
  292.             case 'NOTNULL':
  293.             case 'AUTOINCREMENT':
  294.                 // Add a field option
  295.                 $this->addFieldOpt( $this->current_field, $this->currentElement );
  296.                 break;
  297.             case 'DEFAULT':
  298.                 // Add a field option to the table object
  299.                 
  300.                 // Work around ADOdb datadict issue that misinterprets empty strings.
  301.                 if( $attributes['VALUE'] == '' ) {
  302.                     $attributes['VALUE'] = " '' ";
  303.                 }
  304.                 
  305.                 $this->addFieldOpt( $this->current_field, $this->currentElement, $attributes['VALUE'] );
  306.                 break;
  307.             default:
  308.                 // print_r( array( $tag, $attributes ) );
  309.         }
  310.     }
  311.     
  312.     /**
  313.     * XML Callback to process CDATA elements
  314.     *
  315.     * @access private
  316.     */
  317.     function _tag_cdata( &$parser, $cdata ) {
  318.         switch( $this->currentElement ) {
  319.             // Table constraint
  320.             case 'CONSTRAINT':
  321.             // Table option
  322.             case 'OPT':
  323.                 $this->addTableOpt( $cdata );
  324.                 break;
  325.             default:
  326.                 
  327.         }
  328.     }
  329.     
  330.     /**
  331.     * XML Callback to process end elements
  332.     *
  333.     * @access private
  334.     */
  335.     function _tag_close( &$parser, $tag ) {
  336.         $this->currentElement = '';
  337.         
  338.         switch( strtoupper( $tag ) ) {
  339.             case 'TABLE':
  340.                 $this->parent->addSQL( $this->create( $this->parent ) );
  341.                 xml_set_object( $parser, $this->parent );
  342.                 $this->destroy();
  343.                 break;
  344.         }
  345.     }
  346.     
  347.     /**
  348.     * Adds an index to a table object
  349.     *
  350.     * @param array $attributes Index attributes
  351.     * @return object dbIndex object
  352.     */
  353.     function &addIndex( $attributes ) {
  354.         $name = strtoupper( $attributes['NAME'] );
  355.         $this->indexes[$name] =& new dbIndex( $this, $attributes );
  356.         return $this->indexes[$name];
  357.     }
  358.     
  359.     /**
  360.     * Adds a field to a table object
  361.     *
  362.     * $name is the name of the table to which the field should be added. 
  363.     * $type is an ADODB datadict field type. The following field types
  364.     * are supported as of ADODB 3.40:
  365.     *     - C:  varchar
  366.     *    - X:  CLOB (character large object) or largest varchar size
  367.     *       if CLOB is not supported
  368.     *    - C2: Multibyte varchar
  369.     *    - X2: Multibyte CLOB
  370.     *    - B:  BLOB (binary large object)
  371.     *    - D:  Date (some databases do not support this, and we return a datetime type)
  372.     *    - T:  Datetime or Timestamp
  373.     *    - L:  Integer field suitable for storing booleans (0 or 1)
  374.     *    - I:  Integer (mapped to I4)
  375.     *    - I1: 1-byte integer
  376.     *    - I2: 2-byte integer
  377.     *    - I4: 4-byte integer
  378.     *    - I8: 8-byte integer
  379.     *    - F:  Floating point number
  380.     *    - N:  Numeric or decimal number
  381.     *
  382.     * @param string $name Name of the table to which the field will be added.
  383.     * @param string $type    ADODB datadict field type.
  384.     * @param string $size    Field size
  385.     * @param array $opts    Field options array
  386.     * @return array Field specifier array
  387.     */
  388.     function addField( $name, $type, $size = NULL, $opts = NULL ) {
  389.         $field_id = $this->FieldID( $name );
  390.         
  391.         // Set the field index so we know where we are
  392.         $this->current_field = $field_id;
  393.         
  394.         // Set the field name (required)
  395.         $this->fields[$field_id]['NAME'] = $name;
  396.         
  397.         // Set the field type (required)
  398.         $this->fields[$field_id]['TYPE'] = $type;
  399.         
  400.         // Set the field size (optional)
  401.         if( isset( $size ) ) {
  402.             $this->fields[$field_id]['SIZE'] = $size;
  403.         }
  404.         
  405.         // Set the field options
  406.         if( isset( $opts ) ) {
  407.             $this->fields[$field_id]['OPTS'] = $opts;
  408.         }
  409.     }
  410.     
  411.     /**
  412.     * Adds a field option to the current field specifier
  413.     *
  414.     * This method adds a field option allowed by the ADOdb datadict 
  415.     * and appends it to the given field.
  416.     *
  417.     * @param string $field    Field name
  418.     * @param string $opt ADOdb field option
  419.     * @param mixed $value Field option value
  420.     * @return array Field specifier array
  421.     */
  422.     function addFieldOpt( $field, $opt, $value = NULL ) {
  423.         if( !isset( $value ) ) {
  424.             $this->fields[$this->FieldID( $field )]['OPTS'][] = $opt;
  425.         // Add the option and value
  426.         } else {
  427.             $this->fields[$this->FieldID( $field )]['OPTS'][] = array( $opt => $value );
  428.         }
  429.     }
  430.     
  431.     /**
  432.     * Adds an option to the table
  433.     *
  434.     * This method takes a comma-separated list of table-level options
  435.     * and appends them to the table object.
  436.     *
  437.     * @param string $opt Table option
  438.     * @return array Options
  439.     */
  440.     function addTableOpt( $opt ) {
  441.         $this->opts[] = $opt;
  442.         
  443.         return $this->opts;
  444.     }
  445.     
  446.     /**
  447.     * Generates the SQL that will create the table in the database
  448.     *
  449.     * @param object $xmls adoSchema object
  450.     * @return array Array containing table creation SQL
  451.     */
  452.     function create( &$xmls ) {
  453.         $sql = array();
  454.         
  455.         // drop any existing indexes
  456.         if( is_array( $legacy_indexes = $xmls->dict->MetaIndexes( $this->name ) ) ) {
  457.             foreach( $legacy_indexes as $index => $index_details ) {
  458.                 $sql[] = $xmls->dict->DropIndexSQL( $index, $this->name );
  459.             }
  460.         }
  461.         
  462.         // remove fields to be dropped from table object
  463.         foreach( $this->drop_field as $field ) {
  464.             unset( $this->fields[$field] );
  465.         }
  466.         
  467.         // if table exists
  468.         if( is_array( $legacy_fields = $xmls->dict->MetaColumns( $this->name ) ) ) {
  469.             // drop table
  470.             if( $this->drop_table ) {
  471.                 $sql[] = $xmls->dict->DropTableSQL( $this->name );
  472.                 
  473.                 return $sql;
  474.             }
  475.             
  476.             // drop any existing fields not in schema
  477.             foreach( $legacy_fields as $field_id => $field ) {
  478.                 if( !isset( $this->fields[$field_id] ) ) {
  479.                     $sql[] = $xmls->dict->DropColumnSQL( $this->name, '`'.$field->name.'`' );
  480.                 }
  481.             }
  482.         // if table doesn't exist
  483.         } else {
  484.             if( $this->drop_table ) {
  485.                 return $sql;
  486.             }
  487.             
  488.             $legacy_fields = array();
  489.         }
  490.         
  491.         // Loop through the field specifier array, building the associative array for the field options
  492.         $fldarray = array();
  493.         
  494.         foreach( $this->fields as $field_id => $finfo ) {
  495.             // Set an empty size if it isn't supplied
  496.             if( !isset( $finfo['SIZE'] ) ) {
  497.                 $finfo['SIZE'] = '';
  498.             }
  499.             
  500.             // Initialize the field array with the type and size
  501.             $fldarray[$field_id] = array(
  502.                 'NAME' => $finfo['NAME'],
  503.                 'TYPE' => $finfo['TYPE'],
  504.                 'SIZE' => $finfo['SIZE']
  505.             );
  506.             
  507.             // Loop through the options array and add the field options. 
  508.             if( isset( $finfo['OPTS'] ) ) {
  509.                 foreach( $finfo['OPTS'] as $opt ) {
  510.                     // Option has an argument.
  511.                     if( is_array( $opt ) ) {
  512.                         $key = key( $opt );
  513.                         $value = $opt[key( $opt )];
  514.                         $fldarray[$field_id][$key] = $value;
  515.                     // Option doesn't have arguments
  516.                     } else {
  517.                         $fldarray[$field_id][$opt] = $opt;
  518.                     }
  519.                 }
  520.             }
  521.         }
  522.         
  523.         if( empty( $legacy_fields ) ) {
  524.             // Create the new table
  525.             $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
  526.             logMsg( end( $sql ), 'Generated CreateTableSQL' );
  527.         } else {
  528.             // Upgrade an existing table
  529.             logMsg( "Upgrading {$this->name} using '{$xmls->upgrade}'" );
  530.             switch( $xmls->upgrade ) {
  531.                 // Use ChangeTableSQL
  532.                 case 'ALTER':
  533.                     logMsg( 'Generated ChangeTableSQL (ALTERing table)' );
  534.                     $sql[] = $xmls->dict->ChangeTableSQL( $this->name, $fldarray, $this->opts );
  535.                     break;
  536.                 case 'REPLACE':
  537.                     logMsg( 'Doing upgrade REPLACE (testing)' );
  538.                     $sql[] = $xmls->dict->DropTableSQL( $this->name );
  539.                     $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
  540.                     break;
  541.                 // ignore table
  542.                 default:
  543.                     return array();
  544.             }
  545.         }
  546.         
  547.         foreach( $this->indexes as $index ) {
  548.             $sql[] = $index->create( $xmls );
  549.         }
  550.         
  551.         return $sql;
  552.     }
  553.     
  554.     /**
  555.     * Marks a field or table for destruction
  556.     */
  557.     function drop() {
  558.         if( isset( $this->current_field ) ) {
  559.             // Drop the current field
  560.             logMsg( "Dropping field '{$this->current_field}' from table '{$this->name}'" );
  561.             // $this->drop_field[$this->current_field] = $xmls->dict->DropColumnSQL( $this->name, $this->current_field );
  562.             $this->drop_field[$this->current_field] = $this->current_field;
  563.         } else {
  564.             // Drop the current table
  565.             logMsg( "Dropping table '{$this->name}'" );
  566.             // $this->drop_table = $xmls->dict->DropTableSQL( $this->name );
  567.             $this->drop_table = TRUE;
  568.         }
  569.     }
  570. }
  571.  
  572. /**
  573. * Creates an index object in ADOdb's datadict format
  574. *
  575. * This class stores information about a database index. As charactaristics
  576. * of the index are loaded from the external source, methods and properties
  577. * of this class are used to build up the index description in ADOdb's
  578. * datadict format.
  579. *
  580. * @package axmls
  581. * @access private
  582. */
  583. class dbIndex extends dbObject {
  584.     
  585.     /**
  586.     * @var string    Index name
  587.     */
  588.     var $name;
  589.     
  590.     /**
  591.     * @var array    Index options: Index-level options
  592.     */
  593.     var $opts = array();
  594.     
  595.     /**
  596.     * @var array    Indexed fields: Table columns included in this index
  597.     */
  598.     var $columns = array();
  599.     
  600.     /**
  601.     * @var boolean Mark index for destruction
  602.     * @access private
  603.     */
  604.     var $drop = FALSE;
  605.     
  606.     /**
  607.     * Initializes the new dbIndex object.
  608.     *
  609.     * @param object $parent Parent object
  610.     * @param array $attributes Attributes
  611.     *
  612.     * @internal
  613.     */
  614.     function dbIndex( &$parent, $attributes = NULL ) {
  615.         $this->parent =& $parent;
  616.         
  617.         $this->name = $this->prefix ($attributes['NAME']);
  618.     }
  619.     
  620.     /**
  621.     * XML Callback to process start elements
  622.     *
  623.     * Processes XML opening tags. 
  624.     * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH. 
  625.     *
  626.     * @access private
  627.     */
  628.     function _tag_open( &$parser, $tag, $attributes ) {
  629.         $this->currentElement = strtoupper( $tag );
  630.         
  631.         switch( $this->currentElement ) {
  632.             case 'DROP':
  633.                 $this->drop();
  634.                 break;
  635.             case 'CLUSTERED':
  636.             case 'BITMAP':
  637.             case 'UNIQUE':
  638.             case 'FULLTEXT':
  639.             case 'HASH':
  640.                 // Add index Option
  641.                 $this->addIndexOpt( $this->currentElement );
  642.                 break;
  643.             default:
  644.                 // print_r( array( $tag, $attributes ) );
  645.         }
  646.     }
  647.     
  648.     /**
  649.     * XML Callback to process CDATA elements
  650.     *
  651.     * Processes XML cdata.
  652.     *
  653.     * @access private
  654.     */
  655.     function _tag_cdata( &$parser, $cdata ) {
  656.         switch( $this->currentElement ) {
  657.             // Index field name
  658.             case 'COL':
  659.                 $this->addField( $cdata );
  660.                 break;
  661.             default:
  662.                 
  663.         }
  664.     }
  665.     
  666.     /**
  667.     * XML Callback to process end elements
  668.     *
  669.     * @access private
  670.     */
  671.     function _tag_close( &$parser, $tag ) {
  672.         $this->currentElement = '';
  673.         
  674.         switch( strtoupper( $tag ) ) {
  675.             case 'INDEX':
  676.                 xml_set_object( $parser, $this->parent );
  677.                 break;
  678.         }
  679.     }
  680.     
  681.     /**
  682.     * Adds a field to the index
  683.     *
  684.     * @param string $name Field name
  685.     * @return string Field list
  686.     */
  687.     function addField( $name ) {
  688.         $this->columns[$this->FieldID( $name )] = $name;
  689.         
  690.         // Return the field list
  691.         return $this->columns;
  692.     }
  693.     
  694.     /**
  695.     * Adds options to the index
  696.     *
  697.     * @param string $opt Comma-separated list of index options.
  698.     * @return string Option list
  699.     */
  700.     function addIndexOpt( $opt ) {
  701.         $this->opts[] = $opt;
  702.         
  703.         // Return the options list
  704.         return $this->opts;
  705.     }
  706.     
  707.     /**
  708.     * Generates the SQL that will create the index in the database
  709.     *
  710.     * @param object $xmls adoSchema object
  711.     * @return array Array containing index creation SQL
  712.     */
  713.     function create( &$xmls ) {
  714.         if( $this->drop ) {
  715.             return NULL;
  716.         }
  717.         
  718.         // eliminate any columns that aren't in the table
  719.         foreach( $this->columns as $id => $col ) {
  720.             if( !isset( $this->parent->fields[$id] ) ) {
  721.                 unset( $this->columns[$id] );
  722.             }
  723.         }
  724.         
  725.         return $xmls->dict->CreateIndexSQL( $this->name, $this->parent->name, $this->columns, $this->opts );
  726.     }
  727.     
  728.     /**
  729.     * Marks an index for destruction
  730.     */
  731.     function drop() {
  732.         $this->drop = TRUE;
  733.     }
  734. }
  735.  
  736. /**
  737. * Creates the SQL to execute a list of provided SQL queries
  738. *
  739. * @package axmls
  740. * @access private
  741. */
  742. class dbQuerySet extends dbObject {
  743.     
  744.     /**
  745.     * @var array    List of SQL queries
  746.     */
  747.     var $queries = array();
  748.     
  749.     /**
  750.     * @var string    String used to build of a query line by line
  751.     */
  752.     var $query;
  753.     
  754.     /**
  755.     * @var string    Query prefix key
  756.     */
  757.     var $prefixKey = '';
  758.     
  759.     /**
  760.     * @var boolean    Auto prefix enable (TRUE)
  761.     */
  762.     var $prefixMethod = 'AUTO';
  763.     
  764.     /**
  765.     * Initializes the query set.
  766.     *
  767.     * @param object $parent Parent object
  768.     * @param array $attributes Attributes
  769.     */
  770.     function dbQuerySet( &$parent, $attributes = NULL ) {
  771.         $this->parent =& $parent;
  772.             
  773.         // Overrides the manual prefix key
  774.         if( isset( $attributes['KEY'] ) ) {
  775.             $this->prefixKey = $attributes['KEY'];
  776.         }
  777.         
  778.         $prefixMethod = isset( $attributes['PREFIXMETHOD'] ) ? strtoupper( trim( $attributes['PREFIXMETHOD'] ) ) : '';
  779.         
  780.         // Enables or disables automatic prefix prepending
  781.         switch( $prefixMethod ) {
  782.             case 'AUTO':
  783.                 $this->prefixMethod = 'AUTO';
  784.                 break;
  785.             case 'MANUAL':
  786.                 $this->prefixMethod = 'MANUAL';
  787.                 break;
  788.             case 'NONE':
  789.                 $this->prefixMethod = 'NONE';
  790.                 break;
  791.             default:
  792.                 $this->prefixMethod = 'AUTO';
  793.         }
  794.     }
  795.     
  796.     /**
  797.     * XML Callback to process start elements. Elements currently 
  798.     * processed are: QUERY. 
  799.     *
  800.     * @access private
  801.     */
  802.     function _tag_open( &$parser, $tag, $attributes ) {
  803.         $this->currentElement = strtoupper( $tag );
  804.         
  805.         switch( $this->currentElement ) {
  806.             case 'QUERY':
  807.                 // Create a new query in a SQL queryset.
  808.                 // Ignore this query set if a platform is specified and it's different than the 
  809.                 // current connection platform.
  810.                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
  811.                     $this->newQuery();
  812.                 } else {
  813.                     $this->discardQuery();
  814.                 }
  815.                 break;
  816.             default:
  817.                 // print_r( array( $tag, $attributes ) );
  818.         }
  819.     }
  820.     
  821.     /**
  822.     * XML Callback to process CDATA elements
  823.     */
  824.     function _tag_cdata( &$parser, $cdata ) {
  825.         switch( $this->currentElement ) {
  826.             // Line of queryset SQL data
  827.             case 'QUERY':
  828.                 $this->buildQuery( $cdata );
  829.                 break;
  830.             default:
  831.                 
  832.         }
  833.     }
  834.     
  835.     /**
  836.     * XML Callback to process end elements
  837.     *
  838.     * @access private
  839.     */
  840.     function _tag_close( &$parser, $tag ) {
  841.         $this->currentElement = '';
  842.         
  843.         switch( strtoupper( $tag ) ) {
  844.             case 'QUERY':
  845.                 // Add the finished query to the open query set.
  846.                 $this->addQuery();
  847.                 break;
  848.             case 'SQL':
  849.                 $this->parent->addSQL( $this->create( $this->parent ) );
  850.                 xml_set_object( $parser, $this->parent );
  851.                 $this->destroy();
  852.                 break;
  853.             default:
  854.                 
  855.         }
  856.     }
  857.     
  858.     /**
  859.     * Re-initializes the query.
  860.     *
  861.     * @return boolean TRUE
  862.     */
  863.     function newQuery() {
  864.         $this->query = '';
  865.         
  866.         return TRUE;
  867.     }
  868.     
  869.     /**
  870.     * Discards the existing query.
  871.     *
  872.     * @return boolean TRUE
  873.     */
  874.     function discardQuery() {
  875.         unset( $this->query );
  876.         
  877.         return TRUE;
  878.     }
  879.     
  880.     /** 
  881.     * Appends a line to a query that is being built line by line
  882.     *
  883.     * @param string $data Line of SQL data or NULL to initialize a new query
  884.     * @return string SQL query string.
  885.     */
  886.     function buildQuery( $sql = NULL ) {
  887.         if( !isset( $this->query ) ) {
  888.             return FALSE;
  889.         }
  890.         
  891.         if( empty( $sql ) ) {
  892.             return FALSE;
  893.         }
  894.         
  895.         if( !empty( $this->query ) ) {
  896.             $this->query .= ' ';
  897.         }
  898.         
  899.         $this->query .= trim( $sql );
  900.         
  901.         return $this->query;
  902.     }
  903.     
  904.     /**
  905.     * Adds a completed query to the query list
  906.     *
  907.     * @return string    SQL of added query
  908.     */
  909.     function addQuery() {
  910.         if( !isset( $this->query ) ) {
  911.             return FALSE;
  912.         }
  913.         
  914.         $this->queries[] = $this->query;
  915.         $return = $this->query;
  916.         
  917.         unset( $this->query );
  918.         
  919.         return $return;
  920.     }
  921.     
  922.     /**
  923.     * Creates and returns the current query set
  924.     *
  925.     * @param object $xmls adoSchema object
  926.     * @return array Query set
  927.     */
  928.     function create( &$xmls ) {
  929.         foreach( $this->queries as $id => $query ) {
  930.             switch( $this->prefixMethod ) {
  931.                 case 'AUTO':
  932.                     // Enable auto prefix replacement
  933.                     
  934.                     // Process object prefix.
  935.                     // Evaluate SQL statements to prepend prefix to objects
  936.                     $query = $this->prefixQuery( '/^\s*((?is)INSERT\s+(INTO\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query );
  937.                     $query = $this->prefixQuery( '/^\s*((?is)UPDATE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query );
  938.                     $query = $this->prefixQuery( '/^\s*((?is)DELETE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query );
  939.                     
  940.                     // SELECT statements aren't working yet
  941.                     #$data = preg_replace( '/(?ias)(^\s*SELECT\s+.*\s+FROM)\s+(\W\s*,?\s*)+((?i)\s+WHERE.*$)/', "\1 $prefix\2 \3", $data );
  942.                     
  943.                 case 'MANUAL':
  944.                     // If prefixKey is set and has a value then we use it to override the default constant XMLS_PREFIX.
  945.                     // If prefixKey is not set, we use the default constant XMLS_PREFIX
  946.                     if( isset( $this->prefixKey ) AND( $this->prefixKey !== '' ) ) {
  947.                         // Enable prefix override
  948.                         $query = str_replace( $this->prefixKey, $xmls->objectPrefix, $query );
  949.                     } else {
  950.                         // Use default replacement
  951.                         $query = str_replace( XMLS_PREFIX , $xmls->objectPrefix, $query );
  952.                     }
  953.             }
  954.             
  955.             $this->queries[$id] = trim( $query );
  956.         }
  957.         
  958.         // Return the query set array
  959.         return $this->queries;
  960.     }
  961.     
  962.     /**
  963.     * Rebuilds the query with the prefix attached to any objects
  964.     *
  965.     * @param string $regex Regex used to add prefix
  966.     * @param string $query SQL query string
  967.     * @param string $prefix Prefix to be appended to tables, indices, etc.
  968.     * @return string Prefixed SQL query string.
  969.     */
  970.     function prefixQuery( $regex, $query, $prefix = NULL ) {
  971.         if( !isset( $prefix ) ) {
  972.             return $query;
  973.         }
  974.         
  975.         if( preg_match( $regex, $query, $match ) ) {
  976.             $preamble = $match[1];
  977.             $postamble = $match[5];
  978.             $objectList = explode( ',', $match[3] );
  979.             // $prefix = $prefix . '_';
  980.             
  981.             $prefixedList = '';
  982.             
  983.             foreach( $objectList as $object ) {
  984.                 if( $prefixedList !== '' ) {
  985.                     $prefixedList .= ', ';
  986.                 }
  987.                 
  988.                 $prefixedList .= $prefix . trim( $object );
  989.             }
  990.             
  991.             $query = $preamble . ' ' . $prefixedList . ' ' . $postamble;
  992.         }
  993.         
  994.         return $query;
  995.     }
  996. }
  997.  
  998. /**
  999. * Loads and parses an XML file, creating an array of "ready-to-run" SQL statements
  1000. * This class is used to load and parse the XML file, to create an array of SQL statements
  1001. * that can be used to build a database, and to build the database using the SQL array.
  1002. *
  1003. * @tutorial getting_started.pkg
  1004. *
  1005. * @author Richard Tango-Lowy & Dan Cech
  1006. * @version $Revision: 1.8 $
  1007. *
  1008. * @package axmls
  1009. */
  1010. class adoSchema {
  1011.     
  1012.     /**
  1013.     * @var array    Array containing SQL queries to generate all objects
  1014.     * @access private
  1015.     */
  1016.     var $sqlArray;
  1017.     
  1018.     /**
  1019.     * @var object    ADOdb connection object
  1020.     * @access private
  1021.     */
  1022.     var $db;
  1023.     
  1024.     /**
  1025.     * @var object    ADOdb Data Dictionary
  1026.     * @access private
  1027.     */
  1028.     var $dict;
  1029.     
  1030.     /**
  1031.     * @var string Current XML element
  1032.     * @access private
  1033.     */
  1034.     var $currentElement = '';
  1035.     
  1036.     /**
  1037.     * @var string If set (to 'ALTER' or 'REPLACE'), upgrade an existing database
  1038.     * @access private
  1039.     */
  1040.     var $upgrade = '';
  1041.     
  1042.     /**
  1043.     * @var string Optional object prefix
  1044.     * @access private
  1045.     */
  1046.     var $objectPrefix = '';
  1047.     
  1048.     /**
  1049.     * @var long    Original Magic Quotes Runtime value
  1050.     * @access private
  1051.     */
  1052.     var $mgq;
  1053.     
  1054.     /**
  1055.     * @var long    System debug
  1056.     * @access private
  1057.     */
  1058.     var $debug;
  1059.     
  1060.     /**
  1061.     * @var string Regular expression to find schema version
  1062.     * @access private
  1063.     */
  1064.     var $versionRegex = '/<schema.*?( version="([^"]*)")?.*?>/';
  1065.     
  1066.     /**
  1067.     * @var string Current schema version
  1068.     * @access private
  1069.     */
  1070.     var $schemaVersion;
  1071.     
  1072.     /**
  1073.     * @var int    Success of last Schema execution
  1074.     */
  1075.     var $success;
  1076.     
  1077.     /**
  1078.     * @var bool    Execute SQL inline as it is generated
  1079.     */
  1080.     var $executeInline;
  1081.     
  1082.     /**
  1083.     * @var bool    Continue SQL execution if errors occur
  1084.     */
  1085.     var $continueOnError;
  1086.     
  1087.     /**
  1088.     * Creates an adoSchema object
  1089.     *
  1090.     * Creating an adoSchema object is the first step in processing an XML schema.
  1091.     * The only parameter is an ADOdb database connection object, which must already
  1092.     * have been created.
  1093.     *
  1094.     * @param object $db ADOdb database connection object.
  1095.     */
  1096.     function adoSchema( &$db ) {
  1097.         // Initialize the environment
  1098.         $this->mgq = get_magic_quotes_runtime();
  1099.         set_magic_quotes_runtime(0);
  1100.         
  1101.         $this->debug = $this->db->debug;
  1102.         $this->db =& $db;
  1103.         $this->dict = NewDataDictionary( $this->db );
  1104.         $this->sqlArray = array();
  1105.         $this->schemaVersion = XMLS_SCHEMA_VERSION;
  1106.         $this->executeInline( XMLS_EXECUTE_INLINE );
  1107.         $this->continueOnError( XMLS_CONTINUE_ON_ERROR );
  1108.         $this->setUpgradeMethod();
  1109.     }
  1110.     
  1111.     /**
  1112.     * Sets the method to be used for upgrading an existing database
  1113.     *
  1114.     * Use this method to specify how existing database objects should be upgraded.
  1115.     * The method option can be set to ALTER, REPLACE, BEST, or NONE. ALTER attempts to
  1116.     * alter each database object directly, REPLACE attempts to rebuild each object
  1117.     * from scratch, BEST attempts to determine the best upgrade method for each
  1118.     * object, and NONE disables upgrading.
  1119.     *
  1120.     * This method is not yet used by AXMLS, but exists for backward compatibility.
  1121.     * The ALTER method is automatically assumed when the adoSchema object is
  1122.     * instantiated; other upgrade methods are not currently supported.
  1123.     *
  1124.     * @param string $method Upgrade method (ALTER|REPLACE|BEST|NONE)
  1125.     * @returns string Upgrade method used
  1126.     */
  1127.     function SetUpgradeMethod( $method = '' ) {
  1128.         if( !is_string( $method ) ) {
  1129.             return FALSE;
  1130.         }
  1131.         
  1132.         $method = strtoupper( $method );
  1133.         
  1134.         // Handle the upgrade methods
  1135.         switch( $method ) {
  1136.             case 'ALTER':
  1137.                 $this->upgrade = $method;
  1138.                 break;
  1139.             case 'REPLACE':
  1140.                 $this->upgrade = $method;
  1141.                 break;
  1142.             case 'BEST':
  1143.                 $this->upgrade = 'ALTER';
  1144.                 break;
  1145.             case 'NONE':
  1146.                 $this->upgrade = 'NONE';
  1147.                 break;
  1148.             default:
  1149.                 // Use default if no legitimate method is passed.
  1150.                 $this->upgrade = XMLS_DEFAULT_UPGRADE_METHOD;
  1151.         }
  1152.         
  1153.         return $this->upgrade;
  1154.     }
  1155.     
  1156.     /**
  1157.     * Enables/disables inline SQL execution.
  1158.     *
  1159.     * Call this method to enable or disable inline execution of the schema. If the mode is set to TRUE (inline execution),
  1160.     * AXMLS applies the SQL to the database immediately as each schema entity is parsed. If the mode
  1161.     * is set to FALSE (post execution), AXMLS parses the entire schema and you will need to call adoSchema::ExecuteSchema()
  1162.     * to apply the schema to the database.
  1163.     *
  1164.     * @param bool $mode execute
  1165.     * @return bool current execution mode
  1166.     *
  1167.     * @see ParseSchema(), ExecuteSchema()
  1168.     */
  1169.     function ExecuteInline( $mode = NULL ) {
  1170.         if( is_bool( $mode ) ) {
  1171.             $this->executeInline = $mode;
  1172.         }
  1173.         
  1174.         return $this->executeInline;
  1175.     }
  1176.     
  1177.     /**
  1178.     * Enables/disables SQL continue on error.
  1179.     *
  1180.     * Call this method to enable or disable continuation of SQL execution if an error occurs.
  1181.     * If the mode is set to TRUE (continue), AXMLS will continue to apply SQL to the database, even if an error occurs.
  1182.     * If the mode is set to FALSE (halt), AXMLS will halt execution of generated sql if an error occurs, though parsing
  1183.     * of the schema will continue.
  1184.     *
  1185.     * @param bool $mode execute
  1186.     * @return bool current continueOnError mode
  1187.     *
  1188.     * @see addSQL(), ExecuteSchema()
  1189.     */
  1190.     function ContinueOnError( $mode = NULL ) {
  1191.         if( is_bool( $mode ) ) {
  1192.             $this->continueOnError = $mode;
  1193.         }
  1194.         
  1195.         return $this->continueOnError;
  1196.     }
  1197.     
  1198.     /**
  1199.     * Loads an XML schema from a file and converts it to SQL.
  1200.     *
  1201.     * Call this method to load the specified schema (see the DTD for the proper format) from
  1202.     * the filesystem and generate the SQL necessary to create the database described. 
  1203.     * @see ParseSchemaString()
  1204.     *
  1205.     * @param string $file Name of XML schema file.
  1206.     * @return array Array of SQL queries, ready to execute
  1207.     */
  1208.     function ParseSchema( $filename ) {
  1209.         return $this->ParseSchemaString( $this->ConvertSchemaFile ( $filename ) );
  1210.     }
  1211.     
  1212.     /**
  1213.     * Loads an XML schema from a file and converts it to SQL.
  1214.     *
  1215.     * Call this method to load the specified schema from a file (see the DTD for the proper format) 
  1216.     * and generate the SQL necessary to create the database described by the schema.
  1217.     *
  1218.     * @param string $file Name of XML schema file.
  1219.     * @return array Array of SQL queries, ready to execute.
  1220.     *
  1221.     * @deprecated Replaced by adoSchema::ParseSchema() and adoSchema::ParseSchemaString()
  1222.     * @see ParseSchema(), ParseSchemaString()
  1223.     */
  1224.     function ParseSchemaFile( $filename ) {
  1225.         // Open the file
  1226.         if( !($fp = fopen( $filename, 'r' )) ) {
  1227.             // die( 'Unable to open file' );
  1228.             return FALSE;
  1229.         }
  1230.         
  1231.         // do version detection here
  1232.         if( $this->SchemaFileVersion( $filename ) != $this->schemaVersion ) {
  1233.             return FALSE;
  1234.         }
  1235.         
  1236.         $xmlParser = $this->create_parser();
  1237.         
  1238.         // Process the file
  1239.         while( $data = fread( $fp, 4096 ) ) {
  1240.             if( !xml_parse( $xmlParser, $data, feof( $fp ) ) ) {
  1241.                 die( sprintf(
  1242.                     "XML error: %s at line %d",
  1243.                     xml_error_string( xml_get_error_code( $xmlParser) ),
  1244.                     xml_get_current_line_number( $xmlParser)
  1245.                 ) );
  1246.             }
  1247.         }
  1248.         
  1249.         xml_parser_free( $xmlParser );
  1250.         
  1251.         return $this->sqlArray;
  1252.     }
  1253.     
  1254.     /**
  1255.     * Converts an XML schema string to SQL.
  1256.     *
  1257.     * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
  1258.     * and generate the SQL necessary to create the database described by the schema. 
  1259.     * @see ParseSchema()
  1260.     *
  1261.     * @param string $xmlstring XML schema string.
  1262.     * @return array Array of SQL queries, ready to execute.
  1263.     */
  1264.     function ParseSchemaString( $xmlstring ) {
  1265.         if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
  1266.             return FALSE;
  1267.         }
  1268.         
  1269.         // do version detection here
  1270.         if( $this->SchemaStringVersion( $xmlstring ) != $this->schemaVersion ) {
  1271.             return FALSE;
  1272.         }
  1273.         
  1274.         $xmlParser = $this->create_parser();
  1275.         
  1276.         $this->success = 2;
  1277.         
  1278.         if( !xml_parse( $xmlParser, $xmlstring, TRUE ) ) {
  1279.             die( sprintf(
  1280.                 "XML error: %s at line %d",
  1281.                 xml_error_string( xml_get_error_code( $xmlParser) ),
  1282.                 xml_get_current_line_number( $xmlParser)
  1283.             ) );
  1284.         }
  1285.         
  1286.         xml_parser_free( $xmlParser );
  1287.         return $this->sqlArray;
  1288.     }
  1289.     
  1290.     /**
  1291.     * Applies the current XML schema to the database (post execution).
  1292.     *
  1293.     * Call this method to apply the current schema (generally created by calling 
  1294.     * ParseSchema() or ParseSchemaString() ) to the database (creating the tables, indexes, 
  1295.     * and executing other SQL specified in the schema) after parsing.
  1296.     * @see ParseSchema(), ParseSchemaString(), ExecuteInline()
  1297.     *
  1298.     * @param array $sqlArray Array of SQL statements that will be applied rather than
  1299.     *        the current schema.
  1300.     * @param boolean $continueOnErr Continue to apply the schema even if an error occurs.
  1301.     * @returns integer 0 if failure, 1 if errors, 2 if successful.
  1302.     */
  1303.     function ExecuteSchema( $sqlArray = NULL, $continueOnErr =  NULL ) {
  1304.         if( !is_bool( $continueOnErr ) ) {
  1305.             $continueOnErr = $this->ContinueOnError();
  1306.         }
  1307.         
  1308.         if( !isset( $sqlArray ) ) {
  1309.             $sqlArray = $this->sqlArray;
  1310.         }
  1311.         
  1312.         if( !is_array( $sqlArray ) ) {
  1313.             $this->success = 0;
  1314.         } else {
  1315.             $this->success = $this->dict->ExecuteSQLArray( $sqlArray, $continueOnErr );
  1316.         }
  1317.         
  1318.         return $this->success;
  1319.     }
  1320.     
  1321.     /**
  1322.     * Returns the current SQL array. 
  1323.     *
  1324.     * Call this method to fetch the array of SQL queries resulting from 
  1325.     * ParseSchema() or ParseSchemaString(). 
  1326.     *
  1327.     * @param string $format Format: HTML, TEXT, or NONE (PHP array)
  1328.     * @return array Array of SQL statements or FALSE if an error occurs
  1329.     */
  1330.     function PrintSQL( $format = 'NONE' ) {
  1331.         return $this->getSQL( $format, $sqlArray );
  1332.     }
  1333.     
  1334.     /**
  1335.     * Saves the current SQL array to the local filesystem as a list of SQL queries.
  1336.     *
  1337.     * Call this method to save the array of SQL queries (generally resulting from a
  1338.     * parsed XML schema) to the filesystem.
  1339.     *
  1340.     * @param string $filename Path and name where the file should be saved.
  1341.     * @return boolean TRUE if save is successful, else FALSE. 
  1342.     */
  1343.     function SaveSQL( $filename = './schema.sql' ) {
  1344.         
  1345.         if( !isset( $sqlArray ) ) {
  1346.             $sqlArray = $this->sqlArray;
  1347.         }
  1348.         if( !isset( $sqlArray ) ) {
  1349.             return FALSE;
  1350.         }
  1351.         
  1352.         $fp = fopen( $filename, "w" );
  1353.         
  1354.         foreach( $sqlArray as $key => $query ) {
  1355.             fwrite( $fp, $query . ";\n" );
  1356.         }
  1357.         fclose( $fp );
  1358.     }
  1359.     
  1360.     /**
  1361.     * Create an xml parser
  1362.     *
  1363.     * @return object PHP XML parser object
  1364.     *
  1365.     * @access private
  1366.     */
  1367.     function &create_parser() {
  1368.         // Create the parser
  1369.         $xmlParser = xml_parser_create();
  1370.         xml_set_object( $xmlParser, $this );
  1371.         
  1372.         // Initialize the XML callback functions
  1373.         xml_set_element_handler( $xmlParser, '_tag_open', '_tag_close' );
  1374.         xml_set_character_data_handler( $xmlParser, '_tag_cdata' );
  1375.         
  1376.         return $xmlParser;
  1377.     }
  1378.     
  1379.     /**
  1380.     * XML Callback to process start elements
  1381.     *
  1382.     * @access private
  1383.     */
  1384.     function _tag_open( &$parser, $tag, $attributes ) {
  1385.         switch( strtoupper( $tag ) ) {
  1386.             case 'TABLE':
  1387.                 $this->obj = new dbTable( $this, $attributes );
  1388.                 xml_set_object( $parser, $this->obj );
  1389.                 break;
  1390.             case 'SQL':
  1391.                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
  1392.                     $this->obj = new dbQuerySet( $this, $attributes );
  1393.                     xml_set_object( $parser, $this->obj );
  1394.                 }
  1395.                 break;
  1396.             default:
  1397.                 // print_r( array( $tag, $attributes ) );
  1398.         }
  1399.         
  1400.     }
  1401.     
  1402.     /**
  1403.     * XML Callback to process CDATA elements
  1404.     *
  1405.     * @access private
  1406.     */
  1407.     function _tag_cdata( &$parser, $cdata ) {
  1408.     }
  1409.     
  1410.     /**
  1411.     * XML Callback to process end elements
  1412.     *
  1413.     * @access private
  1414.     * @internal
  1415.     */
  1416.     function _tag_close( &$parser, $tag ) {
  1417.         
  1418.     }
  1419.     
  1420.     /**
  1421.     * Converts an XML schema string to the specified DTD version.
  1422.     *
  1423.     * Call this method to convert a string containing an XML schema to a different AXMLS
  1424.     * DTD version. For instance, to convert a schema created for an pre-1.0 version for 
  1425.     * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version 
  1426.     * parameter is specified, the schema will be converted to the current DTD version. 
  1427.     * If the newFile parameter is provided, the converted schema will be written to the specified
  1428.     * file.
  1429.     * @see ConvertSchemaFile()
  1430.     *
  1431.     * @param string $schema String containing XML schema that will be converted.
  1432.     * @param string $newVersion DTD version to convert to.
  1433.     * @param string $newFile File name of (converted) output file.
  1434.     * @return string Converted XML schema or FALSE if an error occurs.
  1435.     */
  1436.     function ConvertSchemaString( $schema, $newVersion = NULL, $newFile = NULL ) {
  1437.         
  1438.         // grab current version
  1439.         if( !( $version = $this->SchemaStringVersion( $schema ) ) ) {
  1440.             return FALSE;
  1441.         }
  1442.         
  1443.         if( !isset ($newVersion) ) {
  1444.             $newVersion = $this->schemaVersion;
  1445.         }
  1446.         
  1447.         if( $version == $newVersion ) {
  1448.             $result = $schema;
  1449.         } else {
  1450.             // Fail if XSLT extension is not available
  1451.             if( ! function_exists( 'xslt_create' ) ) {
  1452.                 return FALSE;
  1453.             }
  1454.             
  1455.             $xsl_file = dirname( __FILE__ ) . '/xsl/convert-' . $version . '-' . $newVersion . '.xsl';
  1456.             
  1457.             // look for xsl
  1458.             if( !is_readable( $xsl_file ) ) {
  1459.                 return FALSE;
  1460.             }
  1461.             
  1462.             $arguments = array (
  1463.                 '/_xml' => $schema,
  1464.                 '/_xsl' => file_get_contents ($xsl_file)
  1465.             );
  1466.             
  1467.             // create an XSLT processor
  1468.             $xh = xslt_create ();
  1469.             
  1470.             // set error handler
  1471.             xslt_set_error_handler ($xh, array (&$this, 'xslt_error_handler'));
  1472.             
  1473.             // process the schema
  1474.             $result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments); 
  1475.             
  1476.             xslt_free ($xh);
  1477.         }
  1478.         
  1479.         if( is_string ($newFile) AND ( $fp = fopen( $newFile, 'w' ) ) ) {
  1480.             fwrite ($fp, $result);
  1481.             fclose ($fp);
  1482.         }
  1483.         
  1484.         return $result;
  1485.     }
  1486.     
  1487.     /**
  1488.     * Converts an XML schema file to the specified DTD version.
  1489.     *
  1490.     * Call this method to convert the specified XML schema file to a different AXMLS
  1491.     * DTD version. For instance, to convert a schema created for an pre-1.0 version for 
  1492.     * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version 
  1493.     * parameter is specified, the schema will be converted to the current DTD version. 
  1494.     * If the newFile parameter is provided, the converted schema will be written to the specified
  1495.     * file.
  1496.     * @see ConvertSchemaString()
  1497.     *
  1498.     * @param string $filename Name of XML schema file that will be converted.
  1499.     * @param string $newVersion DTD version to convert to.
  1500.     * @param string $newFile File name of (converted) output file.
  1501.     * @return string Converted XML schema or FALSE if an error occurs.
  1502.     */
  1503.     function ConvertSchemaFile( $filename, $newVersion = NULL, $newFile = NULL ) {
  1504.         
  1505.         // grab current version
  1506.         if( !( $version = $this->SchemaFileVersion( $filename ) ) ) {
  1507.             return FALSE;
  1508.         }
  1509.         
  1510.         if( !isset ($newVersion) ) {
  1511.             $newVersion = $this->schemaVersion;
  1512.         }
  1513.         
  1514.         if( $version == $newVersion ) {
  1515.             $result = file_get_contents( $filename );
  1516.             
  1517.             // remove unicode BOM if present
  1518.             if( substr( $result, 0, 3 ) == sprintf( '%c%c%c', 239, 187, 191 ) ) {
  1519.                 $result = substr( $result, 3 );
  1520.             }
  1521.         } else {
  1522.             // Fail if XSLT extension is not available
  1523.             if( ! function_exists( 'xslt_create' ) ) {
  1524.                 return FALSE;
  1525.             }
  1526.             
  1527.             $xsl_file = dirname( __FILE__ ) . '/xsl/convert-' . $version . '-' . $newVersion . '.xsl';
  1528.             
  1529.             // look for xsl
  1530.             if( !is_readable( $xsl_file ) ) {
  1531.                 return FALSE;
  1532.             }
  1533.             
  1534.             $arguments = array (
  1535.                 '/_xml' => file_get_contents ($filename),
  1536.                 '/_xsl' => file_get_contents ($xsl_file)
  1537.             );
  1538.             
  1539.             // create an XSLT processor
  1540.             $xh = xslt_create ();
  1541.             
  1542.             // set error handler
  1543.             xslt_set_error_handler ($xh, array (&$this, 'xslt_error_handler'));
  1544.             
  1545.             // process the schema
  1546.             $result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments); 
  1547.             
  1548.             xslt_free ($xh);
  1549.         }
  1550.         
  1551.         if( is_string ($newFile) AND ( $fp = fopen( $newFile, 'w' ) ) ) {
  1552.             fwrite ($fp, $result);
  1553.             fclose ($fp);
  1554.         }
  1555.         
  1556.         return $result;
  1557.     }
  1558.     
  1559.     /**
  1560.     * Processes XSLT transformation errors
  1561.     *
  1562.     * @param object $parser XML parser object
  1563.     * @param integer $errno Error number
  1564.     * @param integer $level Error level
  1565.     * @param array $fields Error information fields
  1566.     *
  1567.     * @access private
  1568.     */
  1569.     function xslt_error_handler( $parser, $errno, $level, $fields ) {
  1570.         if( is_array( $fields ) ) {
  1571.             $msg = array(
  1572.                 'Message Type' => ucfirst( $fields['msgtype'] ),
  1573.                 'Message Code' => $fields['code'],
  1574.                 'Message' => $fields['msg'],
  1575.                 'Error Number' => $errno,
  1576.                 'Level' => $level
  1577.             );
  1578.             
  1579.             switch( $fields['URI'] ) {
  1580.                 case 'arg:/_xml':
  1581.                     $msg['Input'] = 'XML';
  1582.                     break;
  1583.                 case 'arg:/_xsl':
  1584.                     $msg['Input'] = 'XSL';
  1585.                     break;
  1586.                 default:
  1587.                     $msg['Input'] = $fields['URI'];
  1588.             }
  1589.             
  1590.             $msg['Line'] = $fields['line'];
  1591.         } else {
  1592.             $msg = array(
  1593.                 'Message Type' => 'Error',
  1594.                 'Error Number' => $errno,
  1595.                 'Level' => $level,
  1596.                 'Fields' => var_export( $fields, TRUE )
  1597.             );
  1598.         }
  1599.         
  1600.         $error_details = $msg['Message Type'] . ' in XSLT Transformation' . "\n"
  1601.                        . '<table>' . "\n";
  1602.         
  1603.         foreach( $msg as $label => $details ) {
  1604.             $error_details .= '<tr><td><b>' . $label . ': </b></td><td>' . htmlentities( $details ) . '</td></tr>' . "\n";
  1605.         }
  1606.         
  1607.         $error_details .= '</table>';
  1608.         
  1609.         trigger_error( $error_details, E_USER_ERROR );
  1610.     }
  1611.     
  1612.     /**
  1613.     * Returns the AXMLS Schema Version of the requested XML schema file.
  1614.     *
  1615.     * Call this method to obtain the AXMLS DTD version of the requested XML schema file.
  1616.     * @see SchemaStringVersion()
  1617.     *
  1618.     * @param string $filename AXMLS schema file
  1619.     * @return string Schema version number or FALSE on error
  1620.     */
  1621.     function SchemaFileVersion( $filename ) {
  1622.         // Open the file
  1623.         if( !($fp = fopen( $filename, 'r' )) ) {
  1624.             // die( 'Unable to open file' );
  1625.             return FALSE;
  1626.         }
  1627.         
  1628.         // Process the file
  1629.         while( $data = fread( $fp, 4096 ) ) {
  1630.             if( preg_match( $this->versionRegex, $data, $matches ) ) {
  1631.                 return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION;
  1632.             }
  1633.         }
  1634.         
  1635.         return FALSE;
  1636.     }
  1637.     
  1638.     /**
  1639.     * Returns the AXMLS Schema Version of the provided XML schema string.
  1640.     *
  1641.     * Call this method to obtain the AXMLS DTD version of the provided XML schema string.
  1642.     * @see SchemaFileVersion()
  1643.     *
  1644.     * @param string $xmlstring XML schema string
  1645.     * @return string Schema version number or FALSE on error
  1646.     */
  1647.     function SchemaStringVersion( $xmlstring ) {
  1648.         if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
  1649.             return FALSE;
  1650.         }
  1651.         
  1652.         if( preg_match( $this->versionRegex, $xmlstring, $matches ) ) {
  1653.             return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION;
  1654.         }
  1655.         
  1656.         return FALSE;
  1657.     }
  1658.     
  1659.     /**
  1660.     * Extracts an XML schema from an existing database.
  1661.     *
  1662.     * Call this method to create an XML schema string from an existing database.
  1663.     * If the data parameter is set to TRUE, AXMLS will include the data from the database
  1664.     * in the schema. 
  1665.     *
  1666.     * @param boolean $data Include data in schema dump
  1667.     * @return string Generated XML schema
  1668.     */
  1669.     function ExtractSchema( $data = FALSE ) {
  1670.         $old_mode = $this->db->SetFetchMode( ADODB_FETCH_NUM );
  1671.         
  1672.         $schema = '<?xml version="1.0"?>' . "\n"
  1673.                 . '<schema version="' . $this->schemaVersion . '">' . "\n";
  1674.         
  1675.         if( is_array( $tables = $this->db->MetaTables( 'TABLES' ) ) ) {
  1676.             foreach( $tables as $table ) {
  1677.                 $schema .= '    <table name="' . $table . '">' . "\n";
  1678.                 
  1679.                 // grab details from database
  1680.                 $rs = $this->db->Execute( 'SELECT * FROM ' . $table . ' WHERE -1' );
  1681.                 $fields = $this->db->MetaColumns( $table );
  1682.                 $indexes = $this->db->MetaIndexes( $table );
  1683.                 
  1684.                 if( is_array( $fields ) ) {
  1685.                     foreach( $fields as $details ) {
  1686.                         $extra = '';
  1687.                         $content = array();
  1688.                         
  1689.                         if( $details->max_length > 0 ) {
  1690.                             $extra .= ' size="' . $details->max_length . '"';
  1691.                         }
  1692.                         
  1693.                         if( $details->primary_key ) {
  1694.                             $content[] = '<PRIMARY/>';
  1695.                         } elseif( $details->not_null ) {
  1696.                             $content[] = '<NOTNULL/>';
  1697.                         }
  1698.                         
  1699.                         if( $details->has_default ) {
  1700.                             $content[] = '<DEFAULT value="' . $details->default_value . '"/>';
  1701.                         }
  1702.                         
  1703.                         if( $details->auto_increment ) {
  1704.                             $content[] = '<AUTOINCREMENT/>';
  1705.                         }
  1706.                         
  1707.                         // this stops the creation of 'R' columns,
  1708.                         // AUTOINCREMENT is used to create auto columns
  1709.                         $details->primary_key = 0;
  1710.                         $type = $rs->MetaType( $details );
  1711.                         
  1712.                         $schema .= '        <field name="' . $details->name . '" type="' . $type . '"' . $extra . '>';
  1713.                         
  1714.                         if( !empty( $content ) ) {
  1715.                             $schema .= "\n            " . implode( "\n            ", $content ) . "\n        ";
  1716.                         }
  1717.                         
  1718.                         $schema .= '</field>' . "\n";
  1719.                     }
  1720.                 }
  1721.                 
  1722.                 if( is_array( $indexes ) ) {
  1723.                     foreach( $indexes as $index => $details ) {
  1724.                         $schema .= '        <index name="' . $index . '">' . "\n";
  1725.                         
  1726.                         if( $details['unique'] ) {
  1727.                             $schema .= '            <UNIQUE/>' . "\n";
  1728.                         }
  1729.                         
  1730.                         foreach( $details['columns'] as $column ) {
  1731.                             $schema .= '            <col>' . $column . '</col>' . "\n";
  1732.                         }
  1733.                         
  1734.                         $schema .= '        </index>' . "\n";
  1735.                     }
  1736.                 }
  1737.                 
  1738.                 if( $data ) {
  1739.                     $rs = $this->db->Execute( 'SELECT * FROM ' . $table );
  1740.                     
  1741.                     if( is_object( $rs ) ) {
  1742.                         $schema .= '        <data>' . "\n";
  1743.                         
  1744.                         while( $row = $rs->FetchRow() ) {
  1745.                             $schema .= '            <row><f>' . implode( '</f><f>', $row ) . '</f></row>' . "\n";
  1746.                         }
  1747.                         
  1748.                         $schema .= '        </data>' . "\n";
  1749.                     }
  1750.                 }
  1751.                 
  1752.                 $schema .= '    </table>' . "\n";
  1753.             }
  1754.         }
  1755.         
  1756.         $this->db->SetFetchMode( $old_mode );
  1757.         
  1758.         $schema .= '</schema>';
  1759.         return $schema;
  1760.     }
  1761.     
  1762.     /**
  1763.     * Sets a prefix for database objects
  1764.     *
  1765.     * Call this method to set a standard prefix that will be prepended to all database tables 
  1766.     * and indices when the schema is parsed. Calling setPrefix with no arguments clears the prefix.
  1767.     *
  1768.     * @param string $prefix Prefix that will be prepended.
  1769.     * @param boolean $underscore If TRUE, automatically append an underscore character to the prefix.
  1770.     * @return boolean TRUE if successful, else FALSE
  1771.     */
  1772.     function SetPrefix( $prefix = '', $underscore = TRUE ) {
  1773.         switch( TRUE ) {
  1774.             // clear prefix
  1775.             case empty( $prefix ):
  1776.                 logMsg( 'Cleared prefix' );
  1777.                 $this->objectPrefix = '';
  1778.                 return TRUE;
  1779.             // prefix too long
  1780.             case strlen( $prefix ) > XMLS_PREFIX_MAXLEN:
  1781.             // prefix contains invalid characters
  1782.             case !preg_match( '/^[a-z][a-z0-9]+$/i', $prefix ):
  1783.                 logMsg( 'Invalid prefix: ' . $prefix );
  1784.                 return FALSE;
  1785.         }
  1786.         
  1787.         if( $underscore AND substr( $prefix, -1 ) != '_' ) {
  1788.             $prefix .= '_';
  1789.         }
  1790.         
  1791.         // prefix valid
  1792.         logMsg( 'Set prefix: ' . $prefix );
  1793.         $this->objectPrefix = $prefix;
  1794.         return TRUE;
  1795.     }
  1796.     
  1797.     /**
  1798.     * Returns an object name with the current prefix prepended.
  1799.     *
  1800.     * @param string    $name Name
  1801.     * @return string    Prefixed name
  1802.     *
  1803.     * @access private
  1804.     */
  1805.     function prefix( $name = '' ) {
  1806.         // if prefix is set
  1807.         if( !empty( $this->objectPrefix ) ) {
  1808.             // Prepend the object prefix to the table name
  1809.             // prepend after quote if used
  1810.             return preg_replace( '/^(`?)(.+)$/', '$1' . $this->objectPrefix . '$2', $name );
  1811.         }
  1812.         
  1813.         // No prefix set. Use name provided.
  1814.         return $name;
  1815.     }
  1816.     
  1817.     /**
  1818.     * Checks if element references a specific platform
  1819.     *
  1820.     * @param string $platform Requested platform
  1821.     * @returns boolean TRUE if platform check succeeds
  1822.     *
  1823.     * @access private
  1824.     */
  1825.     function supportedPlatform( $platform = NULL ) {
  1826.         $regex = '/^(\w*\|)*' . $this->db->databaseType . '(\|\w*)*$/';
  1827.         
  1828.         if( !isset( $platform ) OR preg_match( $regex, $platform ) ) {
  1829.             logMsg( "Platform $platform is supported" );
  1830.             return TRUE;
  1831.         } else {
  1832.             logMsg( "Platform $platform is NOT supported" );
  1833.             return FALSE;
  1834.         }
  1835.     }
  1836.     
  1837.     /**
  1838.     * Clears the array of generated SQL.
  1839.     *
  1840.     * @access private
  1841.     */
  1842.     function clearSQL() {
  1843.         $this->sqlArray = array();
  1844.     }
  1845.     
  1846.     /**
  1847.     * Adds SQL into the SQL array.
  1848.     *
  1849.     * @param mixed $sql SQL to Add
  1850.     * @return boolean TRUE if successful, else FALSE.
  1851.     *
  1852.     * @access private
  1853.     */    
  1854.     function addSQL( $sql = NULL ) {
  1855.         if( is_array( $sql ) ) {
  1856.             foreach( $sql as $line ) {
  1857.                 $this->addSQL( $line );
  1858.             }
  1859.             
  1860.             return TRUE;
  1861.         }
  1862.         
  1863.         if( is_string( $sql ) ) {
  1864.             $this->sqlArray[] = $sql;
  1865.             
  1866.             // if executeInline is enabled, and either no errors have occurred or continueOnError is enabled, execute SQL.
  1867.             if( $this->ExecuteInline() && ( $this->success == 2 || $this->ContinueOnError() ) ) {
  1868.                 $saved = $this->db->debug;
  1869.                 $this->db->debug = $this->debug;
  1870.                 $ok = $this->db->Execute( $sql );
  1871.                 $this->db->debug = $saved;
  1872.                 
  1873.                 if( !$ok ) {
  1874.                     if( $this->debug ) {
  1875.                         ADOConnection::outp( $this->db->ErrorMsg() );
  1876.                     }
  1877.                     
  1878.                     $this->success = 1;
  1879.                 }
  1880.             }
  1881.             
  1882.             return TRUE;
  1883.         }
  1884.         
  1885.         return FALSE;
  1886.     }
  1887.     
  1888.     /**
  1889.     * Gets the SQL array in the specified format.
  1890.     *
  1891.     * @param string $format Format
  1892.     * @return mixed SQL
  1893.     *    
  1894.     * @access private
  1895.     */
  1896.     function getSQL( $format = NULL, $sqlArray = NULL ) {
  1897.         if( !is_array( $sqlArray ) ) {
  1898.             $sqlArray = $this->sqlArray;
  1899.         }
  1900.         
  1901.         if( !is_array( $sqlArray ) ) {
  1902.             return FALSE;
  1903.         }
  1904.         
  1905.         switch( strtolower( $format ) ) {
  1906.             case 'string':
  1907.             case 'text':
  1908.                 return !empty( $sqlArray ) ? implode( ";\n\n", $sqlArray ) . ';' : '';
  1909.             case'html':
  1910.                 return !empty( $sqlArray ) ? nl2br( htmlentities( implode( ";\n\n", $sqlArray ) . ';' ) ) : '';
  1911.         }
  1912.         
  1913.         return $this->sqlArray;
  1914.     }
  1915.     
  1916.     /**
  1917.     * Destroys an adoSchema object.
  1918.     *
  1919.     * Call this method to clean up after an adoSchema object that is no longer in use.
  1920.     * @deprecated adoSchema now cleans up automatically.
  1921.     */
  1922.     function Destroy() {
  1923.         set_magic_quotes_runtime( $this->mgq );
  1924.         unset( $this );
  1925.     }
  1926. }
  1927.  
  1928. /**
  1929. * Message logging function
  1930. *
  1931. * @access private
  1932. */
  1933. function logMsg( $msg, $title = NULL ) {
  1934.     if( XMLS_DEBUG ) {
  1935.         echo '<pre>';
  1936.         
  1937.         if( isset( $title ) ) {
  1938.             echo '<h3>' . htmlentities( $title ) . '</h3>';
  1939.         }
  1940.         
  1941.         if( is_object( $this ) ) {
  1942.             echo '[' . get_class( $this ) . '] ';
  1943.         }
  1944.         
  1945.         print_r( $msg );
  1946.         
  1947.         echo '</pre>';
  1948.     }
  1949. }
  1950. ?>