Source code for file /pattemplate/patTemplate/Reader.php
Documentation is available at Reader.php
* Base class for patTemplate readers
* $Id: Reader.php 47 2005-09-15 02:55:27Z rhuk $
* This class is able to parse patTemplate tags from any string you hand it over
* It will emulate some kind of SAX parsing by calling start-, end- and CData-handlers.
// Check to ensure this file is within the rest of the framework
define( 'PATTEMPLATE_READER_ERROR_NO_INPUT', 6000 );
define( 'PATTEMPLATE_READER_ERROR_UNKNOWN_TAG', 6001 );
* Invalid tag (missing attribute)
define( 'PATTEMPLATE_READER_ERROR_INVALID_TAG', 6002 );
define( 'PATTEMPLATE_READER_ERROR_NO_CLOSING_TAG', 6003 );
define( 'PATTEMPLATE_READER_ERROR_INVALID_CLOSING_TAG', 6004 );
* Invalid condition specified
define( 'PATTEMPLATE_READER_ERROR_INVALID_CONDITION', 6005 );
* No name has been specified
define( 'PATTEMPLATE_READER_ERROR_NO_NAME_SPECIFIED', 6010 );
* CData in a conditional template
define( 'PATTEMPLATE_READER_NOTICE_INVALID_CDATA_SECTION', 6050 );
* template already exists
define( 'PATTEMPLATE_READER_NOTICE_TEMPLATE_EXISTS', 6051 );
* Base class for patTemplate readers
* This class is able to parse patTemplate tags from any string you hand it over
* It will emulate some kind of SAX parsing by calling start-, end- and CData-handlers.
* reference to the patTemplate object that instantiated the module
* stack for all open elements
* stack for all open templates
* templates that have been found
* start tag for variables
var $_defaultAtts =
array();
* This is used when reading the template content
var $_rootAtts =
array();
var $_inheritAtts =
array();
* name of the first template that has been found
* all data that has been processed
var $_processedData =
null;
var $_currentInput =
null;
var $_functions =
array();
var $_funcAliases =
array();
* set a reference to the patTemplate object that instantiated the reader
* @param object patTemplate object
* read templates from any input
* @abstract must be implemented in the template readers
* @param mixed input to read from.
* This can be a string, a filename, a resource or whatever the derived class needs to read from
* @param array options, not implemented in current versions, but future versions will allow passing of options
* @return array template structure
* load template from any input
* If the a template is loaded, the content will not get
* analyzed but the whole content is returned as a string.
* @abstract must be implemented in the template readers
* @param mixed input to load from.
* This can be a string, a filename, a resource or whatever the derived class needs to read from
* @param array options, not implemented in current versions, but future versions will allow passing of options
* @return string template content
* @param array array containing options
$this->_startTag =
$options['startTag'];
$this->_endTag =
$options['endTag'];
$this->_options =
$options;
if (isset
($options['functionAliases'])) {
$this->_funcAliases =
$options['functionAliases'];
array_map('strtolower', $this->_funcAliases);
* add an alias for a function
* @param string function name
$this->_funcAliases[strtolower($alias)] =
$function;
* set the root attributes
* @param array array containing options
$this->_rootAtts =
$attributes;
* parse templates from string
* @param string string to parse
* @return array templates
function parseString( $string )
* apply input filter before parsing
$string =
$this->_tmpl->applyInputFilters( $string );
$this->_inheritAtts =
array();
$this->_elStack =
array();
$this->_data =
array( '' );
$this->_tmplStack =
array();
$this->_processedData =
'';
$this->_defaultAtts =
$this->_tmpl->getDefaultAttributes();
if( !isset
( $this->_defaultAtts['autoload'] ) ) {
$this->_defaultAtts['autoload'] =
'on';
* create a special root template
$attributes =
$this->_rootAtts;
$attributes['name'] =
'__ptroot';
$rootTemplate =
$this->_initTemplate( $attributes );
unset
( $rootTemplate['isRoot'] );
$patNamespace =
$this->_tmpl->getNamespace();
$patNamespace =
array_map('strtolower', $patNamespace);
$patNamespace =
array(strtolower( $patNamespace ));
$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
$this->_characterData( $tokens[0] );
$fullTag =
$tokens[$i++
];
$closing =
$tokens[$i++
];
$attString =
$tokens[$i++
];
$empty =
substr( $attString, -
1 );
* check, whether it's a known namespace
* currently only the template namespace is possible
if( !in_array($namespace, $patNamespace) ) {
$this->_characterData( $fullTag );
$this->_characterData( $data );
$result =
$this->_endElement( $namespace, $tagname );
$this->_characterData( $data );
* Is empty or opening tag!
$attString =
substr( $attString, 0, -
1 );
$attributes =
$this->_parseAttributes( $attString );
$result =
$this->_startElement( $namespace, $tagname, $attributes );
* check, if the tag is empty
$result =
$this->_endElement( $namespace, $tagname );
$this->_characterData( $data );
$rootTemplate =
array_pop( $this->_tmplStack );
$this->_closeTemplate( $rootTemplate, $this->_data[0] );
* check for tags that are still open
if( $this->_depth >
0 ) {
$this->_createErrorMessage( "No closing tag for {$el['ns']}:{$el['name']} found" )
* parse an attribute string and build an array
* @param string attribute string
* @param array attribute array
function _parseAttributes( $string )
static $entities =
array(
preg_match_all('/([a-zA-Z_0-9]+)="((?:\\\.|[^"\\\])*)"/U', $string, $match);
for ($i =
0; $i <
count($match[1]); $i++
) {
$attributes[strtolower( $match[1][$i] )] =
strtr( (string)
$match[2][$i], $entities );
* @param string element name
* @param array attributes
function _startElement( $ns, $name, $attributes )
'attributes' =>
$attributes,
$this->_data[$this->_depth] =
'';
$result =
$this->_initTemplate( $attributes );
$result =
$this->_initSubTemplate( $attributes );
$result =
$this->_initLink( $attributes );
if (isset
($this->_funcAliases[strtolower($name)])) {
if( !$this->_tmpl->moduleExists( 'Function', $name ) ) {
if (isset
($this->_options['defaultFunction']) &&
!empty($this->_options['defaultFunction'])) {
$attributes['_originalTag'] =
$name;
$name =
ucfirst($this->_options['defaultFunction']);
$this->_createErrorMessage( "Unknown tag {$ns}:{$name}." )
'attributes' =>
$attributes
* @param string element name
function _endElement( $ns, $name )
$data =
$this->_getCData();
if( $el['name'] !=
$name ||
$el['ns'] !=
$ns ) {
$this->_createErrorMessage( "Invalid closing tag {$ns}:{$name}, {$el['ns']}:{$el['name']} expected" )
$this->_closeTemplate( $tmpl, $data );
$this->_closeSubTemplate( $tmpl, $data );
$this->_closeLink( $tmpl );
$this->_handleVariable( $el['attributes'], $data );
$this->_handleComment( $el['attributes'], $data );
$name =
ucfirst( $tmpl['function'] );
if( !isset
( $this->_functions[$name] ) ) {
$this->_functions[$name] =
$this->_tmpl->loadModule( 'Function', $name );
$this->_functions[$name]->setReader( $this );
$result =
$this->_functions[$name]->call( $tmpl['attributes'], $data );
$this->_characterData( $result, false );
function _characterData( $data, $readFromTemplate =
true )
$this->_data[$this->_depth] .=
$data;
$this->_processedData .=
$data;
* @param array attributes
* @return boolean true on success
function _initLink( $attributes )
if( !isset
( $attributes['src'] ) ) {
$this->_createErrorMessage( "Attribute 'src' missing for link" )
'src' =>
$attributes['src'],
* It will be added to the dependecies of the parent template.
* @param array template definition for the link
function _closeLink( $tmpl )
* add it to the dependencies
if( !empty( $this->_tmplStack ) )
$this->_addToParentTag( 'dependencies', strtolower( $tmpl['src'] ) );
$this->_characterData( sprintf( "%sTMPL:%s%s", $this->_startTag, strtoupper( $tmpl['src'] ), $this->_endTag ) );
* @param array attributes
* @return boolean true on success
function _initTemplate( $attributes )
* build name for the template
if (!isset
( $attributes['name'] )) {
$name =
$this->_buildTemplateName();
unset
( $attributes['name'] );
$this->_createErrorMessage( "Template $name already exists" ),
if( isset
( $attributes['maxloop'] ) ) {
if (!isset
( $attributes['parent'] )) {
$attributes['parent'] =
$this->_getFromParentTemplate( 'name' );
$attributes =
$this->_prepareTmplAttributes( $attributes, $name );
'whitespace' =>
$attributes['whitespace'],
'unusedvars' =>
$attributes['unusedvars'],
'autoclear' =>
$attributes['autoclear']
'attributes' =>
$attributes,
'dependencies' =>
array(),
'input' =>
$this->_name.
'://'.
$this->_currentInput
if( $this->_root ==
null ) {
switch( $attributes['type'] ) {
$tmpl['subtemplates'] =
array();
* @param array attributes
* @param string template name (only used for error messages)
* @return array attributes
function _prepareTmplAttributes( $attributes, $templatename )
if( isset
( $attributes['__prepared'] ) &&
$attributes['__prepared'] ===
true ) {
$attributes =
$this->_inheritAttributes( $attributes );
$attributes['type'] =
strtolower( $attributes['type'] );
if( !isset
( $attributes['rowoffset'] ) ) {
$attributes['rowoffset'] =
1;
if( !isset
( $attributes['addsystemvars'] ) ) {
$attributes['addsystemvars'] =
false;
switch ($attributes['addsystemvars']) {
$attributes['addsystemvars'] =
'boolean';
$attributes['addsystemvars'] =
'integer';
$attributes['addsystemvars'] =
false;
if( isset
( $attributes['src'] ) ) {
if( !isset
( $attributes['parse'] ) )
$attributes['parse'] =
'on';
if( !isset
( $attributes['reader'] ) )
$attributes['reader'] =
$this->getName();
if( !isset
( $attributes['autoload'] ) )
$attributes['autoload'] =
$this->_defaultAtts['autoload'];
if (isset
($attributes['relative']) &&
strtolower($attributes['relative'] ===
'yes')) {
$attributes['relative'] =
false;
if( isset
( $attributes['varscope'] ) ) {
if( $attributes['varscope'] ===
'__parent' ) {
$attributes['varscope'] =
$this->_getFromParentTemplate( 'name' );
$attributes['varscope'] =
strtolower( $attributes['varscope'] );
if (strstr($attributes['varscope'], ',')) {
$attributes['varscope'] =
array_map('trim', explode(',', $attributes['varscope']));
switch( $attributes['type'] ) {
* validate condition template
if( !isset
( $attributes['conditionvar'] ) ) {
$this->_createErrorMessage( "Attribute 'conditionvar' missing for $templatename" )
$attributes['conditionvar'] =
strtoupper( $attributes['conditionvar'] );
if( strstr( $attributes['conditionvar'], '.' ) ) {
list
( $attributes['conditiontmpl'], $attributes['conditionvar'] ) =
explode( '.', $attributes['conditionvar'] );
$attributes['conditiontmpl'] =
strtolower( $attributes['conditiontmpl'] );
$attributes['autoclear'] =
'yes';
if (!isset
( $attributes['useglobals'] )) {
$attributes['useglobals'] =
'no';
* validate simplecondition template
if( !isset
( $attributes['requiredvars'] ) ) {
$this->_createErrorMessage( "Attribute 'requiredvars' missing for $templatename" )
$attributes['requiredvars'] =
array();
foreach( $tmp as $var ) {
array_push( $attributes['requiredvars'], array( $templatename, $var, $val ) );
$attributes['autoclear'] =
'yes';
* oddeven => switch to new modulo syntax
$attributes['type'] =
'modulo';
$attributes['modulo'] =
2;
$attributes['autoclear'] =
'yes';
* modulo => requires a module attribute
if( !isset
( $attributes['modulo'] ) ) {
$this->_createErrorMessage( "Attribute 'modulo' missing for $templatename" )
$attributes['autoclear'] =
'yes';
* standard template => do nothing
$this->_createErrorMessage( "Unknown value for attribute type: {$attributes['type']}" )
$attributes['__prepared'] =
true;
* @return string new template name
function _buildTemplateName()
* close the current template
* @return boolean true on success
function _closeTemplate( $tmpl, $data )
$data =
$this->_adjustWhitespace( $data, $tmpl['attributes']['whitespace'] );
* check for special templates
switch( $tmpl['attributes']['type'] )
* check for whitespace in conditional templates
if( trim( $data ) !=
'' ) {
$this->_createErrorMessage( sprintf( 'No cdata is allowed inside a template of type %s (cdata was found in %s)', $tmpl['attributes']['type'], $tmpl['name'] ) )
$tmpl['content'] =
$data;
if( !isset
( $tmpl['attributes']['src'] ) ) {
* add it to the dependencies
if( !empty( $this->_tmplStack ) ) {
$this->_addToParentTag( 'dependencies', $name );
if( isset
( $tmpl['attributes']['placeholder'] ) ) {
if( $this->shouldMaintainBc() &&
$tmpl['attributes']['placeholder'] ===
'none' ) {
$tmpl['attributes']['placeholder'] =
'__none';
if( $tmpl['attributes']['placeholder'] !==
'__none' ) {
$this->_characterData( $this->_startTag.
(strtoupper( $tmpl['attributes']['placeholder'] ) ).
$this->_endTag );
$this->_characterData( sprintf( "%sTMPL:%s%s", $this->_startTag, strtoupper( $name ), $this->_endTag ) );
* create a new sub-template
* @param array attributes
* @return boolean true on success
function _initSubTemplate( $attributes )
* has to be embedded in a 'tmpl' tag
if (!$this->_parentTagIs('tmpl')) {
$this->_createErrorMessage( 'A subtemplate is only allowed in a TMPL tag' )
* needs a condition attribute
if (!isset
( $attributes['condition'] )) {
PATTEMPLATE_READER_ERROR_NO_CONDITION_SPECIFIED,
$this->_createErrorMessage( 'Missing \'condition\' attribute for subtemplate' )
$regexp =
'/^'.
$this->_startTag.
'([^a-z]+[^\\\])'.
$this->_endTag.
'$/U';
if (preg_match($regexp, $attributes['condition'], $matches)) {
$attributes['var'] =
$matches[1];
$attributes['condition'] =
'__' .
$attributes['condition'];
if( $attributes['condition'] ==
'__odd' ) {
$attributes['condition'] =
1;
} elseif( $attributes['condition'] ==
'__even' ) {
$attributes['condition'] =
0;
if ($parent['attributes']['type'] ==
'modulo') {
if( preg_match( '/^\d$/', $attributes['condition'] ) ) {
if( (integer)
$attributes['condition'] >=
$parent['attributes']['modulo'] ) {
$this->_createErrorMessage( 'Condition may only be between 0 and '.
($parent['attributes']['modulo']-
1) )
$attributes =
$this->_inheritAttributes( $attributes );
$condition =
$attributes['condition'];
unset
( $attributes['condition'] );
'condition' =>
$condition,
'attributes' =>
$attributes,
'dependencies' =>
array()
* @return boolean true on success
function _closeSubTemplate( $subTmpl, $data )
$data =
$this->_adjustWhitespace( $data, $subTmpl['attributes']['whitespace'] );
$subTmpl['data'] =
$data;
$condition =
$subTmpl['condition'];
unset
( $subTmpl['condition'] );
$this->_addToParentTemplate( 'subtemplates',
* @param array attributes of the var tag
* @param string cdata between the tags (will be used as default)
* @return boolean true on success
function _handleVariable( $attributes, $data )
if( !isset
( $attributes['name'] ) ) {
$this->_createErrorMessage( 'Variable needs a name attribute' )
unset
( $attributes['name'] );
* use data as default value
if( isset
( $attributes['default'] ) ) {
$data =
$attributes['default'];
$specs['default'] =
$data;
unset
( $attributes['default'] );
} elseif (!empty( $data )) {
$specs['default'] =
$data;
* add it to template, if it's not hidden
if (!isset
( $attributes['hidden'] ) ||
$attributes['hidden'] ==
'no') {
$this->_characterData( $this->_startTag .
strtoupper( $name ) .
$this->_endTag );
if( isset
( $attributes['hidden'] ) ) {
unset
( $attributes['hidden'] );
* copy value from any other variable
if (isset
( $attributes['copyfrom'] )) {
$specs['copyfrom'] =
strtoupper( $attributes['copyfrom'] );
if (strstr( $specs['copyfrom'], '.' )) {
$specs['copyfrom'] =
explode( '.', $specs['copyfrom'] );
$specs['copyfrom'][0] =
strtolower( $specs['copyfrom'][0] );
unset
( $attributes['copyfrom'] );
if( isset
( $attributes['modifier'] ) ) {
$modifier =
$attributes['modifier'];
unset
( $attributes['modifier'] );
$type = isset
( $attributes['modifiertype'] ) ?
$attributes['modifiertype'] :
'auto';
if( isset
( $attributes['modifiertype'] ) )
unset
( $attributes['modifiertype'] );
$specs['modifier'] =
array( 'mod' =>
$modifier, 'type' =>
$type, 'params' =>
$attributes );
$this->_addToParentTemplate(
* @param array attributes of the comment tag
* @param string cdata between the tags (will be used as default)
* @return boolean true on success
function _handleComment( $attributes, $data )
$this->_addToParentTag( 'comments', $data );
* get the character data of the element
if( $this->_depth ==
0 ) {
return $this->_data[$this->_depth];
* add to a property of the parent template
* @param string property to add to
* @param mixed value to add
function _addToParentTemplate( $property, $value, $key =
null )
$cnt =
count( $this->_tmplStack );
if ($this->_tmplStack[$pos]['type'] !=
'tmpl') {
if (!in_array( $value, $this->_tmplStack[$pos][$property] )) {
array_push( $this->_tmplStack[$pos][$property], $value );
$this->_tmplStack[$pos][$property][$key] =
$value;
* get a property of the parent template
* @param string property to add to
* @return mixed value to add
function _getFromParentTemplate( $property )
$cnt =
count( $this->_tmplStack );
if( $this->_tmplStack[$pos]['type'] !=
'tmpl' ) {
if (isset
( $this->_tmplStack[$pos][$property] )) {
return $this->_tmplStack[$pos][$property];
* add to a property of the parent tag
* @param string property to add to
* @param mixed value to add
function _addToParentTag( $property, $value, $key =
null )
$cnt =
count( $this->_tmplStack );
if (!in_array( $value, $this->_tmplStack[$pos][$property] )) {
array_push( $this->_tmplStack[$pos][$property], $value );
$this->_tmplStack[$pos][$property][$key] =
$value;
* adjust whitespace in a CData block
* @param string behaviour
function _adjustWhitespace( $data, $behaviour )
* inherit attributes from the parent template
* The following attributes are inherited automatically:
* @param array attributes
* @param array attributes with inherited attributes
* @return array new attribute collection
function _inheritAttributes( $attributes )
if (!empty( $this->_inheritAtts )) {
$parent =
end( $this->_inheritAtts );
'whitespace' =>
$this->_defaultAtts['whitespace'],
'unusedvars' =>
$this->_defaultAtts['unusedvars'],
'autoclear' =>
$this->_defaultAtts['autoclear']
* checks, whether the parent tag is of a certain type
* This is needed to ensure, that subtemplates are only
* placed inside a template
* @param string type (tmpl, sub, var, link)
function _parentTagIs( $type )
if( $parent['type'] ==
$type ) {
* get the current line number
* @return integer line number
function _getCurrentLine()
* create an error message
* This method takes an error messages and appends the
* current line number as well as a pointer to the input
* @param string base error message
* @return strin error message
function _createErrorMessage( $msg )
return $this->_currentInput;
* tests whether the reader should maintain backwards compatibility
* If enabled, you can still use 'default', 'empty', 'odd' and 'even'
* instead of '__default', '__empty', etc.
* This will be disabled by default in future versions.
if (!isset
( $this->_options['maintainBc'] )) {
return $this->_options['maintainBc'];
* returns, whether the reader currently is in use
* get the template root for this reader
if (!isset
($this->_options['root'])) {
if (isset
($this->_options['root'][$this->_name])) {
return $this->_options['root'][$this->_name];
if (isset
($this->_options['root']['__default'])) {
return $this->_options['root']['__default'];