<?php
/*
* $Id: XmlToAppData.php 536 2007-01-10 14:30:38Z heltem $
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information please see
* <http://propel.phpdb.org>.
*/
include_once 'propel/engine/database/model/AppData.php';
// Phing dependencies
require_once 'phing/parser/AbstractHandler.php';
include_once 'phing/system/io/FileReader.php';
/**
* A class that is used to parse an input xml schema file and creates an AppData
* PHP object.
*
* @author Hans Lellelid <hide@address.com> (Propel)
* @author Leon Messerschmidt <hide@address.com> (Torque)
* @author Jason van Zyl <hide@address.com> (Torque)
* @author Martin Poeschl <hide@address.com> (Torque)
* @author Daniel Rall <hide@address.com> (Torque)
* @version $Revision: 536 $
* @package propel.engine.database.transform
*/
class XmlToAppData extends AbstractHandler {
/** enables debug output */
const DEBUG = false;
private $app;
private $platform;
private $currDB;
private $currTable;
private $currColumn;
private $currFK;
private $currIndex;
private $currUnique;
private $currValidator;
private $currVendorObject;
private $isForReferenceOnly;
private $currentPackage;
private $currentXmlFile;
private $defaultPackage;
private $encoding;
/** two-dimensional array,
first dimension is for schemas(key is the path to the schema file),
second is for tags within the schema */
private $schemasTagsStack = array();
public $parser;
/**
* Creates a new instance for the specified database type.
*
* @param Platform $platform The type of database for the application.
* @param string $defaultPackage the default PHP package used for the om
* @param string $encoding The database encoding.
*/
public function __construct(Platform $platform, $defaultPackage, $encoding = 'iso-8859-1')
{
$this->app = new AppData($platform);
$this->platform = $platform;
$this->defaultPackage = $defaultPackage;
$this->firstPass = true;
$this->encoding = $encoding;
}
/**
* Parses a XML input file and returns a newly created and
* populated AppData structure.
*
* @param string $xmlFile The input file to parse.
* @return AppData populated by <code>xmlFile</code>.
*/
public function parseFile($xmlFile)
{
// we don't want infinite recursion
if($this->isAlreadyParsed($xmlFile)) {
return;
}
$domDocument = new DomDocument('1.0', 'UTF-8');
$domDocument->load($xmlFile);
// store current schema file path
$this->schemasTagsStack[$xmlFile] = array();
$this->currentXmlFile = $xmlFile;
try {
$fr = new FileReader($xmlFile);
} catch (Exception $e) {
$f = new PhingFile($xmlFile);
throw new Exception("XML File not found: " . $f->getAbsolutePath());
}
$br = new BufferedReader($fr);
$this->parser = new ExpatParser($br);
$this->parser->parserSetOption(XML_OPTION_CASE_FOLDING, 0);
$this->parser->setHandler($this);
try {
$this->parser->parse();
} catch (Exception $e) {
$br->close();
throw $e;
}
$br->close();
array_pop($this->schemasTagsStack);
return $this->app;
}
/**
* Handles opening elements of the xml file.
*
* @param string $uri
* @param string $localName The local name (without prefix), or the empty string if
* Namespace processing is not being performed.
* @param string $rawName The qualified name (with prefix), or the empty string if
* qualified names are not available.
* @param string $attributes The specified or defaulted attributes
*/
public function startElement($name, $attributes) {
try {
$parentTag = $this->peekCurrentSchemaTag();
if ($parentTag === false) {
switch($name) {
case "database":
if ($this->isExternalSchema()) {
$this->currentPackage = @$attributes["package"];
if ($this->currentPackage === null) {
$this->currentPackage = $this->defaultPackage;
}
} else {
$this->currDB = $this->app->addDatabase($attributes);
}
break;
default:
$this->_throwInvalidTagException($name);
}
} elseif ($parentTag == "database") {
switch($name) {
case "external-schema":
$xmlFile = @$attributes["filename"];
//"referenceOnly" attribute is valid in the main schema XML file only,
//and it's ingnored in the nested external-schemas
if(!$this->isExternalSchema()) {
$isForRefOnly = @$attributes["referenceOnly"];
$this->isForReferenceOnly = ($isForRefOnly !== null ? (strtolower($isForRefOnly) === "true") : true); // defaults to TRUE
}
if ($xmlFile{0} != '/') {
$f = new PhingFile($this->currentXmlFile);
$xf = new PhingFile($f->getParent(), $xmlFile);
$xmlFile = $xf->getPath();
}
$this->parseFile($xmlFile);
break;
case "domain":
$this->currDB->addDomain($attributes);
break;
case "table":
$this->currTable = $this->currDB->addTable($attributes);
if ($this->isExternalSchema()) {
$this->currTable->setForReferenceOnly($this->isForReferenceOnly);
$this->currTable->setPackage($this->currentPackage);
}
break;
case "vendor":
$this->currVendorObject = new ObjectWithVendorSpecificData($this->currDB, $attributes['type']);
break;
default:
$this->_throwInvalidTagException($name);
}
} elseif ($parentTag == "table") {
switch($name) {
case "column":
$this->currColumn = $this->currTable->addColumn($attributes);
break;
case "foreign-key":
$this->currFK = $this->currTable->addForeignKey($attributes);
break;
case "index":
$this->currIndex = $this->currTable->addIndex($attributes);
break;
case "unique":
$this->currUnique = $this->currTable->addUnique($attributes);
break;
case "vendor":
$this->currVendorObject = new ObjectWithVendorSpecificData($this->currTable, $attributes['type']);
break;
case "validator":
$this->currValidator = $this->currTable->addValidator($attributes);
break;
case "id-method-parameter":
$this->currTable->addIdMethodParameter($attributes);
break;
default:
$this->_throwInvalidTagException($name);
}
} elseif ($parentTag == "column") {
switch($name) {
case "inheritance":
$this->currColumn->addInheritance($attributes);
break;
case "vendor":
$this->currVendorObject = new ObjectWithVendorSpecificData($this->currColumn, $attributes['type']);
break;
default:
$this->_throwInvalidTagException($name);
}
} elseif ($parentTag == "foreign-key") {
switch($name) {
case "reference":
$this->currFK->addReference($attributes);
break;
case "vendor":
$this->currVendorObject = new ObjectWithVendorSpecificData($this->currFK, $attributes['type']);
break;
default:
$this->_throwInvalidTagException($name);
}
} elseif ($parentTag == "index") {
switch($name) {
case "index-column":
$this->currIndex->addColumn($attributes);
break;
case "vendor":
$this->currVendorObject = new ObjectWithVendorSpecificData($this->currIndex, $attributes['type']);
break;
default:
$this->_throwInvalidTagException($name);
}
} elseif ($parentTag == "unique") {
switch($name) {
case "unique-column":
$this->currUnique->addColumn($attributes);
break;
case "vendor":
$this->currVendorObject = new ObjectWithVendorSpecificData($this->currUnique, $attributes['type']);
break;
default:
$this->_throwInvalidTagException($name);
}
} elseif ($parentTag == "validator") {
switch($name) {
case "rule":
$this->currValidator->addRule($attributes);
break;
default:
$this->_throwInvalidTagException($name);
}
} elseif ($parentTag == "vendor") {
switch($name) {
case "parameter":
if($this->currVendorObject->isCompatible($this->platform->getDatabaseType())) {
$this->currVendorObject->setVendorParameter($attributes['name'], iconv('utf-8',$this->encoding, $attributes['value']));
}
break;
default:
$this->_throwInvalidTagException($name);
}
} else {
// it must be an invalid tag
$this->_throwInvalidTagException($name);
}
$this->pushCurrentSchemaTag($name);
} catch (BuildException $e) {
throw $e;
} catch (Exception $e) {
echo $e;
echo "\n";
throw $e;
}
}
function _throwInvalidTagException($tag_name)
{
throw new BuildException("Unexpected tag <" . $tag_name . ">", $this->parser->getLocation());
}
/**
* Handles closing elements of the xml file.
*
* @param uri
* @param localName The local name (without prefix), or the empty string if
* Namespace processing is not being performed.
* @param rawName The qualified name (with prefix), or the empty string if
* qualified names are not available.
*/
public function endElement($name)
{
if (self::DEBUG) {
print("endElement(" . $name . ") called\n");
}
$this->popCurrentSchemaTag();
}
protected function peekCurrentSchemaTag()
{
$keys = array_keys($this->schemasTagsStack);
return end($this->schemasTagsStack[end($keys)]);
}
protected function popCurrentSchemaTag()
{
$keys = array_keys($this->schemasTagsStack);
array_pop($this->schemasTagsStack[end($keys)]);
}
protected function pushCurrentSchemaTag($tag)
{
$keys = array_keys($this->schemasTagsStack);
$this->schemasTagsStack[end($keys)][] = $tag;
}
protected function isExternalSchema()
{
return (sizeof($this->schemasTagsStack) > 1);
}
protected function isAlreadyParsed($filePath)
{
return isset($this->schemasTagsStack[$filePath]);
}
}
/**
* Utility class used for objects with vendor data.
*
* @package propel.engine.database.transform
*/
class ObjectWithVendorSpecificData
{
protected $object;
protected $vendorType;
public function __construct($object, $vendorType)
{
$this->object = $object;
$this->vendorType = $vendorType;
}
public function isCompatible($type)
{
return ($this->vendorType == $type);
}
public function setVendorParameter($name, $value)
{
$this->object->setVendorParameter($name, $value);
}
}