<?php
/*
Copyright 2008 David Wainwright
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Version: 0.0.0.3
Date: 09/04/2008
Changelog:
Version Date Description
0.0.0.2 18042008 Now parses blank elements as having null values
0.0.0.3 18042008 Added support for array types and default paths
0.0.0.4 30042008 Improved consistency of specifying values in xml file
Removed paths from error text and added ERROR_PREFIX
*/
class ConfigurationLoader
{
//Xml element types
const TEXT_NODE_NAME = "#text";
const TEXT_NODE_TYPE = 3;
const COMMENT_NODE_TYPE = 8;
const ELEMENT_NODE_TYPE = 1;
//Php types
const VARIANT_TYPE = 0;
const ARRAY_TYPE = 1;
const CLASS_TYPE = 2;
//ConfigurationLoader constants
const ERROR_PREFIX = "ConfigurationLoader ERROR";
/*Call this method to read the file at configFilePath and convert it into the php file
specified by targetFilePath. Ensure file permissions are correct for these two operations
*/
public static function update($_configFilePath, $_targetFilePath)
{
$configFilePath = $_configFilePath;
$targetFilePath = $_targetFilePath;
if(is_null($configFilePath))
{
$configFilePath = "config.xml";
}
if(is_null($targetFilePath))
{
$targetFilePath = $configFilePath . ".php";
}
if(file_exists($targetFilePath))
{
//Get last modified date of config file
$configFileLastMod = filemtime($configFilePath);
//Get last modified date of target file
$targetFileLastMod = filemtime($targetFilePath);
if($configFileLastMod > $targetFileLastMod)
{
self::parse($configFilePath, $targetFilePath);
}
}
else
{
self::parse($configFilePath, $targetFilePath);
}
}
//Loads the source file into a dom document and opens the target file for writing
private static function parse($configFilePath, $targetFilePath)
{
$loaded = false;
$doc = new DOMDocument();
$loaded = $doc->load($configFilePath);
if(!$loaded)
{
echo self::ERROR_PREFIX . ": Failed to load xml config file<br />";
}
$root = $doc->documentElement;
//self::outputParsedXML($root, 0);
//open a file to write to
//The file is written to a temporary file, which is then copied over to the target filename
$tempFilePath = $targetFilePath . ".tmp";
$file = fopen($tempFilePath, "w");
if($file)
{
fwrite($file, "<?php\n");
fwrite($file, "/*\n");
fwrite($file, "This file was autogenerated by ConfigurationLoader.class.php.\n");
fwrite($file, "Please do not attempt to alter it's contents manually as any changes you make may be overwritten\n");
fwrite($file, "*/\n");
self::parseNode($root, $file, self::replaceSpecialChars($root->nodeName));
fwrite($file, "?>");
fclose($file);
rename($tempFilePath, $targetFilePath);
}
else
{
echo self::ERROR_PREFIX . ": Please ensure that the xml config file exists and that the user running apache has read permissions for this file";
}
}
//Used for debugging - outputs the xml dom tree as basic html code
private static function outputParsedXML($node, $indent)
{
$indentText = str_repeat(" ", $indent);
echo $indentText . "<" . $node->nodeName . ">\n";
if($node->nodeName == "#text")
{
echo $node->nodeValue;
}
else
{
if($node->hasChildNodes())
{
for($i = 0;$i < $node->childNodes->length;$i++)
{
self::outputParsedXML($node->childNodes->item($i), $indent + 4);
}
}
}
echo $indentText . "</" . $node->nodeName . ">\n";
}
//Recursively parses the xml dom tree. This function is too big and needs re-organising
private static function parseNode($node, $handle, $prefix)
{
fwrite($handle, "class " . $prefix . "\n");
fwrite($handle, "{\n");
$nodeList = array();
$count = 0;
if($node->hasChildNodes())
{
for($i = 0;$i < $node->childNodes->length;$i++)
{
$childNode = $node->childNodes->item($i);
if(self::isValueNode($childNode))
{
fwrite($handle, "public $" . self::replaceSpecialChars($childNode->nodeName) . ";\n");
}
}
//Write constructor
fwrite($handle, "function __construct()\n{\n");
for($i = 0;$i < $node->childNodes->length;$i++)
{
$childNode = $node->childNodes->item($i);
$propertyName = self::replaceSpecialChars($childNode->nodeName);
if(self::isValueNode($childNode))
{
$type = null;
if($childNode->hasAttribute("type"))
{
$type = $childNode->getAttribute("type");
}
if($type == "array")
{
fwrite($handle, "\$this->" . $propertyName . " = array();\n");
//Fill the array
fwrite($handle, self::parseArrayItems($propertyName, $childNode) . "\n");
}
else
{
$value = self::getValue($childNode);
$propertyName = "this->" . $propertyName;
fwrite($handle, self::getPropertyCode($type, $propertyName , $value) . ";\n");
}
}
else
{
if($childNode->nodeType == self::ELEMENT_NODE_TYPE)
{
$className = $prefix . "_" . $propertyName;
fwrite($handle, "\$this->" . $propertyName . " = new " . $className . "();\n");
$nodeList[$count] = $childNode;
$count++;
}
}
}
fwrite($handle, "\n}\n");
fwrite($handle, "}\n");
}
foreach($nodeList as $childNode)
{
$className = $prefix . "_" . self::replaceSpecialChars($childNode->nodeName);
self::parseNode($childNode, $handle, $className);
}
}
//Returns true if the specified node has a child node whose nodeName is #text
private static function isValueNode($node)
{
$nodeType = $node->nodeType;
//If the node is of type text or a comment - return false
if(($nodeType == self::TEXT_NODE_TYPE) || ($nodeType == self::COMMENT_NODE_TYPE))
{
return false;
}
//If the node has no children, it is a value type with no value
if(!$node->hasChildNodes())
{
return true;
}
//If the node has only one child which is of type TEXT, it is a value node
$textNodes = self::getChildNodesOfType($node, self::TEXT_NODE_TYPE);
if(count($textNodes) == 1)
{
return true;
}
else //otherwise, check if it is an array
{
if($nodeType == self::ELEMENT_NODE_TYPE)
{
if($node->getAttribute("type") == "array")
{
return true;
}
}
}
return false;
}
//Returns an array of the specified nodes child nodes that are of the specified type
private static function getChildNodesOfType($node, $type)
{
$children = array();
$iCount = 0;
for($i = 0;$i < $node->childNodes->length;$i++)
{
$child = $node->childNodes->item($i);
if($child->nodeType == $type)
{
$children[$iCount] = $child;
$iCount++;
}
}
return $children;
}
//Returns the number of items in the specified nodeList
private static function getNoOfItems($nodeList)
{
$count = 0;
foreach($nodeList as $item)
{
$count++;
}
return $count;
}
//Used to generate the class names and strip out any non-php compliant characters
private static function replaceSpecialChars($value)
{
//Replace any non-word characters with an underscore
return ereg_replace("[\\W-]", "_", $value); //Convert non-word characters, hyphens and dots to underscores
}
//The node passed into this function must have a text node as it's only child node
private static function getTextValue($node)
{
if($node->hasChildNodes())
{
return $node->firstChild->nodeValue;
}
return null;
}
//Just returns 'string' if the specified value contains non-digit characters, or null otherwise
private static function getPhpType($value)
{
//If the value is an empty string, return string
if(is_null($value))
{
return null;
}
if($value == "")
{
return "string";
}
//Determine whether the value is a string or not here
if(preg_match("[\\D]", $value)) //Looks for any non-digit characters
{
return "string";
}
return null; //type not defined
}
//Parses a set of array items in the xml file into code
private static function parseArrayItems($arrayName, $node)
{
$code = "";
//Get all of the items;
$items = $node->getElementsByTagName("item");
$count = 0;
foreach($items as $item)
{
$value = self::getValue($item);
if($item->hasAttribute("index"))
{
$indexName = $item->getAttribute("index");
}
else
{
$indexName = $count++;
}
$indexType = self::getPhpType($indexName);
if($indexType == "string")
{
$indexName = "\"" . $indexName . "\"";
}
$type = null;
if($item->hasAttribute("type"))
{
$type = $item->getAttribute("type");
}
else
{
$type = self::getPhpType($value);
}
//DEBUG
//echo $indexName . "=" . $value . " (" . gettype($value) . ")\n";
$propertyName = "this->" . $arrayName . "[" . $indexName . "]";
$code .= self::getPropertyCode($type, $propertyName, $value) . ";\n";
}
return $code;
}
//Generates the code for a variable assignment;
private static function getPropertyCode($type, $propertyName, $value)
{
if(is_null($type))
{
//Get type using value
$type = self::getPhpType($value);
}
if(!is_null($value))
{
if($type == "string")
{
$value = "\"" . $value . "\"";
}
}
else
{
$value = "null";
}
if(!is_null($type))
{
$type = "(" . $type . ")";
}
else
{
$type = "";
}
return "\$" . $propertyName . " = " . $type . " " . $value;
}
private static function getValue($node)
{
$value = null;
if($node->hasAttribute("value"))
{
$value = $node->getAttribute("value");
}
else
{
if($node->childNodes->length == 1) //If there's only one child node
{
$value = self::getTextValue($node);
}
}
return $value;
}
}
?>