<?php
/**
* Page DocBlock definition
* @package org.zadara.marius.pax
*/
/**
* Tags definition file.
* This class will be responsbile with loading the tags from the definition file,
* also for filtering (tags, tag's content).
*
* @author Marius Zadara <marius@zadara.org>
* @category Classes
* @copyright (C) 2008-2009 Marius Zadara
* @license Free for non-comercial use
* @package org.zadara.marius.pax
* @final
* @see File
* @see ITags
* @version 6.0
* @since 5.0
*/
final class Tags extends File implements ITags
{
/**
* The tags definition file content.
*
* @access private
* @var string
*/
private $tagsFileContent;
/**
* The array with all the tags from the definition file.
* It is used to keep track of a tag and not search it again in the file
*
* @access private
* @var array
*/
private $tags;
/**
* Class constructor.
*
* @access public
*/
public function __construct()
{
// call the parent constructor
parent::__construct();
// init the tags file content
$this->tagsFileContent = null;
// init the tags array
$this->tags = null;
}
/**
* Method to check if a tag is allowed.
*
* @access public
* @param string <b>$rootTag</b> The root tag from the definition file
* @param string <b>$tag</b> The searched tag
* @return boolean
*/
public function isAllowedTag($rootTag, $tag)
{
// if the tag has been searched before,
// return the previous result
if (isset($this->tags[$tag]))
return $this->tags[$tag];
// check the tags file content
// try to load the tag definition file for the first tme
if (!is_object($this->tagsFileContent))
{
$this->tagsFileContent = @simplexml_load_file($this->path);
// in case of failure,
// throw exception
if ($this->tagsFileContent === false)
throw new PAXException(sprintf(Messages::$MSG_033, $this->path), 33);
}
// search the tag using an XPath expression
$result = @$this->tagsFileContent->xPath(sprintf("/%s/%s", $rootTag, $tag));
// init the tags array the first time
if (is_null($this->tags))
$this->tags = array();
// set the acceptance for this tag
// if the XPath expression failed, is the same as the tag is not accepted
$this->tags[$tag] = is_array($result) ? (sizeof($result) > 0) : false;
// return the result
return $this->tags[$tag];
}
/**
* Method to filter the tags according to the definition file.
* Throws exception if error encountered
*
* @access public
* @static
* @param config <b>$config</b> The PAX config object
* @param config <b>$elements</b> The PAX elements object
* @param config <b>$directories</b> The PAX directories object
* @param config <b>$filenames</b> The PAX filenames object
* @param array <b>$nodes</b> The array with the current nodes
* @param array <b>$namespaces</b> The array with the current namespaces
* @param string <b>$paxNsSeparator</b> The separator used in tag namespace definition
* @return void
*/
public static function filterTags(&$config, &$elements, $directories, $filenames, &$nodes, &$namespaces, $paxNsSeparator)
{
// catch any exceptions
try
{
// get the filter tags flag from the config object
// set the default value to true
$filterTags = $config->get("filterTags", true);
// get the attribute name
// using this attribute, this filtering can be overwritten from the source itself
$filterTagsAttributeName = $config->get("filterTagsAttributeName", "filterTags");
// get the final status of the filtering
// either from the config or from the definition
if (isset($nodes[0]['attributes'][$filterTagsAttributeName]))
{
// check to see if the attribute value is in the 'true' dictionary
$filterTags = Model::isInList($nodes[0]['attributes'][$filterTagsAttributeName], $elements->get("trueTexts"));
}
// continue with the filtering
// only if the status is true
if ($filterTags)
{
$allowedTags = array();
$allowNoTags = $config->get("allowNoTags", false);
// allow the source without any tag definition?
if (!$allowNoTags)
{
$tags = new Tags();
// get the tags path from the configuration
$tagsPath = $directories->get("definitions", "definitions");
$tagsPath .= $filenames->get("tags", "tags.xml");
$tags->setPath($tagsPath);
// validate the path
// if any error, throw exception
if (!$tags->exists())
throw new PAXException(sprintf(Messages::$MSG_030, $tagsPath), 30);
if (!$tags->isOrdinary())
throw new PAXException(sprintf(Messages::$MSG_031, $tagsPath), 31);
if (!$tags->isReadable())
throw new PAXException(sprintf(Messages::$MSG_031, $tagsPath), 32);
}
// get the tags definition file root
$tagsRoot = $elements->get("tagsRoot", "tags");
// create a file object
// this object will be used to check the node definition file
$file = new File();
// parse each node from the list
foreach ($nodes as $index => $nodeData)
{
// get the namespace and tag name based on the node data
$names = self::extractNamesFromTag($nodeData['tag'], ":");
// if failed ...
if ($names === false)
{
// mark the node as invalid and continue
$nodes[$index] = false;
continue;
}
// namespace found?
if ($names['namespace'] !== false)
{
// declared?
if (!isset($namespaces[$names['namespace']]))
throw new PAXException(sprintf(Messages::$MSG_023, $names['namespace']), 23);
// update the directory using the namespace
$namespaceDirectory = $namespaces[$names['namespace']];
$namespaceClass = $names['tagName'];
// get the file format from using the namespace
$namespaceFileFormat = $filenames->get("namespaceClassFileFormat", "[CLASS].class.php");
$namespaceFile = str_ireplace("[CLASS]", $namespaceClass, $namespaceFileFormat);
// update the namespace file path
$namespaceFile = $namespaceDirectory . $namespaceFile;
// update the file path
$file->setPath($namespaceFile);
// check the file
// in case of error, throw exception
if (!$file->exists())
throw new PAXException(sprintf(Messages::$MSG_024, $namespaceFile), 24);
if (!$file->isOrdinary())
throw new PAXException(sprintf(Messages::$MSG_025, $namespaceFile), 25);
if (!$file->isReadable())
throw new PAXException(sprintf(Messages::$MSG_026, $namespaceFile), 26);
// load the file
require_once $namespaceFile;
// update the tag name
// so we eliminate the namespace (is not needed anymore, class previously loaded)
$nodes[$index]['tag'] = $names['tagName'];
unset($usedNamespace, $namespaceDirectory, $namespaceClass, $namespaceFileFormat);
}
else
{
// do the same validations for the non-namespace node
$modelClassFileFormat = $filenames->get("modelClassFileFormat", "[CLASS].class.php");
$modelClassFile = str_ireplace("[CLASS]", $nodeData['tag'], $modelClassFileFormat);
$modelClassFile = $directories->get("implementations") . $modelClassFile;
// set the path of the file
$file->setPath($modelClassFile);
// check the file
if (!$file->exists())
throw new PAXException(sprintf(Messages::$MSG_027, $modelClassFile), 27);
if (!$file->isOrdinary())
throw new PAXException(sprintf(Messages::$MSG_028, $modelClassFile), 28);
if (!$file->isReadable())
throw new PAXException(sprintf(Messages::$MSG_029, $modelClassFile), 29);
// load the file
require_once $modelClassFile;
unset($modelClassFileFormat, $modelClassFile);
if (!$tags->isAllowedTag($tagsRoot, $nodeData['tag']))
$nodes[$index] = false;
}
}
}
}
catch (PAXException $pe)
{
// in case of exception, send it further
throw $pe;
}
catch (Exception $e)
{
// in case of exception, send it further
throw $e;
}
}
/**
* Method to filter the tag content.
* Throws exception in case of error.
*
* @access public
* @static
* @param config <b>$config</b> The PAX config object
* @param config <b>$elements</b> The PAX elements object
* @param array <b>$nodes</b> The PAX nodes array
* @return void
*/
public static function filterTagContent(&$config, $elements, &$nodes)
{
// catch any exceptions
try
{
// load the filtering status from the config
$filterTagContent = $config->get("filterTagContent", true);
// load the filtering status from the file
$filterTagContentAttributeName = $config->get("filterTagContentAttributeName", "filterTagContent");
if (isset($nodes[0]['attributes'][$filterTagContentAttributeName]))
{
// if the attribute exists,
// update the config value with the value from attribute
// in order to continue filtering, the value must be in the 'true texts' dictionary
$filterTagContent = Model::isInList($nodes[0]['attributes'][$filterTagContentAttributeName], $elements->get("trueTexts"));
}
// continue with the filtering
if ($filterTagContent)
{
// get the separator used in tag namespace definition
$paxNsSeparator = $elements->get("paxNsSeparator", ":");
// get the validation method name
// this method, if foun in the tag class, will be called to filter the content
$validationMethodName = $elements->get("tagContentValidationMethodName", "validateContent");
// parse the nodes list
foreach ($nodes as $index => $nodeData)
{
// if invalid node, skip
if ($nodeData === false)
continue;
// if node is closed, continue
// (closed nodes don't have content attached)
if ($nodeData['type'] == 'close')
continue;
// skip the nodes without a content
if (!isset($nodeData['value']))
continue;
// get the position of the separator in the tag name
// in order to get the class name
$paxNsSepPos = strpos($nodeData['tag'], $paxNsSeparator);
if ($paxNsSepPos !== false)
$currentNodeName = substr($nodeData['tag'], $paxNsSepPos + 1);
else
$currentNodeName = $nodeData['tag'];
// beautify the class name
$currentNodeName = ucfirst($currentNodeName);
// at this point, the class should already exists (loaded at filtering nodes method)
// if not found, throw exception
if (!class_exists($currentNodeName))
throw new PAXException(sprintf(Messages::$MSG_038, $currentNodeName), 38);
// if the method does not exists in the class definition,
// skip filtering
if (!method_exists($currentNodeName, $validationMethodName))
continue;
// create the filtering content evaluation string
$evalString = sprintf("\$newValue = %s::%s('%s');", $currentNodeName, $validationMethodName, $nodeData['value']);
// if could not evaluate the string,
// throw exception
if (@eval($evalString) === false)
throw new PAXException(sprintf(Messages::$MSG_040, $currentNodeName, $nodeData['value']), 40);
// update the tag content value with the new value
// the function should return null in case of invalid content
$nodes[$index]['value'] = is_null($newValue) ? false : $newValue;
}
}
}
catch (PAXException $pe)
{
// in case of exception, throw it further
throw $pe;
}
catch (Exception $e)
{
// in case of exception, throw it futher
throw $e;
}
}
/**
* Method to compile the instructions.
*
* @access public
* @param config <b>$instructions</b> The PAX instructions
* @param array <b>$nodes</b> The nodes list
* @return void
*/
public function compileInstructions(&$instructions, &$nodes)
{
if (is_null($instructions))
return;
// create the pattern
$pattern = new Pattern();
$instructionExtractor = new InstructionExtractor();
$instructionEvaluator = new InstructionEvaluator();
$currentInstructions = array();
$startDelimiter = $instructions->get("startDelimiter", "!#");
$endDelimiter = $instructions->get("endDelimiter", "#!");
// parse each node
foreach ($nodes as $nodeIndex => $nodeData)
{
if ($nodeData === false)
continue;
if (!isset($nodeData['value']))
continue;
try
{
// try to extract the instructions from the content of the node
$currentInstructions = $instructionExtractor->extract($nodeData['value'], $startDelimiter, $endDelimiter);
}
catch (PAXException $pe)
{
// if exception, throw it further
throw $pe;
}
catch (Exception $e)
{
// if exception, throw it further
throw $e;
}
// validate the instructions
if ($currentInstructions === false)
continue;
// compile each instruction
// and replace it by its value
foreach ($currentInstructions as $instruction)
{
$replacePattern = $pattern->makeInstructionReplacePattern($instruction, $startDelimiter, $endDelimiter);
$nodes[$nodeIndex]['value'] = InstructionReplacer::replace($replacePattern, $instructionEvaluator->evaluate($instruction), $nodes[$nodeIndex]['value']);
}
}
}
/**
* Method to extract the namespace and tag name based on the node data.
*
* @access public
* @static
* @param string <b>$tagName</b> The name of the tag as appears in the node data
* @param string <b>$namespaceSep</b> The separator used for delimiting the namespace and the tag name
* @return mixed False if invalid separator or array('namespace' => namespace, 'tagName' => name of the tag)
*/
public static function extractNamesFromTag($tagName, $namespaceSep)
{
// assume the worst
$names = false;
$tagName = trim($tagName);
if ($tagName == "")
return $names;
// init the return object as array
$names = array();
// get the separator position
$namespaceSepPos = stripos($tagName, $namespaceSep);
// if the separator has not been found,
// create the return array as containing only the tag name
if ($namespaceSepPos === false)
{
$names['namespace'] = false;
$names['tagName'] = $tagName;
}
else
{
// else get both the namespace and the tag name
// using substrings and separator position
$names['namespace'] = substr($tagName, 0, $namespaceSepPos);
$names['tagName'] = substr($tagName, $namespaceSepPos + 1);
}
// return the data collected so far
return $names;
}
/**
* Class destructor.
*
* @access private
*/
function __destruct()
{
}
}
?>
|