home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Resources / Developers / XAMPP 1.5.4 / Windows installer / xampp-win32-1.5.4-installer.exe / xampp / php / pear / adodb / adodb-xmlschema03.inc.php < prev    next >
Encoding:
PHP Script  |  2006-08-02  |  61.2 KB  |  2,403 lines

  1. <?php
  2. // Copyright (c) 2004-2005 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. /**
  9.  * xmlschema is a class that allows the user to quickly and easily
  10.  * build a database on any ADOdb-supported platform using a simple
  11.  * XML schema.
  12.  *
  13.  * Last Editor: $Author: jlim $
  14.  * @author Richard Tango-Lowy & Dan Cech
  15.  * @version $Revision: 1.62 $
  16.  *
  17.  * @package axmls
  18.  * @tutorial getting_started.pkg
  19.  */
  20.  
  21. function _file_get_contents($file) 
  22. {
  23.      if (function_exists('file_get_contents')) return file_get_contents($file);
  24.     
  25.     $f = fopen($file,'r');
  26.     if (!$f) return '';
  27.     $t = '';
  28.     
  29.     while ($s = fread($f,100000)) $t .= $s;
  30.     fclose($f);
  31.     return $t;
  32. }
  33.  
  34.  
  35. /**
  36. * Debug on or off
  37. */
  38. if( !defined( 'XMLS_DEBUG' ) ) {
  39.     define( 'XMLS_DEBUG', FALSE );
  40. }
  41.  
  42. /**
  43. * Default prefix key
  44. */
  45. if( !defined( 'XMLS_PREFIX' ) ) {
  46.     define( 'XMLS_PREFIX', '%%P' );
  47. }
  48.  
  49. /**
  50. * Maximum length allowed for object prefix
  51. */
  52. if( !defined( 'XMLS_PREFIX_MAXLEN' ) ) {
  53.     define( 'XMLS_PREFIX_MAXLEN', 10 );
  54. }
  55.  
  56. /**
  57. * Execute SQL inline as it is generated
  58. */
  59. if( !defined( 'XMLS_EXECUTE_INLINE' ) ) {
  60.     define( 'XMLS_EXECUTE_INLINE', FALSE );
  61. }
  62.  
  63. /**
  64. * Continue SQL Execution if an error occurs?
  65. */
  66. if( !defined( 'XMLS_CONTINUE_ON_ERROR' ) ) {
  67.     define( 'XMLS_CONTINUE_ON_ERROR', FALSE );
  68. }
  69.  
  70. /**
  71. * Current Schema Version
  72. */
  73. if( !defined( 'XMLS_SCHEMA_VERSION' ) ) {
  74.     define( 'XMLS_SCHEMA_VERSION', '0.3' );
  75. }
  76.  
  77. /**
  78. * Default Schema Version.  Used for Schemas without an explicit version set.
  79. */
  80. if( !defined( 'XMLS_DEFAULT_SCHEMA_VERSION' ) ) {
  81.     define( 'XMLS_DEFAULT_SCHEMA_VERSION', '0.1' );
  82. }
  83.  
  84. /**
  85. * How to handle data rows that already exist in a database during and upgrade.
  86. * Options are INSERT (attempts to insert duplicate rows), UPDATE (updates existing
  87. * rows) and IGNORE (ignores existing rows).
  88. */
  89. if( !defined( 'XMLS_MODE_INSERT' ) ) {
  90.     define( 'XMLS_MODE_INSERT', 0 );
  91. }
  92. if( !defined( 'XMLS_MODE_UPDATE' ) ) {
  93.     define( 'XMLS_MODE_UPDATE', 1 );
  94. }
  95. if( !defined( 'XMLS_MODE_IGNORE' ) ) {
  96.     define( 'XMLS_MODE_IGNORE', 2 );
  97. }
  98. if( !defined( 'XMLS_EXISTING_DATA' ) ) {
  99.     define( 'XMLS_EXISTING_DATA', XMLS_MODE_INSERT );
  100. }
  101.  
  102. /**
  103. * Default Schema Version.  Used for Schemas without an explicit version set.
  104. */
  105. if( !defined( 'XMLS_DEFAULT_UPGRADE_METHOD' ) ) {
  106.     define( 'XMLS_DEFAULT_UPGRADE_METHOD', 'ALTER' );
  107. }
  108.  
  109. /**
  110. * Include the main ADODB library
  111. */
  112. if( !defined( '_ADODB_LAYER' ) ) {
  113.     require( 'adodb.inc.php' );
  114.     require( 'adodb-datadict.inc.php' );
  115. }
  116.  
  117. /**
  118. * Abstract DB Object. This class provides basic methods for database objects, such
  119. * as tables and indexes.
  120. *
  121. * @package axmls
  122. * @access private
  123. */
  124. class dbObject {
  125.     
  126.     /**
  127.     * var object Parent
  128.     */
  129.     var $parent;
  130.     
  131.     /**
  132.     * var string current element
  133.     */
  134.     var $currentElement;
  135.     
  136.     /**
  137.     * NOP
  138.     */
  139.     function dbObject( &$parent, $attributes = NULL ) {
  140.         $this->parent =& $parent;
  141.     }
  142.     
  143.     /**
  144.     * XML Callback to process start elements
  145.     *
  146.     * @access private
  147.     */
  148.     function _tag_open( &$parser, $tag, $attributes ) {
  149.         
  150.     }
  151.     
  152.     /**
  153.     * XML Callback to process CDATA elements
  154.     *
  155.     * @access private
  156.     */
  157.     function _tag_cdata( &$parser, $cdata ) {
  158.         
  159.     }
  160.     
  161.     /**
  162.     * XML Callback to process end elements
  163.     *
  164.     * @access private
  165.     */
  166.     function _tag_close( &$parser, $tag ) {
  167.         
  168.     }
  169.     
  170.     function create() {
  171.         return array();
  172.     }
  173.     
  174.     /**
  175.     * Destroys the object
  176.     */
  177.     function destroy() {
  178.         unset( $this );
  179.     }
  180.     
  181.     /**
  182.     * Checks whether the specified RDBMS is supported by the current
  183.     * database object or its ranking ancestor.
  184.     *
  185.     * @param string $platform RDBMS platform name (from ADODB platform list).
  186.     * @return boolean TRUE if RDBMS is supported; otherwise returns FALSE.
  187.     */
  188.     function supportedPlatform( $platform = NULL ) {
  189.         return is_object( $this->parent ) ? $this->parent->supportedPlatform( $platform ) : TRUE;
  190.     }
  191.     
  192.     /**
  193.     * Returns the prefix set by the ranking ancestor of the database object.
  194.     *
  195.     * @param string $name Prefix string.
  196.     * @return string Prefix.
  197.     */
  198.     function prefix( $name = '' ) {
  199.         return is_object( $this->parent ) ? $this->parent->prefix( $name ) : $name;
  200.     }
  201.     
  202.     /**
  203.     * Extracts a field ID from the specified field.
  204.     *
  205.     * @param string $field Field.
  206.     * @return string Field ID.
  207.     */
  208.     function FieldID( $field ) {
  209.         return strtoupper( preg_replace( '/^`(.+)`$/', '$1', $field ) );
  210.     }
  211. }
  212.  
  213. /**
  214. * Creates a table object in ADOdb's datadict format
  215. *
  216. * This class stores information about a database table. As charactaristics
  217. * of the table are loaded from the external source, methods and properties
  218. * of this class are used to build up the table description in ADOdb's
  219. * datadict format.
  220. *
  221. * @package axmls
  222. * @access private
  223. */
  224. class dbTable extends dbObject {
  225.     
  226.     /**
  227.     * @var string Table name
  228.     */
  229.     var $name;
  230.     
  231.     /**
  232.     * @var array Field specifier: Meta-information about each field
  233.     */
  234.     var $fields = array();
  235.     
  236.     /**
  237.     * @var array List of table indexes.
  238.     */
  239.     var $indexes = array();
  240.     
  241.     /**
  242.     * @var array Table options: Table-level options
  243.     */
  244.     var $opts = array();
  245.     
  246.     /**
  247.     * @var string Field index: Keeps track of which field is currently being processed
  248.     */
  249.     var $current_field;
  250.     
  251.     /**
  252.     * @var boolean Mark table for destruction
  253.     * @access private
  254.     */
  255.     var $drop_table;
  256.     
  257.     /**
  258.     * @var boolean Mark field for destruction (not yet implemented)
  259.     * @access private
  260.     */
  261.     var $drop_field = array();
  262.     
  263.     /**
  264.     * @var array Platform-specific options
  265.     * @access private
  266.     */
  267.     var $currentPlatform = true;
  268.     
  269.     
  270.     /**
  271.     * Iniitializes a new table object.
  272.     *
  273.     * @param string $prefix DB Object prefix
  274.     * @param array $attributes Array of table attributes.
  275.     */
  276.     function dbTable( &$parent, $attributes = NULL ) {
  277.         $this->parent =& $parent;
  278.         $this->name = $this->prefix($attributes['NAME']);
  279.     }
  280.     
  281.     /**
  282.     * XML Callback to process start elements. Elements currently 
  283.     * processed are: INDEX, DROP, FIELD, KEY, NOTNULL, AUTOINCREMENT & DEFAULT. 
  284.     *
  285.     * @access private
  286.     */
  287.     function _tag_open( &$parser, $tag, $attributes ) {
  288.         $this->currentElement = strtoupper( $tag );
  289.         
  290.         switch( $this->currentElement ) {
  291.             case 'INDEX':
  292.                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
  293.                     xml_set_object( $parser, $this->addIndex( $attributes ) );
  294.                 }
  295.                 break;
  296.             case 'DATA':
  297.                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
  298.                     xml_set_object( $parser, $this->addData( $attributes ) );
  299.                 }
  300.                 break;
  301.             case 'DROP':
  302.                 $this->drop();
  303.                 break;
  304.             case 'FIELD':
  305.                 // Add a field
  306.                 $fieldName = $attributes['NAME'];
  307.                 $fieldType = $attributes['TYPE'];
  308.                 $fieldSize = isset( $attributes['SIZE'] ) ? $attributes['SIZE'] : NULL;
  309.                 $fieldOpts = !empty( $attributes['OPTS'] ) ? $attributes['OPTS'] : NULL;
  310.                 
  311.                 $this->addField( $fieldName, $fieldType, $fieldSize, $fieldOpts );
  312.                 break;
  313.             case 'KEY':
  314.             case 'NOTNULL':
  315.             case 'AUTOINCREMENT':
  316.             case 'DEFDATE':
  317.             case 'DEFTIMESTAMP':
  318.             case 'UNSIGNED':
  319.                 // Add a field option
  320.                 $this->addFieldOpt( $this->current_field, $this->currentElement );
  321.                 break;
  322.             case 'DEFAULT':
  323.                 // Add a field option to the table object
  324.                 
  325.                 // Work around ADOdb datadict issue that misinterprets empty strings.
  326.                 if( $attributes['VALUE'] == '' ) {
  327.                     $attributes['VALUE'] = " '' ";
  328.                 }
  329.                 
  330.                 $this->addFieldOpt( $this->current_field, $this->currentElement, $attributes['VALUE'] );
  331.                 break;
  332.             case 'OPT':
  333.             case 'CONSTRAINT':
  334.                 // Accept platform-specific options
  335.                 $this->currentPlatform = ( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) );
  336.                 break;
  337.             default:
  338.                 // print_r( array( $tag, $attributes ) );
  339.         }
  340.     }
  341.     
  342.     /**
  343.     * XML Callback to process CDATA elements
  344.     *
  345.     * @access private
  346.     */
  347.     function _tag_cdata( &$parser, $cdata ) {
  348.         switch( $this->currentElement ) {
  349.             // Table/field constraint
  350.             case 'CONSTRAINT':
  351.                 if( isset( $this->current_field ) ) {
  352.                     $this->addFieldOpt( $this->current_field, $this->currentElement, $cdata );
  353.                 } else {
  354.                     $this->addTableOpt( $cdata );
  355.                 }
  356.                 break;
  357.             // Table/field option
  358.             case 'OPT':
  359.                 if( isset( $this->current_field ) ) {
  360.                     $this->addFieldOpt( $this->current_field, $cdata );
  361.                 } else {
  362.                 $this->addTableOpt( $cdata );
  363.                 }
  364.                 break;
  365.             default:
  366.                 
  367.         }
  368.     }
  369.     
  370.     /**
  371.     * XML Callback to process end elements
  372.     *
  373.     * @access private
  374.     */
  375.     function _tag_close( &$parser, $tag ) {
  376.         $this->currentElement = '';
  377.         
  378.         switch( strtoupper( $tag ) ) {
  379.             case 'TABLE':
  380.                 $this->parent->addSQL( $this->create( $this->parent ) );
  381.                 xml_set_object( $parser, $this->parent );
  382.                 $this->destroy();
  383.                 break;
  384.             case 'FIELD':
  385.                 unset($this->current_field);
  386.                 break;
  387.             case 'OPT':
  388.             case 'CONSTRAINT':
  389.                 $this->currentPlatform = true;
  390.                 break;
  391.             default:
  392.  
  393.         }
  394.     }
  395.     
  396.     /**
  397.     * Adds an index to a table object
  398.     *
  399.     * @param array $attributes Index attributes
  400.     * @return object dbIndex object
  401.     */
  402.     function &addIndex( $attributes ) {
  403.         $name = strtoupper( $attributes['NAME'] );
  404.         $this->indexes[$name] =& new dbIndex( $this, $attributes );
  405.         return $this->indexes[$name];
  406.     }
  407.     
  408.     /**
  409.     * Adds data to a table object
  410.     *
  411.     * @param array $attributes Data attributes
  412.     * @return object dbData object
  413.     */
  414.     function &addData( $attributes ) {
  415.         if( !isset( $this->data ) ) {
  416.             $this->data =& new dbData( $this, $attributes );
  417.         }
  418.         return $this->data;
  419.     }
  420.     
  421.     /**
  422.     * Adds a field to a table object
  423.     *
  424.     * $name is the name of the table to which the field should be added. 
  425.     * $type is an ADODB datadict field type. The following field types
  426.     * are supported as of ADODB 3.40:
  427.     *     - C:  varchar
  428.     *    - X:  CLOB (character large object) or largest varchar size
  429.     *       if CLOB is not supported
  430.     *    - C2: Multibyte varchar
  431.     *    - X2: Multibyte CLOB
  432.     *    - B:  BLOB (binary large object)
  433.     *    - D:  Date (some databases do not support this, and we return a datetime type)
  434.     *    - T:  Datetime or Timestamp
  435.     *    - L:  Integer field suitable for storing booleans (0 or 1)
  436.     *    - I:  Integer (mapped to I4)
  437.     *    - I1: 1-byte integer
  438.     *    - I2: 2-byte integer
  439.     *    - I4: 4-byte integer
  440.     *    - I8: 8-byte integer
  441.     *    - F:  Floating point number
  442.     *    - N:  Numeric or decimal number
  443.     *
  444.     * @param string $name Name of the table to which the field will be added.
  445.     * @param string $type    ADODB datadict field type.
  446.     * @param string $size    Field size
  447.     * @param array $opts    Field options array
  448.     * @return array Field specifier array
  449.     */
  450.     function addField( $name, $type, $size = NULL, $opts = NULL ) {
  451.         $field_id = $this->FieldID( $name );
  452.         
  453.         // Set the field index so we know where we are
  454.         $this->current_field = $field_id;
  455.         
  456.         // Set the field name (required)
  457.         $this->fields[$field_id]['NAME'] = $name;
  458.         
  459.         // Set the field type (required)
  460.         $this->fields[$field_id]['TYPE'] = $type;
  461.         
  462.         // Set the field size (optional)
  463.         if( isset( $size ) ) {
  464.             $this->fields[$field_id]['SIZE'] = $size;
  465.         }
  466.         
  467.         // Set the field options
  468.         if( isset( $opts ) ) {
  469.             $this->fields[$field_id]['OPTS'] = array($opts);
  470.         } else {
  471.             $this->fields[$field_id]['OPTS'] = array();
  472.         }
  473.     }
  474.     
  475.     /**
  476.     * Adds a field option to the current field specifier
  477.     *
  478.     * This method adds a field option allowed by the ADOdb datadict 
  479.     * and appends it to the given field.
  480.     *
  481.     * @param string $field    Field name
  482.     * @param string $opt ADOdb field option
  483.     * @param mixed $value Field option value
  484.     * @return array Field specifier array
  485.     */
  486.     function addFieldOpt( $field, $opt, $value = NULL ) {
  487.         if( $this->currentPlatform ) {
  488.         if( !isset( $value ) ) {
  489.             $this->fields[$this->FieldID( $field )]['OPTS'][] = $opt;
  490.         // Add the option and value
  491.         } else {
  492.             $this->fields[$this->FieldID( $field )]['OPTS'][] = array( $opt => $value );
  493.         }
  494.     }
  495.     }
  496.     
  497.     /**
  498.     * Adds an option to the table
  499.     *
  500.     * This method takes a comma-separated list of table-level options
  501.     * and appends them to the table object.
  502.     *
  503.     * @param string $opt Table option
  504.     * @return array Options
  505.     */
  506.     function addTableOpt( $opt ) {
  507.         if( $this->currentPlatform ) {
  508.         $this->opts[] = $opt;
  509.         }
  510.         return $this->opts;
  511.     }
  512.     
  513.     /**
  514.     * Generates the SQL that will create the table in the database
  515.     *
  516.     * @param object $xmls adoSchema object
  517.     * @return array Array containing table creation SQL
  518.     */
  519.     function create( &$xmls ) {
  520.         $sql = array();
  521.         
  522.         // drop any existing indexes
  523.         if( is_array( $legacy_indexes = $xmls->dict->MetaIndexes( $this->name ) ) ) {
  524.             foreach( $legacy_indexes as $index => $index_details ) {
  525.                 $sql[] = $xmls->dict->DropIndexSQL( $index, $this->name );
  526.             }
  527.         }
  528.         
  529.         // remove fields to be dropped from table object
  530.         foreach( $this->drop_field as $field ) {
  531.             unset( $this->fields[$field] );
  532.         }
  533.         
  534.         // if table exists
  535.         if( is_array( $legacy_fields = $xmls->dict->MetaColumns( $this->name ) ) ) {
  536.             // drop table
  537.             if( $this->drop_table ) {
  538.                 $sql[] = $xmls->dict->DropTableSQL( $this->name );
  539.                 
  540.                 return $sql;
  541.             }
  542.             
  543.             // drop any existing fields not in schema
  544.             foreach( $legacy_fields as $field_id => $field ) {
  545.                 if( !isset( $this->fields[$field_id] ) ) {
  546.                     $sql[] = $xmls->dict->DropColumnSQL( $this->name, $field->name );
  547.                 }
  548.             }
  549.         // if table doesn't exist
  550.         } else {
  551.             if( $this->drop_table ) {
  552.                 return $sql;
  553.             }
  554.             
  555.             $legacy_fields = array();
  556.         }
  557.         
  558.         // Loop through the field specifier array, building the associative array for the field options
  559.         $fldarray = array();
  560.         
  561.         foreach( $this->fields as $field_id => $finfo ) {
  562.             // Set an empty size if it isn't supplied
  563.             if( !isset( $finfo['SIZE'] ) ) {
  564.                 $finfo['SIZE'] = '';
  565.             }
  566.             
  567.             // Initialize the field array with the type and size
  568.             $fldarray[$field_id] = array(
  569.                 'NAME' => $finfo['NAME'],
  570.                 'TYPE' => $finfo['TYPE'],
  571.                 'SIZE' => $finfo['SIZE']
  572.             );
  573.             
  574.             // Loop through the options array and add the field options. 
  575.             if( isset( $finfo['OPTS'] ) ) {
  576.                 foreach( $finfo['OPTS'] as $opt ) {
  577.                     // Option has an argument.
  578.                     if( is_array( $opt ) ) {
  579.                         $key = key( $opt );
  580.                         $value = $opt[key( $opt )];
  581.                         @$fldarray[$field_id][$key] .= $value;
  582.                     // Option doesn't have arguments
  583.                     } else {
  584.                         $fldarray[$field_id][$opt] = $opt;
  585.                     }
  586.                 }
  587.             }
  588.         }
  589.         
  590.         if( empty( $legacy_fields ) ) {
  591.             // Create the new table
  592.             $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
  593.             logMsg( end( $sql ), 'Generated CreateTableSQL' );
  594.         } else {
  595.             // Upgrade an existing table
  596.             logMsg( "Upgrading {$this->name} using '{$xmls->upgrade}'" );
  597.             switch( $xmls->upgrade ) {
  598.                 // Use ChangeTableSQL
  599.                 case 'ALTER':
  600.                     logMsg( 'Generated ChangeTableSQL (ALTERing table)' );
  601.                     $sql[] = $xmls->dict->ChangeTableSQL( $this->name, $fldarray, $this->opts );
  602.                     break;
  603.                 case 'REPLACE':
  604.                     logMsg( 'Doing upgrade REPLACE (testing)' );
  605.                     $sql[] = $xmls->dict->DropTableSQL( $this->name );
  606.                     $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
  607.                     break;
  608.                 // ignore table
  609.                 default:
  610.                     return array();
  611.             }
  612.         }
  613.         
  614.         foreach( $this->indexes as $index ) {
  615.             $sql[] = $index->create( $xmls );
  616.         }
  617.         
  618.         if( isset( $this->data ) ) {
  619.             $sql[] = $this->data->create( $xmls );
  620.         }
  621.         
  622.         return $sql;
  623.     }
  624.     
  625.     /**
  626.     * Marks a field or table for destruction
  627.     */
  628.     function drop() {
  629.         if( isset( $this->current_field ) ) {
  630.             // Drop the current field
  631.             logMsg( "Dropping field '{$this->current_field}' from table '{$this->name}'" );
  632.             // $this->drop_field[$this->current_field] = $xmls->dict->DropColumnSQL( $this->name, $this->current_field );
  633.             $this->drop_field[$this->current_field] = $this->current_field;
  634.         } else {
  635.             // Drop the current table
  636.             logMsg( "Dropping table '{$this->name}'" );
  637.             // $this->drop_table = $xmls->dict->DropTableSQL( $this->name );
  638.             $this->drop_table = TRUE;
  639.         }
  640.     }
  641. }
  642.  
  643. /**
  644. * Creates an index object in ADOdb's datadict format
  645. *
  646. * This class stores information about a database index. As charactaristics
  647. * of the index are loaded from the external source, methods and properties
  648. * of this class are used to build up the index description in ADOdb's
  649. * datadict format.
  650. *
  651. * @package axmls
  652. * @access private
  653. */
  654. class dbIndex extends dbObject {
  655.     
  656.     /**
  657.     * @var string    Index name
  658.     */
  659.     var $name;
  660.     
  661.     /**
  662.     * @var array    Index options: Index-level options
  663.     */
  664.     var $opts = array();
  665.     
  666.     /**
  667.     * @var array    Indexed fields: Table columns included in this index
  668.     */
  669.     var $columns = array();
  670.     
  671.     /**
  672.     * @var boolean Mark index for destruction
  673.     * @access private
  674.     */
  675.     var $drop = FALSE;
  676.     
  677.     /**
  678.     * Initializes the new dbIndex object.
  679.     *
  680.     * @param object $parent Parent object
  681.     * @param array $attributes Attributes
  682.     *
  683.     * @internal
  684.     */
  685.     function dbIndex( &$parent, $attributes = NULL ) {
  686.         $this->parent =& $parent;
  687.         
  688.         $this->name = $this->prefix ($attributes['NAME']);
  689.     }
  690.     
  691.     /**
  692.     * XML Callback to process start elements
  693.     *
  694.     * Processes XML opening tags. 
  695.     * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH. 
  696.     *
  697.     * @access private
  698.     */
  699.     function _tag_open( &$parser, $tag, $attributes ) {
  700.         $this->currentElement = strtoupper( $tag );
  701.         
  702.         switch( $this->currentElement ) {
  703.             case 'DROP':
  704.                 $this->drop();
  705.                 break;
  706.             case 'CLUSTERED':
  707.             case 'BITMAP':
  708.             case 'UNIQUE':
  709.             case 'FULLTEXT':
  710.             case 'HASH':
  711.                 // Add index Option
  712.                 $this->addIndexOpt( $this->currentElement );
  713.                 break;
  714.             default:
  715.                 // print_r( array( $tag, $attributes ) );
  716.         }
  717.     }
  718.     
  719.     /**
  720.     * XML Callback to process CDATA elements
  721.     *
  722.     * Processes XML cdata.
  723.     *
  724.     * @access private
  725.     */
  726.     function _tag_cdata( &$parser, $cdata ) {
  727.         switch( $this->currentElement ) {
  728.             // Index field name
  729.             case 'COL':
  730.                 $this->addField( $cdata );
  731.                 break;
  732.             default:
  733.                 
  734.         }
  735.     }
  736.     
  737.     /**
  738.     * XML Callback to process end elements
  739.     *
  740.     * @access private
  741.     */
  742.     function _tag_close( &$parser, $tag ) {
  743.         $this->currentElement = '';
  744.         
  745.         switch( strtoupper( $tag ) ) {
  746.             case 'INDEX':
  747.                 xml_set_object( $parser, $this->parent );
  748.                 break;
  749.         }
  750.     }
  751.     
  752.     /**
  753.     * Adds a field to the index
  754.     *
  755.     * @param string $name Field name
  756.     * @return string Field list
  757.     */
  758.     function addField( $name ) {
  759.         $this->columns[$this->FieldID( $name )] = $name;
  760.         
  761.         // Return the field list
  762.         return $this->columns;
  763.     }
  764.     
  765.     /**
  766.     * Adds options to the index
  767.     *
  768.     * @param string $opt Comma-separated list of index options.
  769.     * @return string Option list
  770.     */
  771.     function addIndexOpt( $opt ) {
  772.         $this->opts[] = $opt;
  773.         
  774.         // Return the options list
  775.         return $this->opts;
  776.     }
  777.     
  778.     /**
  779.     * Generates the SQL that will create the index in the database
  780.     *
  781.     * @param object $xmls adoSchema object
  782.     * @return array Array containing index creation SQL
  783.     */
  784.     function create( &$xmls ) {
  785.         if( $this->drop ) {
  786.             return NULL;
  787.         }
  788.         
  789.         // eliminate any columns that aren't in the table
  790.         foreach( $this->columns as $id => $col ) {
  791.             if( !isset( $this->parent->fields[$id] ) ) {
  792.                 unset( $this->columns[$id] );
  793.             }
  794.         }
  795.         
  796.         return $xmls->dict->CreateIndexSQL( $this->name, $this->parent->name, $this->columns, $this->opts );
  797.     }
  798.     
  799.     /**
  800.     * Marks an index for destruction
  801.     */
  802.     function drop() {
  803.         $this->drop = TRUE;
  804.     }
  805. }
  806.  
  807. /**
  808. * Creates a data object in ADOdb's datadict format
  809. *
  810. * This class stores information about table data, and is called
  811. * when we need to load field data into a table.
  812. *
  813. * @package axmls
  814. * @access private
  815. */
  816. class dbData extends dbObject {
  817.     
  818.     var $data = array();
  819.     
  820.     var $row;
  821.     
  822.     /**
  823.     * Initializes the new dbData object.
  824.     *
  825.     * @param object $parent Parent object
  826.     * @param array $attributes Attributes
  827.     *
  828.     * @internal
  829.     */
  830.     function dbData( &$parent, $attributes = NULL ) {
  831.         $this->parent =& $parent;
  832.     }
  833.     
  834.     /**
  835.     * XML Callback to process start elements
  836.     *
  837.     * Processes XML opening tags. 
  838.     * Elements currently processed are: ROW and F (field). 
  839.     *
  840.     * @access private
  841.     */
  842.     function _tag_open( &$parser, $tag, $attributes ) {
  843.         $this->currentElement = strtoupper( $tag );
  844.         
  845.         switch( $this->currentElement ) {
  846.             case 'ROW':
  847.                 $this->row = count( $this->data );
  848.                 $this->data[$this->row] = array();
  849.                 break;
  850.             case 'F':
  851.                 $this->addField($attributes);
  852.             default:
  853.                 // print_r( array( $tag, $attributes ) );
  854.         }
  855.     }
  856.     
  857.     /**
  858.     * XML Callback to process CDATA elements
  859.     *
  860.     * Processes XML cdata.
  861.     *
  862.     * @access private
  863.     */
  864.     function _tag_cdata( &$parser, $cdata ) {
  865.         switch( $this->currentElement ) {
  866.             // Index field name
  867.             case 'F':
  868.                 $this->addData( $cdata );
  869.                 break;
  870.             default:
  871.                 
  872.         }
  873.     }
  874.     
  875.     /**
  876.     * XML Callback to process end elements
  877.     *
  878.     * @access private
  879.     */
  880.     function _tag_close( &$parser, $tag ) {
  881.         $this->currentElement = '';
  882.         
  883.         switch( strtoupper( $tag ) ) {
  884.             case 'DATA':
  885.                 xml_set_object( $parser, $this->parent );
  886.                 break;
  887.         }
  888.     }
  889.     
  890.     /**
  891.     * Adds a field to the insert
  892.     *
  893.     * @param string $name Field name
  894.     * @return string Field list
  895.     */
  896.     function addField( $attributes ) {
  897.         // check we're in a valid row
  898.         if( !isset( $this->row ) || !isset( $this->data[$this->row] ) ) {
  899.             return;
  900.         }
  901.         
  902.         // Set the field index so we know where we are
  903.         if( isset( $attributes['NAME'] ) ) {
  904.             $this->current_field = $this->FieldID( $attributes['NAME'] );
  905.         } else {
  906.             $this->current_field = count( $this->data[$this->row] );
  907.         }
  908.         
  909.         // initialise data
  910.         if( !isset( $this->data[$this->row][$this->current_field] ) ) {
  911.             $this->data[$this->row][$this->current_field] = '';
  912.         }
  913.     }
  914.     
  915.     /**
  916.     * Adds options to the index
  917.     *
  918.     * @param string $opt Comma-separated list of index options.
  919.     * @return string Option list
  920.     */
  921.     function addData( $cdata ) {
  922.         // check we're in a valid field
  923.         if ( isset( $this->data[$this->row][$this->current_field] ) ) {
  924.             // add data to field
  925.             $this->data[$this->row][$this->current_field] .= $cdata;
  926.         }
  927.     }
  928.     
  929.     /**
  930.     * Generates the SQL that will add/update the data in the database
  931.     *
  932.     * @param object $xmls adoSchema object
  933.     * @return array Array containing index creation SQL
  934.     */
  935.     function create( &$xmls ) {
  936.         $table = $xmls->dict->TableName($this->parent->name);
  937.         $table_field_count = count($this->parent->fields);
  938.         $tables = $xmls->db->MetaTables(); 
  939.         $sql = array();
  940.         
  941.         $ukeys = $xmls->db->MetaPrimaryKeys( $table );
  942.         if( !empty( $this->parent->indexes ) and !empty( $ukeys ) ) {
  943.             foreach( $this->parent->indexes as $indexObj ) {
  944.                 if( !in_array( $indexObj->name, $ukeys ) ) $ukeys[] = $indexObj->name;
  945.             }
  946.         }
  947.         
  948.         // eliminate any columns that aren't in the table
  949.         foreach( $this->data as $row ) {
  950.             $table_fields = $this->parent->fields;
  951.             $fields = array();
  952.             $rawfields = array(); // Need to keep some of the unprocessed data on hand.
  953.             
  954.             foreach( $row as $field_id => $field_data ) {
  955.                 if( !array_key_exists( $field_id, $table_fields ) ) {
  956.                     if( is_numeric( $field_id ) ) {
  957.                         $field_id = reset( array_keys( $table_fields ) );
  958.                     } else {
  959.                         continue;
  960.                     }
  961.                 }
  962.                 
  963.                 $name = $table_fields[$field_id]['NAME'];
  964.                 
  965.                 switch( $table_fields[$field_id]['TYPE'] ) {
  966.                     case 'I':
  967.                     case 'I1':
  968.                     case 'I2':
  969.                     case 'I4':
  970.                     case 'I8':
  971.                         $fields[$name] = intval($field_data);
  972.                         break;
  973.                     case 'C':
  974.                     case 'C2':
  975.                     case 'X':
  976.                     case 'X2':
  977.                     default:
  978.                         $fields[$name] = $xmls->db->qstr( $field_data );
  979.                         $rawfields[$name] = $field_data;
  980.                 }
  981.                 
  982.                 unset($table_fields[$field_id]);
  983.                 
  984.             }
  985.             
  986.             // check that at least 1 column is specified
  987.             if( empty( $fields ) ) {
  988.                 continue;
  989.             }
  990.             
  991.             // check that no required columns are missing
  992.             if( count( $fields ) < $table_field_count ) {
  993.                 foreach( $table_fields as $field ) {
  994.                     if( isset( $field['OPTS'] ) and ( in_array( 'NOTNULL', $field['OPTS'] ) || in_array( 'KEY', $field['OPTS'] ) ) && !in_array( 'AUTOINCREMENT', $field['OPTS'] ) ) {
  995.                             continue(2);
  996.                         }
  997.                 }
  998.             }
  999.             
  1000.             // The rest of this method deals with updating existing data records.
  1001.             
  1002.             if( !in_array( $table, $tables ) or ( $mode = $xmls->existingData() ) == XMLS_MODE_INSERT ) {
  1003.                 // Table doesn't yet exist, so it's safe to insert.
  1004.                 logMsg( "$table doesn't exist, inserting or mode is INSERT" );
  1005.             $sql[] = 'INSERT INTO '. $table .' ('. implode( ',', array_keys( $fields ) ) .') VALUES ('. implode( ',', $fields ) .')';
  1006.                 continue;
  1007.         }
  1008.         
  1009.             // Prepare to test for potential violations. Get primary keys and unique indexes
  1010.             $mfields = array_merge( $fields, $rawfields );
  1011.             $keyFields = array_intersect( $ukeys, array_keys( $mfields ) );
  1012.             
  1013.             if( empty( $ukeys ) or count( $keyFields ) == 0 ) {
  1014.                 // No unique keys in schema, so safe to insert
  1015.                 logMsg( "Either schema or data has no unique keys, so safe to insert" );
  1016.                 $sql[] = 'INSERT INTO '. $table .' ('. implode( ',', array_keys( $fields ) ) .') VALUES ('. implode( ',', $fields ) .')';
  1017.                 continue;
  1018.             }
  1019.             
  1020.             // Select record containing matching unique keys.
  1021.             $where = '';
  1022.             foreach( $ukeys as $key ) {
  1023.                 if( isset( $mfields[$key] ) and $mfields[$key] ) {
  1024.                     if( $where ) $where .= ' AND ';
  1025.                     $where .= $key . ' = ' . $xmls->db->qstr( $mfields[$key] );
  1026.                 }
  1027.             }
  1028.             $records = $xmls->db->Execute( 'SELECT * FROM ' . $table . ' WHERE ' . $where );
  1029.             switch( $records->RecordCount() ) {
  1030.                 case 0:
  1031.                     // No matching record, so safe to insert.
  1032.                     logMsg( "No matching records. Inserting new row with unique data" );
  1033.                     $sql[] = $xmls->db->GetInsertSQL( $records, $mfields );
  1034.                     break;
  1035.                 case 1:
  1036.                     // Exactly one matching record, so we can update if the mode permits.
  1037.                     logMsg( "One matching record..." );
  1038.                     if( $mode == XMLS_MODE_UPDATE ) {
  1039.                         logMsg( "...Updating existing row from unique data" );
  1040.                         $sql[] = $xmls->db->GetUpdateSQL( $records, $mfields );
  1041.                     }
  1042.                     break;
  1043.                 default:
  1044.                     // More than one matching record; the result is ambiguous, so we must ignore the row.
  1045.                     logMsg( "More than one matching record. Ignoring row." );
  1046.             }
  1047.         }
  1048.         return $sql;
  1049.     }
  1050. }
  1051.  
  1052. /**
  1053. * Creates the SQL to execute a list of provided SQL queries
  1054. *
  1055. * @package axmls
  1056. * @access private
  1057. */
  1058. class dbQuerySet extends dbObject {
  1059.     
  1060.     /**
  1061.     * @var array    List of SQL queries
  1062.     */
  1063.     var $queries = array();
  1064.     
  1065.     /**
  1066.     * @var string    String used to build of a query line by line
  1067.     */
  1068.     var $query;
  1069.     
  1070.     /**
  1071.     * @var string    Query prefix key
  1072.     */
  1073.     var $prefixKey = '';
  1074.     
  1075.     /**
  1076.     * @var boolean    Auto prefix enable (TRUE)
  1077.     */
  1078.     var $prefixMethod = 'AUTO';
  1079.     
  1080.     /**
  1081.     * Initializes the query set.
  1082.     *
  1083.     * @param object $parent Parent object
  1084.     * @param array $attributes Attributes
  1085.     */
  1086.     function dbQuerySet( &$parent, $attributes = NULL ) {
  1087.         $this->parent =& $parent;
  1088.             
  1089.         // Overrides the manual prefix key
  1090.         if( isset( $attributes['KEY'] ) ) {
  1091.             $this->prefixKey = $attributes['KEY'];
  1092.         }
  1093.         
  1094.         $prefixMethod = isset( $attributes['PREFIXMETHOD'] ) ? strtoupper( trim( $attributes['PREFIXMETHOD'] ) ) : '';
  1095.         
  1096.         // Enables or disables automatic prefix prepending
  1097.         switch( $prefixMethod ) {
  1098.             case 'AUTO':
  1099.                 $this->prefixMethod = 'AUTO';
  1100.                 break;
  1101.             case 'MANUAL':
  1102.                 $this->prefixMethod = 'MANUAL';
  1103.                 break;
  1104.             case 'NONE':
  1105.                 $this->prefixMethod = 'NONE';
  1106.                 break;
  1107.         }
  1108.     }
  1109.     
  1110.     /**
  1111.     * XML Callback to process start elements. Elements currently 
  1112.     * processed are: QUERY. 
  1113.     *
  1114.     * @access private
  1115.     */
  1116.     function _tag_open( &$parser, $tag, $attributes ) {
  1117.         $this->currentElement = strtoupper( $tag );
  1118.         
  1119.         switch( $this->currentElement ) {
  1120.             case 'QUERY':
  1121.                 // Create a new query in a SQL queryset.
  1122.                 // Ignore this query set if a platform is specified and it's different than the 
  1123.                 // current connection platform.
  1124.                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
  1125.                     $this->newQuery();
  1126.                 } else {
  1127.                     $this->discardQuery();
  1128.                 }
  1129.                 break;
  1130.             default:
  1131.                 // print_r( array( $tag, $attributes ) );
  1132.         }
  1133.     }
  1134.     
  1135.     /**
  1136.     * XML Callback to process CDATA elements
  1137.     */
  1138.     function _tag_cdata( &$parser, $cdata ) {
  1139.         switch( $this->currentElement ) {
  1140.             // Line of queryset SQL data
  1141.             case 'QUERY':
  1142.                 $this->buildQuery( $cdata );
  1143.                 break;
  1144.             default:
  1145.                 
  1146.         }
  1147.     }
  1148.     
  1149.     /**
  1150.     * XML Callback to process end elements
  1151.     *
  1152.     * @access private
  1153.     */
  1154.     function _tag_close( &$parser, $tag ) {
  1155.         $this->currentElement = '';
  1156.         
  1157.         switch( strtoupper( $tag ) ) {
  1158.             case 'QUERY':
  1159.                 // Add the finished query to the open query set.
  1160.                 $this->addQuery();
  1161.                 break;
  1162.             case 'SQL':
  1163.                 $this->parent->addSQL( $this->create( $this->parent ) );
  1164.                 xml_set_object( $parser, $this->parent );
  1165.                 $this->destroy();
  1166.                 break;
  1167.             default:
  1168.                 
  1169.         }
  1170.     }
  1171.     
  1172.     /**
  1173.     * Re-initializes the query.
  1174.     *
  1175.     * @return boolean TRUE
  1176.     */
  1177.     function newQuery() {
  1178.         $this->query = '';
  1179.         
  1180.         return TRUE;
  1181.     }
  1182.     
  1183.     /**
  1184.     * Discards the existing query.
  1185.     *
  1186.     * @return boolean TRUE
  1187.     */
  1188.     function discardQuery() {
  1189.         unset( $this->query );
  1190.         
  1191.         return TRUE;
  1192.     }
  1193.     
  1194.     /** 
  1195.     * Appends a line to a query that is being built line by line
  1196.     *
  1197.     * @param string $data Line of SQL data or NULL to initialize a new query
  1198.     * @return string SQL query string.
  1199.     */
  1200.     function buildQuery( $sql = NULL ) {
  1201.         if( !isset( $this->query ) OR empty( $sql ) ) {
  1202.             return FALSE;
  1203.         }
  1204.         
  1205.         $this->query .= $sql;
  1206.         
  1207.         return $this->query;
  1208.     }
  1209.     
  1210.     /**
  1211.     * Adds a completed query to the query list
  1212.     *
  1213.     * @return string    SQL of added query
  1214.     */
  1215.     function addQuery() {
  1216.         if( !isset( $this->query ) ) {
  1217.             return FALSE;
  1218.         }
  1219.         
  1220.         $this->queries[] = $return = trim($this->query);
  1221.         
  1222.         unset( $this->query );
  1223.         
  1224.         return $return;
  1225.     }
  1226.     
  1227.     /**
  1228.     * Creates and returns the current query set
  1229.     *
  1230.     * @param object $xmls adoSchema object
  1231.     * @return array Query set
  1232.     */
  1233.     function create( &$xmls ) {
  1234.         foreach( $this->queries as $id => $query ) {
  1235.             switch( $this->prefixMethod ) {
  1236.                 case 'AUTO':
  1237.                     // Enable auto prefix replacement
  1238.                     
  1239.                     // Process object prefix.
  1240.                     // Evaluate SQL statements to prepend prefix to objects
  1241.                     $query = $this->prefixQuery( '/^\s*((?is)INSERT\s+(INTO\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
  1242.                     $query = $this->prefixQuery( '/^\s*((?is)UPDATE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
  1243.                     $query = $this->prefixQuery( '/^\s*((?is)DELETE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
  1244.                     
  1245.                     // SELECT statements aren't working yet
  1246.                     #$data = preg_replace( '/(?ias)(^\s*SELECT\s+.*\s+FROM)\s+(\W\s*,?\s*)+((?i)\s+WHERE.*$)/', "\1 $prefix\2 \3", $data );
  1247.                     
  1248.                 case 'MANUAL':
  1249.                     // If prefixKey is set and has a value then we use it to override the default constant XMLS_PREFIX.
  1250.                     // If prefixKey is not set, we use the default constant XMLS_PREFIX
  1251.                     if( isset( $this->prefixKey ) AND( $this->prefixKey !== '' ) ) {
  1252.                         // Enable prefix override
  1253.                         $query = str_replace( $this->prefixKey, $xmls->objectPrefix, $query );
  1254.                     } else {
  1255.                         // Use default replacement
  1256.                         $query = str_replace( XMLS_PREFIX , $xmls->objectPrefix, $query );
  1257.                     }
  1258.             }
  1259.             
  1260.             $this->queries[$id] = trim( $query );
  1261.         }
  1262.         
  1263.         // Return the query set array
  1264.         return $this->queries;
  1265.     }
  1266.     
  1267.     /**
  1268.     * Rebuilds the query with the prefix attached to any objects
  1269.     *
  1270.     * @param string $regex Regex used to add prefix
  1271.     * @param string $query SQL query string
  1272.     * @param string $prefix Prefix to be appended to tables, indices, etc.
  1273.     * @return string Prefixed SQL query string.
  1274.     */
  1275.     function prefixQuery( $regex, $query, $prefix = NULL ) {
  1276.         if( !isset( $prefix ) ) {
  1277.             return $query;
  1278.         }
  1279.         
  1280.         if( preg_match( $regex, $query, $match ) ) {
  1281.             $preamble = $match[1];
  1282.             $postamble = $match[5];
  1283.             $objectList = explode( ',', $match[3] );
  1284.             // $prefix = $prefix . '_';
  1285.             
  1286.             $prefixedList = '';
  1287.             
  1288.             foreach( $objectList as $object ) {
  1289.                 if( $prefixedList !== '' ) {
  1290.                     $prefixedList .= ', ';
  1291.                 }
  1292.                 
  1293.                 $prefixedList .= $prefix . trim( $object );
  1294.             }
  1295.             
  1296.             $query = $preamble . ' ' . $prefixedList . ' ' . $postamble;
  1297.         }
  1298.         
  1299.         return $query;
  1300.     }
  1301. }
  1302.  
  1303. /**
  1304. * Loads and parses an XML file, creating an array of "ready-to-run" SQL statements
  1305. * This class is used to load and parse the XML file, to create an array of SQL statements
  1306. * that can be used to build a database, and to build the database using the SQL array.
  1307. *
  1308. * @tutorial getting_started.pkg
  1309. *
  1310. * @author Richard Tango-Lowy & Dan Cech
  1311. * @version $Revision: 1.62 $
  1312. *
  1313. * @package axmls
  1314. */
  1315. class adoSchema {
  1316.     
  1317.     /**
  1318.     * @var array    Array containing SQL queries to generate all objects
  1319.     * @access private
  1320.     */
  1321.     var $sqlArray;
  1322.     
  1323.     /**
  1324.     * @var object    ADOdb connection object
  1325.     * @access private
  1326.     */
  1327.     var $db;
  1328.     
  1329.     /**
  1330.     * @var object    ADOdb Data Dictionary
  1331.     * @access private
  1332.     */
  1333.     var $dict;
  1334.     
  1335.     /**
  1336.     * @var string Current XML element
  1337.     * @access private
  1338.     */
  1339.     var $currentElement = '';
  1340.     
  1341.     /**
  1342.     * @var string If set (to 'ALTER' or 'REPLACE'), upgrade an existing database
  1343.     * @access private
  1344.     */
  1345.     var $upgrade = '';
  1346.     
  1347.     /**
  1348.     * @var string Optional object prefix
  1349.     * @access private
  1350.     */
  1351.     var $objectPrefix = '';
  1352.     
  1353.     /**
  1354.     * @var long    Original Magic Quotes Runtime value
  1355.     * @access private
  1356.     */
  1357.     var $mgq;
  1358.     
  1359.     /**
  1360.     * @var long    System debug
  1361.     * @access private
  1362.     */
  1363.     var $debug;
  1364.     
  1365.     /**
  1366.     * @var string Regular expression to find schema version
  1367.     * @access private
  1368.     */
  1369.     var $versionRegex = '/<schema.*?( version="([^"]*)")?.*?>/';
  1370.     
  1371.     /**
  1372.     * @var string Current schema version
  1373.     * @access private
  1374.     */
  1375.     var $schemaVersion;
  1376.     
  1377.     /**
  1378.     * @var int    Success of last Schema execution
  1379.     */
  1380.     var $success;
  1381.     
  1382.     /**
  1383.     * @var bool    Execute SQL inline as it is generated
  1384.     */
  1385.     var $executeInline;
  1386.     
  1387.     /**
  1388.     * @var bool    Continue SQL execution if errors occur
  1389.     */
  1390.     var $continueOnError;
  1391.     
  1392.     /**
  1393.     * @var int    How to handle existing data rows (insert, update, or ignore)
  1394.     */
  1395.     var $existingData;
  1396.     
  1397.     /**
  1398.     * Creates an adoSchema object
  1399.     *
  1400.     * Creating an adoSchema object is the first step in processing an XML schema.
  1401.     * The only parameter is an ADOdb database connection object, which must already
  1402.     * have been created.
  1403.     *
  1404.     * @param object $db ADOdb database connection object.
  1405.     */
  1406.     function adoSchema( &$db ) {
  1407.         // Initialize the environment
  1408.         $this->mgq = get_magic_quotes_runtime();
  1409.         set_magic_quotes_runtime(0);
  1410.         
  1411.         $this->db =& $db;
  1412.         $this->debug = $this->db->debug;
  1413.         $this->dict = NewDataDictionary( $this->db );
  1414.         $this->sqlArray = array();
  1415.         $this->schemaVersion = XMLS_SCHEMA_VERSION;
  1416.         $this->executeInline( XMLS_EXECUTE_INLINE );
  1417.         $this->continueOnError( XMLS_CONTINUE_ON_ERROR );
  1418.         $this->existingData( XMLS_EXISTING_DATA );
  1419.         $this->setUpgradeMethod();
  1420.     }
  1421.     
  1422.     /**
  1423.     * Sets the method to be used for upgrading an existing database
  1424.     *
  1425.     * Use this method to specify how existing database objects should be upgraded.
  1426.     * The method option can be set to ALTER, REPLACE, BEST, or NONE. ALTER attempts to
  1427.     * alter each database object directly, REPLACE attempts to rebuild each object
  1428.     * from scratch, BEST attempts to determine the best upgrade method for each
  1429.     * object, and NONE disables upgrading.
  1430.     *
  1431.     * This method is not yet used by AXMLS, but exists for backward compatibility.
  1432.     * The ALTER method is automatically assumed when the adoSchema object is
  1433.     * instantiated; other upgrade methods are not currently supported.
  1434.     *
  1435.     * @param string $method Upgrade method (ALTER|REPLACE|BEST|NONE)
  1436.     * @returns string Upgrade method used
  1437.     */
  1438.     function SetUpgradeMethod( $method = '' ) {
  1439.         if( !is_string( $method ) ) {
  1440.             return FALSE;
  1441.         }
  1442.         
  1443.         $method = strtoupper( $method );
  1444.         
  1445.         // Handle the upgrade methods
  1446.         switch( $method ) {
  1447.             case 'ALTER':
  1448.                 $this->upgrade = $method;
  1449.                 break;
  1450.             case 'REPLACE':
  1451.                 $this->upgrade = $method;
  1452.                 break;
  1453.             case 'BEST':
  1454.                 $this->upgrade = 'ALTER';
  1455.                 break;
  1456.             case 'NONE':
  1457.                 $this->upgrade = 'NONE';
  1458.                 break;
  1459.             default:
  1460.                 // Use default if no legitimate method is passed.
  1461.                 $this->upgrade = XMLS_DEFAULT_UPGRADE_METHOD;
  1462.         }
  1463.         
  1464.         return $this->upgrade;
  1465.     }
  1466.     
  1467.     /**
  1468.     * Specifies how to handle existing data row when there is a unique key conflict.
  1469.     *
  1470.     * The existingData setting specifies how the parser should handle existing rows
  1471.     * when a unique key violation occurs during the insert. This can happen when inserting
  1472.     * data into an existing table with one or more primary keys or unique indexes.
  1473.     * The existingData method takes one of three options: XMLS_MODE_INSERT attempts
  1474.     * to always insert the data as a new row. In the event of a unique key violation,
  1475.     * the database will generate an error.  XMLS_MODE_UPDATE attempts to update the 
  1476.     * any existing rows with the new data based upon primary or unique key fields in
  1477.     * the schema. If the data row in the schema specifies no unique fields, the row
  1478.     * data will be inserted as a new row. XMLS_MODE_IGNORE specifies that any data rows
  1479.     * that would result in a unique key violation be ignored; no inserts or updates will
  1480.     * take place. For backward compatibility, the default setting is XMLS_MODE_INSERT,
  1481.     * but XMLS_MODE_UPDATE will generally be the most appropriate setting.
  1482.     *
  1483.     * @param int $mode XMLS_MODE_INSERT, XMLS_MODE_UPDATE, or XMLS_MODE_IGNORE
  1484.     * @return int current mode
  1485.     */
  1486.     function ExistingData( $mode = NULL ) {
  1487.         if( is_int( $mode ) ) {
  1488.             switch( $mode ) {
  1489.                 case XMLS_MODE_UPDATE:
  1490.                     $mode = XMLS_MODE_UPDATE;
  1491.                     break;
  1492.                 case XMLS_MODE_IGNORE:
  1493.                     $mode = XMLS_MODE_IGNORE;
  1494.                     break;
  1495.                 case XMLS_MODE_INSERT:
  1496.                     $mode = XMLS_MODE_INSERT;
  1497.                     break;
  1498.                 default:
  1499.                     $mode = XMLS_EXISITNG_DATA;
  1500.                     break;
  1501.             }
  1502.             $this->existingData = $mode;
  1503.         }
  1504.         
  1505.         return $this->existingData;
  1506.     }
  1507.     
  1508.     /**
  1509.     * Enables/disables inline SQL execution.
  1510.     *
  1511.     * Call this method to enable or disable inline execution of the schema. If the mode is set to TRUE (inline execution),
  1512.     * AXMLS applies the SQL to the database immediately as each schema entity is parsed. If the mode
  1513.     * is set to FALSE (post execution), AXMLS parses the entire schema and you will need to call adoSchema::ExecuteSchema()
  1514.     * to apply the schema to the database.
  1515.     *
  1516.     * @param bool $mode execute
  1517.     * @return bool current execution mode
  1518.     *
  1519.     * @see ParseSchema(), ExecuteSchema()
  1520.     */
  1521.     function ExecuteInline( $mode = NULL ) {
  1522.         if( is_bool( $mode ) ) {
  1523.             $this->executeInline = $mode;
  1524.         }
  1525.         
  1526.         return $this->executeInline;
  1527.     }
  1528.     
  1529.     /**
  1530.     * Enables/disables SQL continue on error.
  1531.     *
  1532.     * Call this method to enable or disable continuation of SQL execution if an error occurs.
  1533.     * If the mode is set to TRUE (continue), AXMLS will continue to apply SQL to the database, even if an error occurs.
  1534.     * If the mode is set to FALSE (halt), AXMLS will halt execution of generated sql if an error occurs, though parsing
  1535.     * of the schema will continue.
  1536.     *
  1537.     * @param bool $mode execute
  1538.     * @return bool current continueOnError mode
  1539.     *
  1540.     * @see addSQL(), ExecuteSchema()
  1541.     */
  1542.     function ContinueOnError( $mode = NULL ) {
  1543.         if( is_bool( $mode ) ) {
  1544.             $this->continueOnError = $mode;
  1545.         }
  1546.         
  1547.         return $this->continueOnError;
  1548.     }
  1549.     
  1550.     /**
  1551.     * Loads an XML schema from a file and converts it to SQL.
  1552.     *
  1553.     * Call this method to load the specified schema (see the DTD for the proper format) from
  1554.     * the filesystem and generate the SQL necessary to create the database
  1555.     * described. This method automatically converts the schema to the latest
  1556.     * axmls schema version.
  1557.     * @see ParseSchemaString()
  1558.     *
  1559.     * @param string $file Name of XML schema file.
  1560.     * @param bool $returnSchema Return schema rather than parsing.
  1561.     * @return array Array of SQL queries, ready to execute
  1562.     */
  1563.     function ParseSchema( $filename, $returnSchema = FALSE ) {
  1564.         return $this->ParseSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema );
  1565.     }
  1566.     
  1567.     /**
  1568.     * Loads an XML schema from a file and converts it to SQL.
  1569.     *
  1570.     * Call this method to load the specified schema directly from a file (see
  1571.     * the DTD for the proper format) and generate the SQL necessary to create
  1572.     * the database described by the schema. Use this method when you are dealing
  1573.     * with large schema files. Otherwise, ParseSchema() is faster.
  1574.     * This method does not automatically convert the schema to the latest axmls
  1575.     * schema version. You must convert the schema manually using either the
  1576.     * ConvertSchemaFile() or ConvertSchemaString() method.
  1577.     * @see ParseSchema()
  1578.     * @see ConvertSchemaFile()
  1579.     * @see ConvertSchemaString()
  1580.     *
  1581.     * @param string $file Name of XML schema file.
  1582.     * @param bool $returnSchema Return schema rather than parsing.
  1583.     * @return array Array of SQL queries, ready to execute.
  1584.     *
  1585.     * @deprecated Replaced by adoSchema::ParseSchema() and adoSchema::ParseSchemaString()
  1586.     * @see ParseSchema(), ParseSchemaString()
  1587.     */
  1588.     function ParseSchemaFile( $filename, $returnSchema = FALSE ) {
  1589.         // Open the file
  1590.         if( !($fp = fopen( $filename, 'r' )) ) {
  1591.             logMsg( 'Unable to open file' );
  1592.             return FALSE;
  1593.         }
  1594.         
  1595.         // do version detection here
  1596.         if( $this->SchemaFileVersion( $filename ) != $this->schemaVersion ) {
  1597.             logMsg( 'Invalid Schema Version' );
  1598.             return FALSE;
  1599.         }
  1600.         
  1601.         if( $returnSchema ) {
  1602.             $xmlstring = '';
  1603.             while( $data = fread( $fp, 4096 ) ) {
  1604.                 $xmlstring .= $data . "\n";
  1605.             }
  1606.             return $xmlstring;
  1607.         }
  1608.         
  1609.         $this->success = 2;
  1610.         
  1611.         $xmlParser = $this->create_parser();
  1612.         
  1613.         // Process the file
  1614.         while( $data = fread( $fp, 4096 ) ) {
  1615.             if( !xml_parse( $xmlParser, $data, feof( $fp ) ) ) {
  1616.                 die( sprintf(
  1617.                     "XML error: %s at line %d",
  1618.                     xml_error_string( xml_get_error_code( $xmlParser) ),
  1619.                     xml_get_current_line_number( $xmlParser)
  1620.                 ) );
  1621.             }
  1622.         }
  1623.         
  1624.         xml_parser_free( $xmlParser );
  1625.         
  1626.         return $this->sqlArray;
  1627.     }
  1628.     
  1629.     /**
  1630.     * Converts an XML schema string to SQL.
  1631.     *
  1632.     * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
  1633.     * and generate the SQL necessary to create the database described by the schema. 
  1634.     * @see ParseSchema()
  1635.     *
  1636.     * @param string $xmlstring XML schema string.
  1637.     * @param bool $returnSchema Return schema rather than parsing.
  1638.     * @return array Array of SQL queries, ready to execute.
  1639.     */
  1640.     function ParseSchemaString( $xmlstring, $returnSchema = FALSE ) {
  1641.         if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
  1642.             logMsg( 'Empty or Invalid Schema' );
  1643.             return FALSE;
  1644.         }
  1645.         
  1646.         // do version detection here
  1647.         if( $this->SchemaStringVersion( $xmlstring ) != $this->schemaVersion ) {
  1648.             logMsg( 'Invalid Schema Version' );
  1649.             return FALSE;
  1650.         }
  1651.         
  1652.         if( $returnSchema ) {
  1653.             return $xmlstring;
  1654.         }
  1655.         
  1656.         $this->success = 2;
  1657.         
  1658.         $xmlParser = $this->create_parser();
  1659.         
  1660.         if( !xml_parse( $xmlParser, $xmlstring, TRUE ) ) {
  1661.             die( sprintf(
  1662.                 "XML error: %s at line %d",
  1663.                 xml_error_string( xml_get_error_code( $xmlParser) ),
  1664.                 xml_get_current_line_number( $xmlParser)
  1665.             ) );
  1666.         }
  1667.         
  1668.         xml_parser_free( $xmlParser );
  1669.         
  1670.         return $this->sqlArray;
  1671.     }
  1672.     
  1673.     /**
  1674.     * Loads an XML schema from a file and converts it to uninstallation SQL.
  1675.     *
  1676.     * Call this method to load the specified schema (see the DTD for the proper format) from
  1677.     * the filesystem and generate the SQL necessary to remove the database described.
  1678.     * @see RemoveSchemaString()
  1679.     *
  1680.     * @param string $file Name of XML schema file.
  1681.     * @param bool $returnSchema Return schema rather than parsing.
  1682.     * @return array Array of SQL queries, ready to execute
  1683.     */
  1684.     function RemoveSchema( $filename, $returnSchema = FALSE ) {
  1685.         return $this->RemoveSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema );
  1686.     }
  1687.     
  1688.     /**
  1689.     * Converts an XML schema string to uninstallation SQL.
  1690.     *
  1691.     * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
  1692.     * and generate the SQL necessary to uninstall the database described by the schema. 
  1693.     * @see RemoveSchema()
  1694.     *
  1695.     * @param string $schema XML schema string.
  1696.     * @param bool $returnSchema Return schema rather than parsing.
  1697.     * @return array Array of SQL queries, ready to execute.
  1698.     */
  1699.     function RemoveSchemaString( $schema, $returnSchema = FALSE ) {
  1700.         
  1701.         // grab current version
  1702.         if( !( $version = $this->SchemaStringVersion( $schema ) ) ) {
  1703.             return FALSE;
  1704.         }
  1705.         
  1706.         return $this->ParseSchemaString( $this->TransformSchema( $schema, 'remove-' . $version), $returnSchema );
  1707.     }
  1708.     
  1709.     /**
  1710.     * Applies the current XML schema to the database (post execution).
  1711.     *
  1712.     * Call this method to apply the current schema (generally created by calling 
  1713.     * ParseSchema() or ParseSchemaString() ) to the database (creating the tables, indexes, 
  1714.     * and executing other SQL specified in the schema) after parsing.
  1715.     * @see ParseSchema(), ParseSchemaString(), ExecuteInline()
  1716.     *
  1717.     * @param array $sqlArray Array of SQL statements that will be applied rather than
  1718.     *        the current schema.
  1719.     * @param boolean $continueOnErr Continue to apply the schema even if an error occurs.
  1720.     * @returns integer 0 if failure, 1 if errors, 2 if successful.
  1721.     */
  1722.     function ExecuteSchema( $sqlArray = NULL, $continueOnErr =  NULL ) {
  1723.         if( !is_bool( $continueOnErr ) ) {
  1724.             $continueOnErr = $this->ContinueOnError();
  1725.         }
  1726.         
  1727.         if( !isset( $sqlArray ) ) {
  1728.             $sqlArray = $this->sqlArray;
  1729.         }
  1730.         
  1731.         if( !is_array( $sqlArray ) ) {
  1732.             $this->success = 0;
  1733.         } else {
  1734.             $this->success = $this->dict->ExecuteSQLArray( $sqlArray, $continueOnErr );
  1735.         }
  1736.         
  1737.         return $this->success;
  1738.     }
  1739.     
  1740.     /**
  1741.     * Returns the current SQL array. 
  1742.     *
  1743.     * Call this method to fetch the array of SQL queries resulting from 
  1744.     * ParseSchema() or ParseSchemaString(). 
  1745.     *
  1746.     * @param string $format Format: HTML, TEXT, or NONE (PHP array)
  1747.     * @return array Array of SQL statements or FALSE if an error occurs
  1748.     */
  1749.     function PrintSQL( $format = 'NONE' ) {
  1750.         $sqlArray = null;
  1751.         return $this->getSQL( $format, $sqlArray );
  1752.     }
  1753.     
  1754.     /**
  1755.     * Saves the current SQL array to the local filesystem as a list of SQL queries.
  1756.     *
  1757.     * Call this method to save the array of SQL queries (generally resulting from a
  1758.     * parsed XML schema) to the filesystem.
  1759.     *
  1760.     * @param string $filename Path and name where the file should be saved.
  1761.     * @return boolean TRUE if save is successful, else FALSE. 
  1762.     */
  1763.     function SaveSQL( $filename = './schema.sql' ) {
  1764.         
  1765.         if( !isset( $sqlArray ) ) {
  1766.             $sqlArray = $this->sqlArray;
  1767.         }
  1768.         if( !isset( $sqlArray ) ) {
  1769.             return FALSE;
  1770.         }
  1771.         
  1772.         $fp = fopen( $filename, "w" );
  1773.         
  1774.         foreach( $sqlArray as $key => $query ) {
  1775.             fwrite( $fp, $query . ";\n" );
  1776.         }
  1777.         fclose( $fp );
  1778.     }
  1779.     
  1780.     /**
  1781.     * Create an xml parser
  1782.     *
  1783.     * @return object PHP XML parser object
  1784.     *
  1785.     * @access private
  1786.     */
  1787.     function &create_parser() {
  1788.         // Create the parser
  1789.         $xmlParser = xml_parser_create();
  1790.         xml_set_object( $xmlParser, $this );
  1791.         
  1792.         // Initialize the XML callback functions
  1793.         xml_set_element_handler( $xmlParser, '_tag_open', '_tag_close' );
  1794.         xml_set_character_data_handler( $xmlParser, '_tag_cdata' );
  1795.         
  1796.         return $xmlParser;
  1797.     }
  1798.     
  1799.     /**
  1800.     * XML Callback to process start elements
  1801.     *
  1802.     * @access private
  1803.     */
  1804.     function _tag_open( &$parser, $tag, $attributes ) {
  1805.         switch( strtoupper( $tag ) ) {
  1806.             case 'TABLE':
  1807.                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
  1808.                 $this->obj = new dbTable( $this, $attributes );
  1809.                 xml_set_object( $parser, $this->obj );
  1810.                 }
  1811.                 break;
  1812.             case 'SQL':
  1813.                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
  1814.                     $this->obj = new dbQuerySet( $this, $attributes );
  1815.                     xml_set_object( $parser, $this->obj );
  1816.                 }
  1817.                 break;
  1818.             default:
  1819.                 // print_r( array( $tag, $attributes ) );
  1820.         }
  1821.         
  1822.     }
  1823.     
  1824.     /**
  1825.     * XML Callback to process CDATA elements
  1826.     *
  1827.     * @access private
  1828.     */
  1829.     function _tag_cdata( &$parser, $cdata ) {
  1830.     }
  1831.     
  1832.     /**
  1833.     * XML Callback to process end elements
  1834.     *
  1835.     * @access private
  1836.     * @internal
  1837.     */
  1838.     function _tag_close( &$parser, $tag ) {
  1839.         
  1840.     }
  1841.     
  1842.     /**
  1843.     * Converts an XML schema string to the specified DTD version.
  1844.     *
  1845.     * Call this method to convert a string containing an XML schema to a different AXMLS
  1846.     * DTD version. For instance, to convert a schema created for an pre-1.0 version for 
  1847.     * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version 
  1848.     * parameter is specified, the schema will be converted to the current DTD version. 
  1849.     * If the newFile parameter is provided, the converted schema will be written to the specified
  1850.     * file.
  1851.     * @see ConvertSchemaFile()
  1852.     *
  1853.     * @param string $schema String containing XML schema that will be converted.
  1854.     * @param string $newVersion DTD version to convert to.
  1855.     * @param string $newFile File name of (converted) output file.
  1856.     * @return string Converted XML schema or FALSE if an error occurs.
  1857.     */
  1858.     function ConvertSchemaString( $schema, $newVersion = NULL, $newFile = NULL ) {
  1859.         
  1860.         // grab current version
  1861.         if( !( $version = $this->SchemaStringVersion( $schema ) ) ) {
  1862.             return FALSE;
  1863.         }
  1864.         
  1865.         if( !isset ($newVersion) ) {
  1866.             $newVersion = $this->schemaVersion;
  1867.         }
  1868.         
  1869.         if( $version == $newVersion ) {
  1870.             $result = $schema;
  1871.         } else {
  1872.             $result = $this->TransformSchema( $schema, 'convert-' . $version . '-' . $newVersion);
  1873.         }
  1874.         
  1875.         if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) {
  1876.             fwrite( $fp, $result );
  1877.             fclose( $fp );
  1878.         }
  1879.         
  1880.         return $result;
  1881.     }
  1882.  
  1883.     /*
  1884.     // compat for pre-4.3 - jlim
  1885.     function _file_get_contents($path)
  1886.     {
  1887.         if (function_exists('file_get_contents')) return file_get_contents($path);
  1888.         return join('',file($path));
  1889.     }*/
  1890.     
  1891.     /**
  1892.     * Converts an XML schema file to the specified DTD version.
  1893.     *
  1894.     * Call this method to convert the specified XML schema file to a different AXMLS
  1895.     * DTD version. For instance, to convert a schema created for an pre-1.0 version for 
  1896.     * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version 
  1897.     * parameter is specified, the schema will be converted to the current DTD version. 
  1898.     * If the newFile parameter is provided, the converted schema will be written to the specified
  1899.     * file.
  1900.     * @see ConvertSchemaString()
  1901.     *
  1902.     * @param string $filename Name of XML schema file that will be converted.
  1903.     * @param string $newVersion DTD version to convert to.
  1904.     * @param string $newFile File name of (converted) output file.
  1905.     * @return string Converted XML schema or FALSE if an error occurs.
  1906.     */
  1907.     function ConvertSchemaFile( $filename, $newVersion = NULL, $newFile = NULL ) {
  1908.         
  1909.         // grab current version
  1910.         if( !( $version = $this->SchemaFileVersion( $filename ) ) ) {
  1911.             return FALSE;
  1912.         }
  1913.         
  1914.         if( !isset ($newVersion) ) {
  1915.             $newVersion = $this->schemaVersion;
  1916.         }
  1917.         
  1918.         if( $version == $newVersion ) {
  1919.             $result = _file_get_contents( $filename );
  1920.             
  1921.             // remove unicode BOM if present
  1922.             if( substr( $result, 0, 3 ) == sprintf( '%c%c%c', 239, 187, 191 ) ) {
  1923.                 $result = substr( $result, 3 );
  1924.             }
  1925.         } else {
  1926.             $result = $this->TransformSchema( $filename, 'convert-' . $version . '-' . $newVersion, 'file' );
  1927.         }
  1928.         
  1929.         if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) {
  1930.             fwrite( $fp, $result );
  1931.             fclose( $fp );
  1932.         }
  1933.         
  1934.         return $result;
  1935.     }
  1936.     
  1937.     function TransformSchema( $schema, $xsl, $schematype='string' )
  1938.     {
  1939.         // Fail if XSLT extension is not available
  1940.         if( ! function_exists( 'xslt_create' ) ) {
  1941.             return FALSE;
  1942.         }
  1943.         
  1944.         $xsl_file = dirname( __FILE__ ) . '/xsl/' . $xsl . '.xsl';
  1945.         
  1946.         // look for xsl
  1947.         if( !is_readable( $xsl_file ) ) {
  1948.             return FALSE;
  1949.         }
  1950.         
  1951.         switch( $schematype )
  1952.         {
  1953.             case 'file':
  1954.                 if( !is_readable( $schema ) ) {
  1955.                     return FALSE;
  1956.                 }
  1957.                 
  1958.                 $schema = _file_get_contents( $schema );
  1959.                 break;
  1960.             case 'string':
  1961.             default:
  1962.                 if( !is_string( $schema ) ) {
  1963.                     return FALSE;
  1964.                 }
  1965.         }
  1966.         
  1967.         $arguments = array (
  1968.             '/_xml' => $schema,
  1969.             '/_xsl' => _file_get_contents( $xsl_file )
  1970.         );
  1971.         
  1972.         // create an XSLT processor
  1973.         $xh = xslt_create ();
  1974.         
  1975.         // set error handler
  1976.         xslt_set_error_handler ($xh, array (&$this, 'xslt_error_handler'));
  1977.         
  1978.         // process the schema
  1979.         $result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments); 
  1980.         
  1981.         xslt_free ($xh);
  1982.         
  1983.         return $result;
  1984.     }
  1985.     
  1986.     /**
  1987.     * Processes XSLT transformation errors
  1988.     *
  1989.     * @param object $parser XML parser object
  1990.     * @param integer $errno Error number
  1991.     * @param integer $level Error level
  1992.     * @param array $fields Error information fields
  1993.     *
  1994.     * @access private
  1995.     */
  1996.     function xslt_error_handler( $parser, $errno, $level, $fields ) {
  1997.         if( is_array( $fields ) ) {
  1998.             $msg = array(
  1999.                 'Message Type' => ucfirst( $fields['msgtype'] ),
  2000.                 'Message Code' => $fields['code'],
  2001.                 'Message' => $fields['msg'],
  2002.                 'Error Number' => $errno,
  2003.                 'Level' => $level
  2004.             );
  2005.             
  2006.             switch( $fields['URI'] ) {
  2007.                 case 'arg:/_xml':
  2008.                     $msg['Input'] = 'XML';
  2009.                     break;
  2010.                 case 'arg:/_xsl':
  2011.                     $msg['Input'] = 'XSL';
  2012.                     break;
  2013.                 default:
  2014.                     $msg['Input'] = $fields['URI'];
  2015.             }
  2016.             
  2017.             $msg['Line'] = $fields['line'];
  2018.         } else {
  2019.             $msg = array(
  2020.                 'Message Type' => 'Error',
  2021.                 'Error Number' => $errno,
  2022.                 'Level' => $level,
  2023.                 'Fields' => var_export( $fields, TRUE )
  2024.             );
  2025.         }
  2026.         
  2027.         $error_details = $msg['Message Type'] . ' in XSLT Transformation' . "\n"
  2028.                        . '<table>' . "\n";
  2029.         
  2030.         foreach( $msg as $label => $details ) {
  2031.             $error_details .= '<tr><td><b>' . $label . ': </b></td><td>' . htmlentities( $details ) . '</td></tr>' . "\n";
  2032.         }
  2033.         
  2034.         $error_details .= '</table>';
  2035.         
  2036.         trigger_error( $error_details, E_USER_ERROR );
  2037.     }
  2038.     
  2039.     /**
  2040.     * Returns the AXMLS Schema Version of the requested XML schema file.
  2041.     *
  2042.     * Call this method to obtain the AXMLS DTD version of the requested XML schema file.
  2043.     * @see SchemaStringVersion()
  2044.     *
  2045.     * @param string $filename AXMLS schema file
  2046.     * @return string Schema version number or FALSE on error
  2047.     */
  2048.     function SchemaFileVersion( $filename ) {
  2049.         // Open the file
  2050.         if( !($fp = fopen( $filename, 'r' )) ) {
  2051.             // die( 'Unable to open file' );
  2052.             return FALSE;
  2053.         }
  2054.         
  2055.         // Process the file
  2056.         while( $data = fread( $fp, 4096 ) ) {
  2057.             if( preg_match( $this->versionRegex, $data, $matches ) ) {
  2058.                 return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION;
  2059.             }
  2060.         }
  2061.         
  2062.         return FALSE;
  2063.     }
  2064.     
  2065.     /**
  2066.     * Returns the AXMLS Schema Version of the provided XML schema string.
  2067.     *
  2068.     * Call this method to obtain the AXMLS DTD version of the provided XML schema string.
  2069.     * @see SchemaFileVersion()
  2070.     *
  2071.     * @param string $xmlstring XML schema string
  2072.     * @return string Schema version number or FALSE on error
  2073.     */
  2074.     function SchemaStringVersion( $xmlstring ) {
  2075.         if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
  2076.             return FALSE;
  2077.         }
  2078.         
  2079.         if( preg_match( $this->versionRegex, $xmlstring, $matches ) ) {
  2080.             return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION;
  2081.         }
  2082.         
  2083.         return FALSE;
  2084.     }
  2085.     
  2086.     /**
  2087.     * Extracts an XML schema from an existing database.
  2088.     *
  2089.     * Call this method to create an XML schema string from an existing database.
  2090.     * If the data parameter is set to TRUE, AXMLS will include the data from the database
  2091.     * in the schema. 
  2092.     *
  2093.     * @param boolean $data Include data in schema dump
  2094.     * @indent string indentation to use
  2095.     * @prefix string extract only tables with given prefix
  2096.     * @stripprefix strip prefix string when storing in XML schema
  2097.     * @return string Generated XML schema
  2098.     */
  2099.     function ExtractSchema( $data = FALSE, $indent = '  ', $prefix = '' , $stripprefix=false) {
  2100.         $old_mode = $this->db->SetFetchMode( ADODB_FETCH_NUM );
  2101.         
  2102.         $schema = '<?xml version="1.0"?>' . "\n"
  2103.                 . '<schema version="' . $this->schemaVersion . '">' . "\n";
  2104.         
  2105.         if( is_array( $tables = $this->db->MetaTables( 'TABLES' , ($prefix) ? $prefix.'%' : '') ) ) {
  2106.             foreach( $tables as $table ) {
  2107.                 if ($stripprefix) $table = str_replace(str_replace('\\_', '_', $pfx ), '', $table);
  2108.                 $schema .= $indent . '<table name="' . htmlentities( $table ) . '">' . "\n";
  2109.                 
  2110.                 // grab details from database
  2111.                 $rs = $this->db->Execute( 'SELECT * FROM ' . $table . ' WHERE -1' );
  2112.                 $fields = $this->db->MetaColumns( $table );
  2113.                 $indexes = $this->db->MetaIndexes( $table );
  2114.                 
  2115.                 if( is_array( $fields ) ) {
  2116.                     foreach( $fields as $details ) {
  2117.                         $extra = '';
  2118.                         $content = array();
  2119.                         
  2120.                         if( isset($details->max_length) && $details->max_length > 0 ) {
  2121.                             $extra .= ' size="' . $details->max_length . '"';
  2122.                         }
  2123.                         
  2124.                         if( isset($details->primary_key) && $details->primary_key ) {
  2125.                             $content[] = '<KEY/>';
  2126.                         } elseif( isset($details->not_null) && $details->not_null ) {
  2127.                             $content[] = '<NOTNULL/>';
  2128.                         }
  2129.                         
  2130.                         if( isset($details->has_default) && $details->has_default ) {
  2131.                             $content[] = '<DEFAULT value="' . htmlentities( $details->default_value ) . '"/>';
  2132.                         }
  2133.                         
  2134.                         if( isset($details->auto_increment) && $details->auto_increment ) {
  2135.                             $content[] = '<AUTOINCREMENT/>';
  2136.                         }
  2137.                         
  2138.                         if( isset($details->unsigned) && $details->unsigned ) {
  2139.                             $content[] = '<UNSIGNED/>';
  2140.                         }
  2141.                         
  2142.                         // this stops the creation of 'R' columns,
  2143.                         // AUTOINCREMENT is used to create auto columns
  2144.                         $details->primary_key = 0;
  2145.                         $type = $rs->MetaType( $details );
  2146.                         
  2147.                         $schema .= str_repeat( $indent, 2 ) . '<field name="' . htmlentities( $details->name ) . '" type="' . $type . '"' . $extra;
  2148.                         
  2149.                         if( !empty( $content ) ) {
  2150.                             $schema .= ">\n" . str_repeat( $indent, 3 )
  2151.                                      . implode( "\n" . str_repeat( $indent, 3 ), $content ) . "\n"
  2152.                                      . str_repeat( $indent, 2 ) . '</field>' . "\n";
  2153.                         } else {
  2154.                             $schema .= "/>\n";
  2155.                         }
  2156.                     }
  2157.                 }
  2158.                 
  2159.                 if( is_array( $indexes ) ) {
  2160.                     foreach( $indexes as $index => $details ) {
  2161.                         $schema .= str_repeat( $indent, 2 ) . '<index name="' . $index . '">' . "\n";
  2162.                         
  2163.                         if( $details['unique'] ) {
  2164.                             $schema .= str_repeat( $indent, 3 ) . '<UNIQUE/>' . "\n";
  2165.                         }
  2166.                         
  2167.                         foreach( $details['columns'] as $column ) {
  2168.                             $schema .= str_repeat( $indent, 3 ) . '<col>' . htmlentities( $column ) . '</col>' . "\n";
  2169.                         }
  2170.                         
  2171.                         $schema .= str_repeat( $indent, 2 ) . '</index>' . "\n";
  2172.                     }
  2173.                 }
  2174.                 
  2175.                 if( $data ) {
  2176.                     $rs = $this->db->Execute( 'SELECT * FROM ' . $table );
  2177.                     
  2178.                     if( is_object( $rs ) && !$rs->EOF ) {
  2179.                         $schema .= str_repeat( $indent, 2 ) . "<data>\n";
  2180.                         
  2181.                         while( $row = $rs->FetchRow() ) {
  2182.                             foreach( $row as $key => $val ) {
  2183.                                 if ( $val != htmlentities( $val ) ) {
  2184.                                     $row[$key] = '<![CDATA[' . $val . ']]>';
  2185.                                 }
  2186.                             }
  2187.                             
  2188.                             $schema .= str_repeat( $indent, 3 ) . '<row><f>' . implode( '</f><f>', $row ) . "</f></row>\n";
  2189.                         }
  2190.                         
  2191.                         $schema .= str_repeat( $indent, 2 ) . "</data>\n";
  2192.                     }
  2193.                 }
  2194.                 
  2195.                 $schema .= $indent . "</table>\n";
  2196.             }
  2197.         }
  2198.         
  2199.         $this->db->SetFetchMode( $old_mode );
  2200.         
  2201.         $schema .= '</schema>';
  2202.         return $schema;
  2203.     }
  2204.     
  2205.     /**
  2206.     * Sets a prefix for database objects
  2207.     *
  2208.     * Call this method to set a standard prefix that will be prepended to all database tables 
  2209.     * and indices when the schema is parsed. Calling setPrefix with no arguments clears the prefix.
  2210.     *
  2211.     * @param string $prefix Prefix that will be prepended.
  2212.     * @param boolean $underscore If TRUE, automatically append an underscore character to the prefix.
  2213.     * @return boolean TRUE if successful, else FALSE
  2214.     */
  2215.     function SetPrefix( $prefix = '', $underscore = TRUE ) {
  2216.         switch( TRUE ) {
  2217.             // clear prefix
  2218.             case empty( $prefix ):
  2219.                 logMsg( 'Cleared prefix' );
  2220.                 $this->objectPrefix = '';
  2221.                 return TRUE;
  2222.             // prefix too long
  2223.             case strlen( $prefix ) > XMLS_PREFIX_MAXLEN:
  2224.             // prefix contains invalid characters
  2225.             case !preg_match( '/^[a-z][a-z0-9_]+$/i', $prefix ):
  2226.                 logMsg( 'Invalid prefix: ' . $prefix );
  2227.                 return FALSE;
  2228.         }
  2229.         
  2230.         if( $underscore AND substr( $prefix, -1 ) != '_' ) {
  2231.             $prefix .= '_';
  2232.         }
  2233.         
  2234.         // prefix valid
  2235.         logMsg( 'Set prefix: ' . $prefix );
  2236.         $this->objectPrefix = $prefix;
  2237.         return TRUE;
  2238.     }
  2239.     
  2240.     /**
  2241.     * Returns an object name with the current prefix prepended.
  2242.     *
  2243.     * @param string    $name Name
  2244.     * @return string    Prefixed name
  2245.     *
  2246.     * @access private
  2247.     */
  2248.     function prefix( $name = '' ) {
  2249.         // if prefix is set
  2250.         if( !empty( $this->objectPrefix ) ) {
  2251.             // Prepend the object prefix to the table name
  2252.             // prepend after quote if used
  2253.             return preg_replace( '/^(`?)(.+)$/', '$1' . $this->objectPrefix . '$2', $name );
  2254.         }
  2255.         
  2256.         // No prefix set. Use name provided.
  2257.         return $name;
  2258.     }
  2259.     
  2260.     /**
  2261.     * Checks if element references a specific platform
  2262.     *
  2263.     * @param string $platform Requested platform
  2264.     * @returns boolean TRUE if platform check succeeds
  2265.     *
  2266.     * @access private
  2267.     */
  2268.     function supportedPlatform( $platform = NULL ) {
  2269.         if( !empty( $platform ) ) {
  2270.             $regex = '/(^|\|)' . $this->db->databaseType . '(\||$)/i';
  2271.         
  2272.             if( preg_match( '/^- /', $platform ) ) {
  2273.                 if (preg_match ( $regex, substr( $platform, 2 ) ) ) {
  2274.                     logMsg( 'Platform ' . $platform . ' is NOT supported' );
  2275.                     return FALSE;
  2276.                 }
  2277.         } else {
  2278.                 if( !preg_match ( $regex, $platform ) ) {
  2279.                     logMsg( 'Platform ' . $platform . ' is NOT supported' );
  2280.             return FALSE;
  2281.         }
  2282.     }
  2283.         }
  2284.         
  2285.         logMsg( 'Platform ' . $platform . ' is supported' );
  2286.         return TRUE;
  2287.     }
  2288.     
  2289.     /**
  2290.     * Clears the array of generated SQL.
  2291.     *
  2292.     * @access private
  2293.     */
  2294.     function clearSQL() {
  2295.         $this->sqlArray = array();
  2296.     }
  2297.     
  2298.     /**
  2299.     * Adds SQL into the SQL array.
  2300.     *
  2301.     * @param mixed $sql SQL to Add
  2302.     * @return boolean TRUE if successful, else FALSE.
  2303.     *
  2304.     * @access private
  2305.     */    
  2306.     function addSQL( $sql = NULL ) {
  2307.         if( is_array( $sql ) ) {
  2308.             foreach( $sql as $line ) {
  2309.                 $this->addSQL( $line );
  2310.             }
  2311.             
  2312.             return TRUE;
  2313.         }
  2314.         
  2315.         if( is_string( $sql ) ) {
  2316.             $this->sqlArray[] = $sql;
  2317.             
  2318.             // if executeInline is enabled, and either no errors have occurred or continueOnError is enabled, execute SQL.
  2319.             if( $this->ExecuteInline() && ( $this->success == 2 || $this->ContinueOnError() ) ) {
  2320.                 $saved = $this->db->debug;
  2321.                 $this->db->debug = $this->debug;
  2322.                 $ok = $this->db->Execute( $sql );
  2323.                 $this->db->debug = $saved;
  2324.                 
  2325.                 if( !$ok ) {
  2326.                     if( $this->debug ) {
  2327.                         ADOConnection::outp( $this->db->ErrorMsg() );
  2328.                     }
  2329.                     
  2330.                     $this->success = 1;
  2331.                 }
  2332.             }
  2333.             
  2334.             return TRUE;
  2335.         }
  2336.         
  2337.         return FALSE;
  2338.     }
  2339.     
  2340.     /**
  2341.     * Gets the SQL array in the specified format.
  2342.     *
  2343.     * @param string $format Format
  2344.     * @return mixed SQL
  2345.     *    
  2346.     * @access private
  2347.     */
  2348.     function getSQL( $format = NULL, $sqlArray = NULL ) {
  2349.         if( !is_array( $sqlArray ) ) {
  2350.             $sqlArray = $this->sqlArray;
  2351.         }
  2352.         
  2353.         if( !is_array( $sqlArray ) ) {
  2354.             return FALSE;
  2355.         }
  2356.         
  2357.         switch( strtolower( $format ) ) {
  2358.             case 'string':
  2359.             case 'text':
  2360.                 return !empty( $sqlArray ) ? implode( ";\n\n", $sqlArray ) . ';' : '';
  2361.             case'html':
  2362.                 return !empty( $sqlArray ) ? nl2br( htmlentities( implode( ";\n\n", $sqlArray ) . ';' ) ) : '';
  2363.         }
  2364.         
  2365.         return $this->sqlArray;
  2366.     }
  2367.     
  2368.     /**
  2369.     * Destroys an adoSchema object.
  2370.     *
  2371.     * Call this method to clean up after an adoSchema object that is no longer in use.
  2372.     * @deprecated adoSchema now cleans up automatically.
  2373.     */
  2374.     function Destroy() {
  2375.         set_magic_quotes_runtime( $this->mgq );
  2376.         unset( $this );
  2377.     }
  2378. }
  2379.  
  2380. /**
  2381. * Message logging function
  2382. *
  2383. * @access private
  2384. */
  2385. function logMsg( $msg, $title = NULL, $force = FALSE ) {
  2386.     if( XMLS_DEBUG or $force ) {
  2387.         echo '<pre>';
  2388.         
  2389.         if( isset( $title ) ) {
  2390.             echo '<h3>' . htmlentities( $title ) . '</h3>';
  2391.         }
  2392.         
  2393.         if( @is_object( $this ) ) {
  2394.             echo '[' . get_class( $this ) . '] ';
  2395.         }
  2396.         
  2397.         print_r( $msg );
  2398.         
  2399.         echo '</pre>';
  2400.     }
  2401. }
  2402. ?>