* @package patForms
* @subpackage Parser
* @license LGPL
* @copyright PHP Application Tools
*/
/**
* file does not exist
*/
define( 'PATFORMS_PARSER_ERROR_FILE_NOT_FOUND', 100000 );
/**
* file could not be created
*/
define( 'PATFORMS_PARSER_ERROR_FILE_NOT_CREATED', 100001 );
/**
* element cannot be serialized
*/
define( 'PATFORMS_PARSER_ERROR_ELEMENT_NOT_SERIALIZEABLE', 100002 );
/**
* element cannot be serialized
*/
define( 'PATFORMS_PARSER_ERROR_CACHEDIR_NOT_VALID', 100003 );
/**
* no namespace has been declared
*/
define( 'PATFORMS_PARSER_ERROR_NO_NAMESPACE', 100004 );
/**
* static property does not exist
*/
define( 'PATFORMS_PARSER_ERROR_NO_STATIC_PROPERTY', 100005 );
/**
* basedir is not valid
*/
define( 'PATFORMS_PARSER_ERROR_BASEDIR_NOT_VALID', 100006 );
/**
* no closing tag found
*/
define( 'PATFORMS_PARSER_ERROR_NO_CLOSING_TAG', 100007 );
/**
* invalid tag found
*/
define( 'PATFORMS_PARSER_ERROR_INVALID_CLOSING_TAG', 100008 );
/**
* invalid tag found
*/
define( 'PATFORMS_PARSER_ERROR_DRIVER_FILE_NOT_FOUND', 100009 );
/**
* invalid tag found
*/
define( 'PATFORMS_PARSER_ERROR_DRIVER_CLASS_NOT_FOUND', 100010 );
/**
* form does not exist
*/
define( 'PATFORMS_PARSER_ERROR_FORM_NOT_FOUND', 100011 );
/**
* unknown tag in custom namespace
*/
define('PATFORMS_PARSER_ERROR_UNKNOWN_TAG', 100020);
/**
* static properties
* @var array
* @access private
*/
$GLOBALS['_patForms_Parser'] = array(
'cacheFolder' => false,
'baseDir' => false,
'namespace' => false,
'placeholder' => '{PATFORMS_ELEMENT_%s}',
'placeholder_form_start' => '{PATFORMS_FORM_%s_START}',
'placeholder_form_end' => '{PATFORMS_FORM_%s_END}',
'placeholder_case' => 'upper',
'namespaceHandlers' => array()
);
/**
* class to parse an HTML document or patTemplate and extract all
* patForms elements. They will be replaced by placeholders.
*
* It is possible to attach handlers for other namespaces.
* The parser will delegate the tags to these handlers and
* the return values will be used to create the form instead.
*
* Known issues of the parser:
* - Currently it's only possible to parse one form per document, this will change in future versions
*
* @author Stephan Schmidt
* @package patForms
* @subpackage Parser
* @license LGPL
* @copyright PHP Application Tools
*/
class patForms_Parser
{
/**
* Stores the names of all static properties that patForms_Parser will use as defaults
* for the properties with the same name on startup.
*
* @access private
*/
var $staticProperties = array(
'cacheFolder' => 'setCacheDir',
'baseDir' => 'setBaseDir',
'namespace' => 'setNamespace',
);
/**
* namespace for form elements
* @var string
* @access private
*/
var $_namespace = null;
/**
* namespace handlers
* @var string
* @access private
*/
var $_namespaceHandlers = array();
/**
* cache folder
* @var string
* @access private
*/
var $_cacheFolder = null;
/**
* base directory
* @var string
* @access private
*/
var $_baseDir = null;
/**
* placeholder template for form elements
*
* %s will be replaced with the name of the element
*
* @var string
* @access private
* @see $_placeholder_case
*/
var $_placeholder = '{PATFORMS_ELEMENT_%s}';
/**
* placeholder template for start of form
*
* %s will be replaced with the name of the form
*
* @var string
* @access private
* @see $_placeholder_case
* @see $_placeholder_form_end
*/
var $_placeholder_form_start = '{PATFORMS_FORM_%s_START}';
/**
* placeholder template for start of form
*
* %s will be replaced with the name of the form
*
* @var string
* @access private
* @see $_placeholder_case
* @see $_placeholder_form_start
*/
var $_placeholder_form_end = '{PATFORMS_FORM_%s_END}';
/**
* case of the element name in the template
*
* @var string
* @access private
* @see $_placeholder
*/
var $_placeholder_case = 'upper';
/**
* sourcefile name
* @var string
* @access private
*/
var $_sourceFile;
/**
* outputfile name
* @var string
* @access private
*/
var $_outputFile;
/**
* form object
* @var object
* @access private
*/
var $_form;
/**
* name of the current form
* @var string
* @access private
*/
var $_currentForm = '__default';
/**
* form element definitions
* @var array
* @access private
*/
var $_elementDefinitions = array();
/**
* form attributes
* @var array
* @access private
*/
var $_formAttributes = array();
/**
* HTML code
* @access private
* @var string
*/
var $_html;
/**
* elements found during parsing process
* @access private
* @var array
*/
var $_elStack = array();
/**
* cdata found during parsing process
* @access private
* @var array
*/
var $_cData = array();
/**
* tag depth
* @access private
* @var integer
*/
var $_depth = 0;
/**
* entities that may be used in attributes
* @var array
* @access private
*/
var $_entities = array(
'"' => '"',
'&' => '&',
''' => '\'',
'>' => '>',
'<' => '<',
);
/**
* constructor
*
* @access public
*/
function patForms_Parser()
{
$this->__construct();
}
/**
* constructor
*
* @access public
*/
function __construct()
{
foreach ($this->staticProperties as $staticProperty => $setMethod) {
$propValue = patForms_Parser::getStaticProperty( $staticProperty );
if (patErrorManager::isError($propValue)) {
continue;
}
$this->$setMethod($propValue);
}
/**
* set the placeholders
*/
$this->setPlaceholder( patForms_Parser::getStaticProperty( 'placeholder' ), patForms_Parser::getStaticProperty( 'placeholder_case' ) );
$this->setFormPlaceholders( patForms_Parser::getStaticProperty( 'placeholder_form_start' ), patForms_Parser::getStaticProperty( 'placeholder_form_end' ) );
// configure namespace handler
$nsHandlers = &patForms_Parser::getStaticProperty('namespaceHandlers');
$namespaces = array_keys( $nsHandlers );
foreach ($namespaces as $ns) {
$this->addNamespace( $ns, $nsHandlers[$ns] );
}
}
/**
* setCacheDir
*
* The cache dir has to be set to utilize the caching features.
*
* @access public
* @param mixed path to the directory or false to disable caching
* @return boolean true on success
* @see $_cacheFolder
*/
function setCacheDir( $dir )
{
if ($dir != false) {
if (!is_dir($dir) || !is_writable($dir)) {
return patErrorManager::raiseError(
PATFORMS_PARSER_ERROR_CACHEDIR_NOT_VALID,
"Cache folder '$dir' is either no directory or not writable.",
'Check path and permissions'
);
}
}
if (isset($this) && is_a($this, 'patForms_Parser')) {
$this->_cacheFolder = $dir;
} else {
patForms_Parser::setStaticProperty('cacheFolder', $dir);
}
return true;
}
/**
* Set base directory for all files.
*
* @access public
* @param mixed path to the directory or false reset the basedir
* @return boolean $result true on success
* @see $_cacheFolder
*/
function setBaseDir( $dir )
{
if( $dir != false )
{
if( !is_dir( $dir ) )
{
return patErrorManager::raiseError(
PATFORMS_PARSER_ERROR_BASEDIR_NOT_VALID,
"Base directory '$dir' is does not exist or is no directory"
);
}
}
if( isset( $this ) && is_a( $this, "patForms_Parser" ) )
{
$this->_baseDir = $dir;
}
else
{
patForms_Parser::setStaticProperty( "baseDir", $dir );
}
return true;
}
/**
* Set the namespace for the form elements
*
* If this method is called statically, it will set the
* namespace for future static calls to createFormFromTemplate()
* and parseFile().
*
* If the namespace is set to null, patForms_Parser will try
* to get the namespace declaration from the (X)HTML document
* that is being parsed.
*
* That means that you should include a
* xmlns:myForm="http://www.php-tools.net/patForms/basic"
* attribute in the root tag of your templates.
* "myForm" is the namespace that you are using for your
* patForms elements. Make sure that you are using the
* correct URI for the attribute so it can be recognized.
*
* @access public
* @param string namespace
* @see getNamespacePrefix()
*/
function setNamespace( $ns )
{
if( isset( $this ) && is_a( $this, "patForms_Parser" ) )
{
$this->_namespace = $ns;
}
else
{
patForms_Parser::setStaticProperty( "namespace", $ns );
}
}
/**
* Set the placeholder template for elements.
*
* When parsing an HTML page that contains patForms elements
* they will be replaced by placeholders. This method allows
* you to set the format of the placeholders.
*
* You may specify a format string like you would for
* sprintf, with one %s that marks where the name of the
* element will be inserted.
*
* @access public
* @param string placeholder
* @param string flag to indicate, whether the name should be inserted in uppercase ('upper'),
* lowercase ('lower') or how it was specified ('keep').
* @see sprintf()
* @see setFormPlaceholders()
*/
function setPlaceholder( $placeholder, $case = "upper" )
{
if( isset( $this ) && is_a( $this, "patForms_Parser" ) )
{
$this->_placeholder = $placeholder;
$this->_placeholder_case = $case;
}
else
{
patForms_Parser::setStaticProperty( "placeholder", $placeholder );
patForms_Parser::setStaticProperty( "placeholder_case", $case );
}
}
/**
* Set the placeholder template for form start and end tag.
*
* When parsing an HTML page that contains a patForms:Form tag
* this will be replaced by placeholders. This method allows
* you to set the format of the placeholders.
*
* You may specify a format string like you would for
* sprintf, with one %s that marks where the name of the
* element will be inserted.
*
* @access public
* @param string placeholder for the start tag
* @param string placeholder for the end tag
* @see sprintf()
* @see setPlaceholder()
*/
function setFormPlaceholders( $start, $end )
{
if( isset( $this ) && is_a( $this, "patForms_Parser" ) )
{
$this->_placeholder_form_start = $start;
$this->_placeholder_form_end = $end;
}
else
{
patForms_Parser::setStaticProperty( "placeholder_form_start", $start );
patForms_Parser::setStaticProperty( "placeholder_form_end", $end );
}
}
/**
* add a namespace handler
*
* Namespace handlers can be used to include external
* data in patForms elements.
*
* @access public
* @param string namespace
* @param object handler
*/
function addNamespace( $namespace, &$handler )
{
if( isset( $this ) && is_a( $this, "patForms_Parser" ) )
{
$this->_namespaceHandlers[$namespace] =& $handler;
}
else
{
$ns =& patForms_Parser::getStaticProperty( 'namespaceHandlers' );
$ns[$namespace] =& $handler;
}
}
/**
* parse a file and extract all elements
*
* If an outputfile is specified and the cache directory
* has been set, two files will be created:
* - the outputfile, containing the HTML code with placeholders for all elements
* - a cache file that contains a serialized string with the attributes of all elements
*
* On the next call to parseFile() patForms_Parser will check
* if these files exist and use them instead of parsing the sourcefile.
*
* @access public
* @param string $filename filename
* @return boolean
* @uses parseString()
*/
function parseFile( $filename, $outputFile = null )
{
$this->_sourceFile = $filename;
$this->_outputFile = $outputFile;
if ($this->_outputFile != null) {
$cache = $this->_checkCache();
if ($cache) {
return true;
}
}
$string = file_get_contents( $this->_adjustFilename( $this->_sourceFile ) );
if ($string === false) {
$relative = '(no basedir set)';
if( !empty( $this->_baseDir ) ) {
$relative = '(relative to "'.$this->_baseDir.'")';
}
return patErrorManager::raiseError(
PATFORMS_PARSER_ERROR_FILE_NOT_FOUND,
'Sourcefile could not be read',
'Tried to open file "'.$this->_sourceFile.'" '.$relative
);
}
$result = $this->parseString($string);
if ($this->_outputFile != null && $this->_cacheFolder != null) {
$success = $this->_writeHTMLToFile();
if (patErrorManager::isError($success)) {
return $success;
}
$success = $this->_writeFormToFile();
if (patErrorManager::isError($success)) {
return $success;
}
}
return $result;
}
/**
* write the resulting HTML code to a file
*
* @access private
*/
function _writeHTMLToFile()
{
return $this->_writeToFile( $this->_adjustFilename( $this->_outputFile ), $this->getHTML() );
}
/**
* write the form defintions to a cache file
*
* @access private
*/
function _writeFormToFile()
{
$tmp = array(
'attributes' => $this->_formAttributes,
'elements' => $this->_elementDefinitions
);
$formDef = serialize( $tmp );
$cacheFile = $this->_getFormCacheFilename();
return $this->_writeToFile( $cacheFile, $formDef );
}
/**
* get the filename for the form cache
*
* @access private
* @return string
*/
function _getFormCacheFilename()
{
return $this->_cacheFolder . "/" . md5( $this->_sourceFile ) . ".form";
}
/**
* adjust a filename according to the specified basedir
*
* @access private
* @param string filename
* @param string adjusted filename
*/
function _adjustFilename( $filename )
{
if( !empty( $this->_baseDir ) )
return "{$this->_baseDir}/$filename";
return $filename;
}
/**
* write a string to a file
*
* I dreamed of a world, where PHP5 has already been declared as
* stable and I could just use file_put_contents() for such an easy
* task.
*
* @access private
* @param string $filename
* @param string $data
*/
function _writeToFile( $file, $data )
{
$fp = @fopen( $file, "w" );
if (!$fp) {
return patErrorManager::raiseError(
PATFORMS_PARSER_ERROR_FILE_NOT_CREATED,
"Could not create file '$file'.",
"File: " . $file
);
}
flock($fp, LOCK_EX);
fwrite($fp, $data);
flock($fp, LOCK_UN);
fclose($fp);
return true;
}
/**
* check, whether a cache can be used
*
* @access private
* @return boolean true, if the cache exists and still is valid, false otherwise
*/
function _checkCache()
{
if( empty( $this->_cacheFolder ) )
return false;
if( !file_exists( $this->_outputFile ) )
return false;
$cacheFile = $this->_getFormCacheFilename();
if( !file_exists( $cacheFile ) )
return false;
$srcFile = $this->_adjustFilename( $this->_sourceFile );
$srcTime = filemtime( $srcFile );
if( filemtime( $this->_outputFile ) < $srcTime )
return false;
if( filemtime( $cacheFile ) < $srcTime )
return false;
$form = file_get_contents( $cacheFile );
if ($form === false) {
return false;
}
$tmp = unserialize($form);
$this->_formAttributes = $tmp['attributes'];
$this->_elementDefinitions = $tmp['elements'];
return true;
}
/**
* parse a string and extract all elements
*
* @access public
* @param string $string html string that should be parsed
* @return boolean
*/
function parseString( $string )
{
$this->_elStack = array();
$this->_cData = array();
$this->_depth = 0;
// has namespace been set?
if( $this->_namespace == null )
{
$ns = $this->getNamespacePrefix( $string );
if( patErrorManager::isError( $ns ) )
{
return $ns;
}
$this->setNamespace( $ns );
}
$knownNamespaces = array_merge( array( $this->_namespace ), array_keys( $this->_namespaceHandlers ) );
$regexp = "/(<(\/?)([[:alnum:]]+):([[:alnum:]]+)[[:space:]]*([^>]*)>)/im";
$tokens = preg_split( $regexp, $string, -1, PREG_SPLIT_DELIM_CAPTURE );
/**
* the first token is always character data
* Though it could just be empty
*/
if( $tokens[0] != '' )
$this->_characterData( $tokens[0] );
$cnt = count( $tokens );
$i = 1;
// process all tokens
while( $i < $cnt )
{
$fullTag = $tokens[$i++];
$closing = $tokens[$i++];
$namespace = $tokens[$i++];
$tagname = $tokens[$i++];
$attString = $tokens[$i++];
$empty = substr( $attString, -1 );
$data = $tokens[$i++];
// check, whether it's a known namespace
if (!in_array($namespace, $knownNamespaces)) {
$this->_characterData($fullTag);
$this->_characterData($data);
continue;
}
// is it a closing tag?
if ($closing == "/") {
$result = $this->_endElement( $namespace, $tagname );
if (patErrorManager::isError($result)) {
return $result;
}
$this->_characterData( $data );
continue;
}
// Is empty or opening tag!
$attributes = $this->_parseAttributes($attString);
$result = $this->_startElement( $namespace, $tagname, $attributes );
if (patErrorManager::isError($result)) {
return $result;
}
// check, if the tag is empty
if ($empty == '/') {
$result = $this->_endElement($namespace, $tagname);
if (patErrorManager::isError($result)) {
return $result;
}
}
$this->_characterData($data);
}
// check for tags that are still open
if ($this->_depth > 0) {
$el = array_pop($this->_elStack);
return patErrorManager::raiseError(
PATFORMS_PARSER_ERROR_NO_CLOSING_TAG,
"No closing tag for {$el['ns']}:{$el['name']} found."
);
}
return true;
}
/**
* parse an attribute string and build an array
*
* @access private
* @param string attribute string
* @param array attribute array
*/
function _parseAttributes( $string )
{
// Check for trailing slash, if tag was an empty XML Tag
if( substr( $string, -1 ) == "/" )
$string = trim( substr( $string, 0, strlen( $string )-1 ) );
$attributes = array();
preg_match_all('/([a-zA-Z_\:]+)="((?:\\\.|[^"\\\])*)"/', $string, $match);
for ($i = 0; $i < count($match[1]); $i++)
{
$attributes[strtolower( $match[1][$i] )] = strtr( (string)$match[2][$i], $this->_entities );
}
return $attributes;
}
/**
* start element handler
*
* @access private
* @param string namespace
* @param string local part
* @param array attributes
* @see endElement()
*/
function _startElement( $ns, $name, $attributes )
{
array_push( $this->_elStack, array(
"ns" => $ns,
"name" => $name,
"attributes" => $attributes,
"hasChildren" => false
)
);
switch ($name) {
// Build a form
case 'Form':
if (isset($attributes['name'])) {
$this->_currentForm = $attributes['name'];
}
$this->_formAttributes[$this->_currentForm] = $attributes;
$this->_characterData( $this->_getPlaceholderForForm( $attributes['name'], 'start' ) );
break;
}
$this->_depth++;
$this->_data[$this->_depth] = '';
}
/**
* end element handler
*
* @access private
* @param string namespace
* @param string local part
* @uses addElementDefinition() to add an element to the form
* @uses _callNamespaceHandler() to delegate callback to a handler
*/
function _endElement($ns, $name)
{
$el = array_pop($this->_elStack);
$data = $this->_getCData();
$this->_depth--;
if ($el["name"] != $name || $el["ns"] != $ns) {
return patErrorManager::raiseError(
PATFORMS_PARSER_ERROR_INVALID_CLOSING_TAG,
"Invalid closing tag {$ns}:{$name}. {$el['ns']}:{$el['name']} expected."
);
}
/**
* Foreign namespace has been found
* delegate it to the namespace handler
*/
if ($ns != $this->_namespace) {
$result = $this->_callNamespaceHandler( $ns, $name, $el["attributes"], $data );
if ($this->_depth > 0) {
$this->_addToParentTag($result);
} elseif (is_string( $result)) {
$this->_characterData($result);
}
return true;
}
switch ($name) {
// Build a form
case 'Form':
$this->_characterData($data);
$this->_currentForm = '__default';
$this->_characterData($this->_getPlaceholderForForm($el['attributes']['name'], 'end'));
break;
// Add an option to an Enum element
case 'Option':
$parent = array_pop($this->_elStack);
array_push($this->_elStack, $parent);
$parentName = strtolower( $parent['name'] );
if (isset($el['attributes']['value'])) {
if (!isset($el['attributes']['id'])) {
if (!isset($parent['children'])) {
$parent['children'] = array();
}
$cnt = count($parent['children']) + 1;
$el['attributes']['id'] = $parent['attributes']['name'] . '_option' . $cnt;
}
$label = isset($el['attributes']['label']) ? $el['attributes']['label'] : $data;
$id = isset($el['attributes']['id']) ? $el['attributes']['id'] : $el['attributes']['value'];
$option = $el['attributes'];
$option['label'] = $label;
$option['id'] = $id;
} else {
$option = $data;
}
$this->_addToParentTag($option, null, true);
// if it is an option of a radio group, add a placeholder
if ($parentName == 'radiogroup') {
$this->_characterData('{PATFORMS_ELEMENT_'.strtoupper( $parent['attributes']['name'] ).'_' . strtoupper( $el['attributes']['id'] ) . '}');
}
break;
// Use a datasoucre
case 'Datasource':
switch (strtolower($el['attributes']['type'])) {
// Datasource is a callback
// This can either be a function or a static method
case 'callback':
// get method from method or function attribute
if (isset($el['attributes']['method'])) {
$method = $el['attributes']['method'];
} elseif (isset($el['attributes']['function'])) {
$method = $el['attributes']['function'];
} else {
return patErrorManager::raiseError(PATFORMS_PARSER_ERROR_UNKNOWN_TAG, 'No function or method has been specified as callback');
}
if (isset( $el['attributes']['class'])) {
$datasource = array( $el['attributes']['class'], $method );
} else {
$datasource = $method;
}
break;
// add a custom datasource
case 'custom':
$datasource = $el['children'];
break;
}
$this->_addToParentAttributes('datasource', $datasource);
break;
// Set any attribute
case 'Attribute':
if (isset($el['children'])) {
$data = $el['children'];
}
$this->_addToParentAttributes($el['attributes']['name'], $data);
break;
// Adjust a radio-group => use the children as values
case 'RadioGroup':
$el['type'] = $el['name'];
unset($el['name']);
if (!isset($el['attributes']['id'])) {
$el['attributes']['id'] = $this->_getNextId($el['attributes']['name']);
}
if( isset( $el['children'] ) ) {
if( is_array( $el['children'] ) ) {
$el['attributes']['values'] = $el['children'];
unset($el['children']);
}
}
$renderer = null;
// for the radio group, we use the string renderer as renderer, but
// only if a template is available at all - if there is none, we
// simply let the radiogroup render itself the default way.
$template = trim( $data );
if( !empty( $template ) ) {
$el['renderer'] =& patForms::createRenderer('String');
$el['renderer']->setTemplate( $data );
$el['renderer']->setPlaceholder('{PATFORMS_ELEMENT_'.strtoupper( $el['attributes']['id'] ).'_%s}');
}
if (count($this->_elStack) > 1) {
$this->_addToParentTag($el, $el['attributes']['name'], true);
} else {
$this->addElementDefinitionByArray( $el );
}
$this->_characterData( $this->_getPlaceholderForElement( $el['attributes']['id'] ) );
break;
// Adjust an enum => use the children as values
case 'Enum':
case 'Set':
$el['type'] = $el['name'];
unset($el['name']);
if (!isset($el['attributes']['id'])) {
$el['attributes']['id'] = $this->_getNextId($el['attributes']['name']);
}
if( isset( $el['children'] ) ) {
if( is_array( $el['children'] ) ) {
$el['attributes']['values'] = $el['children'];
unset($el['children']);
}
}
if (count($this->_elStack) > 1) {
$this->_addToParentTag($el, $el['attributes']['name'], true);
} else {
$this->addElementDefinitionByArray( $el );
}
$this->_characterData( $this->_getPlaceholderForElement( $el['attributes']['id'] ) );
break;
// No reserved value, treat it as an
// element and add it to the definitions
default:
$el['type'] = $el['name'];
unset($el['name']);
if (!isset($el['attributes']['id'])) {
$el['attributes']['id'] = $this->_getNextId($el['attributes']['name']);
}
// add a renderer to the Group
if (strtolower($el['type']) === 'group') {
$el['renderer'] = patForms::createRenderer('String');
$el['renderer']->setTemplate($data);
$el['renderer']->setPlaceholder('{'.$this->_placeholder.'}', 'name');
}
if (count($this->_elStack) > 1) {
$this->_addToParentTag($el, $el['attributes']['name'], true);
} else {
$this->addElementDefinitionByArray( $el );
}
$this->_characterData( $this->_getPlaceholderForElement( $el["attributes"]["id"] ) );
break;
}
}
/**
* call a method in a namespace handler
*
* @access private
* @param string namespace
* @param string tag name, will be used as method name
* @param array attributes of the tag
* @param mixed content of the tag
*/
function &_callNamespaceHandler($ns, $name, $attributes, $data)
{
if (!method_exists($this->_namespaceHandlers[$ns], $name)) {
patErrorManager::raiseError(PATFORMS_PARSER_ERROR_UNKNOWN_TAG, "Unknown tag $name in namespace $ns.");
}
$result = &$this->_namespaceHandlers[$ns]->$name( $attributes, $data );
return $result;
}
/**
* add an element to the element definition list
*
* The element definition list is used to build the form
* and also serialized for caching
*
* @access public
* @param string name
* @param string type
* @param array attributes
* @param object renderer
*/
function addElementDefinition($name, $type, $attributes, $renderer = null)
{
if (!isset($this->_elementDefinitions[$this->_currentForm])) {
$this->_elementDefinitions[$this->_currentForm] = array();
}
$this->_elementDefinitions[$this->_currentForm][] = array(
'name' => $name,
'type' => $type,
'attributes' => $attributes,
'renderer' => &$renderer
);
return true;
}
/**
* add an element to the element definition list
*
* The element definition list is used to build the form
* and also serialized for caching
*
* @access public
* @param string name
* @param string type
* @param array attributes
* @param object renderer
*/
function addElementDefinitionByArray( $def )
{
if (!isset($this->_elementDefinitions[$this->_currentForm])) {
$this->_elementDefinitions[$this->_currentForm] = array();
}
$def['name'] = $def['attributes']['name'];
$this->_elementDefinitions[$this->_currentForm][] = $def;
return true;
}
/**
* cdata handler
*
* @access private
* @param string data
*/
function _characterData($data)
{
if ($this->_depth == 0) {
$this->_html .= $data;
return true;
}
$this->_data[$this->_depth] .= $data;
}
/**
* get the character data of the element
*
* @access private
* @return string
*/
function _getCData()
{
if ($this->_depth == 0) {
return '';
}
return $this->_data[$this->_depth];
}
/**
* adds an attribute to the parent tag
*
* @access private
* @param string attribute name
* @param mixed attribute value
*/
function _addToParentAttributes( $name, $value )
{
$parent = array_pop( $this->_elStack );
$parent["attributes"][$name] = $value;
array_push( $this->_elStack, $parent );
return true;
}
/**
* add child element to parant tag
*
* This is used to build enum lists or groups.
* In the definition of the
*
* @access private
* @param mixed child to add, normally is an array
* @param string key of the child
* @param boolean defines whether the element always has more the one child.
* If set to true the first element will be stored in an array
* @return boolean success, currently always true
*/
function _addToParentTag( $child, $key = null, $hasMultiple = false )
{
// get the parent tag
$parent = array_pop( $this->_elStack );
// check if there already are children
if( !$parent["hasChildren"] )
{
$parent["hasChildren"] = true;
/**
* no key defined => just set this as only child
*/
if( $key == null && !$hasMultiple )
{
$parent["children"] = $child;
array_push( $this->_elStack, $parent );
return true;
}
else
{
$parent["children"] = array();
}
}
// if a key has been supplied
if( $key != null )
$parent["children"][$key] = $child;
else
array_push( $parent["children"], $child );
array_push( $this->_elStack, $parent );
return true;
}
/**
* get the name of the parent tag
*
* @access private
* @return string tag name
*/
function _getParentName()
{
$parent = array_pop( $this->_elStack );
array_push( $this->_elStack, $parent );
return $parent['name'];
}
/**
* get the placeholder for an element
*
* @access protected
* @param string element name
* @param string name of the placeholder template
* @return string placeholder
*/
function _getPlaceholderForElement( $element, $template = 'placeholder' )
{
// adjust the case
switch( $this->_placeholder_case )
{
case "upper":
$element = strtoupper( $element );
break;
case "lower":
$element = strtolower( $element );
break;
default:
break;
}
return sprintf( $this->{'_'.$template}, $element );
}
/**
* get the placeholder for a form tag
*
* @access protected
* @param string name of the form
* @param string type (start|end)
* @return string placeholder
*/
function _getPlaceholderForForm( $form, $type )
{
// adjust the case
switch( $this->_placeholder_case )
{
case 'upper':
$form = strtoupper( $form );
break;
case 'lower':
$form = strtolower( $form );
break;
default:
break;
}
$template = '_placeholder_form_'.$type;
return sprintf( $this->$template, $form );
}
/**
* get the one form element
*
* @access public
* @param string element name
* @param string form name, of the parser extracted more than one form
* @return object patForms element
* @deprecated Please use getForm() instead and fetch the elements from the form
*/
function &getFormElement($name, $form = null)
{
$form = &$this->getForm($form);
if (patErrorManager::isError($form)) {
return $form;
}
return $form->getElementByName($name);
}
/**
* get the complete form object
*
* @access public
* @return object patForms object
*/
function &getForm($name = null)
{
if ($name === null && count($this->_elementDefinitions) === 1) {
reset($this->_elementDefinitions);
$name = key($this->_elementDefinitions);
}
if ($name === null) {
if (!is_object($this->_form)) {
require_once PATFORMS_INCLUDE_PATH . '/Collection.php';
$this->_form = &new patForms_Collection();
}
foreach ($this->_elementDefinitions as $formName => $elementDefinitions) {
if ($this->_form->containsForm($formName)) {
continue;
}
$this->_form->addForm($this->_createForm($formName));
}
return $this->_form;
}
if (is_object($this->_form[$name])) {
return $this->_form[$name];
}
if (!isset($this->_elementDefinitions[$name])) {
return patErrorManager::raiseError(PATFORMS_PARSER_ERROR_FORM_NOT_FOUND, 'The form does not exist.');
}
$this->_form[$name] = $this->_createForm($name);
return $this->_form[$name];
}
/**
* create a new form from a form definition
*
* @access protected
* @param string name of the form
* @return patForms
*/
function &_createForm($name)
{
// prepare the attributes
if (!isset($this->_formAttributes[$name])) {
$this->_formAttributes[$name] = array(
'name' => 'form'
);
}
// check for a namespace
$namespace = false;
if (isset($this->_formAttributes[$name]['namespace'])) {
$namespace = $this->_formAttributes[$name]['namespace'];
unset($this->_formAttributes[$name]['namespace']);
}
// check for defined listeners
$listeners = array();
foreach ($this->_formAttributes[$name] as $attName => $attValue) {
if (strpos($attName, 'on') !== 0) {
continue;
}
unset($this->_formAttributes[$name][$attName]);
$attName[2] = strtoupper($attName[2]);
$listeners[$attName] = $attValue;
}
$form = &patForms::createForm($this->_elementDefinitions[$name], $this->_formAttributes[$name]);
if ($namespace != null) {
$form->setNamespace($namespace);
}
if (!empty($listeners)) {
foreach ($listeners as $event => $handler) {
$form->registerEventHandler($event, $handler);
}
}
return $form;
}
/**
* get the HTML code where all form elements have been replaced with variables
*
* @access public
* @return string HTML code
*/
function getHTML()
{
if( $this->_html != null )
return $this->_html;
if( $this->_outputFile == null )
return false;
$this->_html = file_get_contents( $this->_adjustFilename( $this->_outputFile ) );
if( $this->_html === false )
{
return patErrorManager::raiseError(
PATFORMS_PARSER_ERROR_FILE_NOT_FOUND,
"Sourcefile could not be read",
"Sourcefile: " . $this->_sourceFile
);
}
return $this->_html;
}
/**
* create a new parser
*
* Use this method to create a specialty
* parser, that uses a driver.
*
* This could include a parser that is a renderer
* at the same time.
*
* @static
* @access public
* @param mixed driver name
* @return object patForms_Parser
*/
function &createParser( $driver = null )
{
// not based on any driver
if( $driver == null )
{
$parser = &new patForms_Parser;
}
else
{
$driverFile = dirname( __FILE__ ) . "/Parser/{$driver}.php";
if( !@include_once( $driverFile ) )
{
return patErrorManager::raiseError(
PATFORMS_PARSER_ERROR_DRIVER_FILE_NOT_FOUND,
"Driver file '$driverFile' could not be loaded, file not found"
);
}
$parserClass = "patForms_Parser_{$driver}";
if( !class_exists( $parserClass ) )
{
return patErrorManager::raiseError(
PATFORMS_PARSER_ERROR_DRIVER_CLASS_NOT_FOUND,
"Driver file has been loaded correctly, but the according driver class '$parserClass' could not be found"
);
}
$parser = &new $parserClass;
}
return $parser;
}
/**
* Create a form from an FTMPL (Form Template) file.
*
* This method will return a form object, that already
* contains a reference to the parser, that can be used
* as a renderer for the form.
*
* @access public
* @static
* @param string driver for the parser, use null for the default parser
* @param string form template to create the form
* @param string outputfile for the resulting HTML code without form elements
* @return object patForms|object patError
*/
function &createFormFromTemplate( $driver, $formTemplate, $outputFile = null )
{
// create a new parser
$parser =& patForms_Parser::createParser( $driver );
if( patErrorManager::isError( $parser ) ) {
return $parser;
}
$success =& $parser->parseFile( $formTemplate, $outputFile );
if (patErrorManager::isError($success)) {
return $success;
}
$form =& $parser->getForm();
if( patErrorManager::isError( $form ) ) {
return $form;
}
$form->setRenderer( $parser, array( 'includeElements' => true ) );
return $form;
}
/**
* get the namespace of patForms elements from the
* html page.
*
* To make this method work, you will need an
* xmlns:foo="http://www.php-tools.net/patForms/basic"
* attribute in any tag of your page.
*
* @access public
* @param string $html HTML document that should be parsed
* @return string namespace or a patError
*/
function getNamespacePrefix( $html )
{
$regExp = '~xmlns:([^=]+)=["\']http://www.php-tools.net/patForms/basic["\']~im';
$matches = array();
$result = preg_match($regExp, $html, $matches);
if ($result) {
return $matches[1];
}
return patErrorManager::raiseError(
PATFORMS_PARSER_ERROR_NO_NAMESPACE,
"No namespace for patForms declared."
);
}
/**
* Set a static property.
*
* Static properties are stored in an array in a global variable,
* until PHP5 is ready to use.
*
* @static
* @param string property name
* @param string property value
* @see getStaticProperty()
*/
function setStaticProperty($property, &$value)
{
$GLOBALS["_patForms_Parser"][$property] = &$value;
}
/**
* Get a static property.
*
* Static properties are stored in an array in a global variable,
* until PHP5 is ready to use.
*
* @static
* @param string property name
* @return string property value
* @see setStaticProperty()
*/
function &getStaticProperty($property)
{
if (isset($GLOBALS["_patForms_Parser"][$property])) {
return $GLOBALS["_patForms_Parser"][$property];
}
return patErrorManager::raiseWarning(
PATFORMS_PARSER_ERROR_NO_STATIC_PROPERTY,
"Static property '$property' does not exist."
);
}
/**
* Get the id for an element
*
* @access protected
* @param string element name
* @return string
*/
function _getNextId($elementNyme)
{
return $elementNyme;
}
}
?>