mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
ENHANCEMENT: Add Up support
This commit is contained in:
parent
829bf23192
commit
71892085fc
@ -67,12 +67,6 @@ class SSTemplateParser extends Parser {
|
|||||||
*/
|
*/
|
||||||
protected $includeDebuggingComments = false;
|
protected $includeDebuggingComments = false;
|
||||||
|
|
||||||
function construct($name) {
|
|
||||||
$result = parent::construct($name);
|
|
||||||
$result['tags'] = array();
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Word: / [A-Za-z_] [A-Za-z0-9_]* / */
|
/* Word: / [A-Za-z_] [A-Za-z0-9_]* / */
|
||||||
function match_Word ($substack = array()) {
|
function match_Word ($substack = array()) {
|
||||||
$result = array("name"=>"Word", "text"=>"");
|
$result = array("name"=>"Word", "text"=>"");
|
||||||
@ -169,7 +163,7 @@ class SSTemplateParser extends Parser {
|
|||||||
if (isset($res['php'])) $res['php'] .= ', ';
|
if (isset($res['php'])) $res['php'] .= ', ';
|
||||||
else $res['php'] = '';
|
else $res['php'] = '';
|
||||||
|
|
||||||
$res['php'] .= ($sub['ArgumentMode'] == 'default') ? $sub['string_php'] : $sub['php'];
|
$res['php'] .= ($sub['ArgumentMode'] == 'default') ? $sub['string_php'] : str_replace('$$FINAL', 'XML_val', $sub['php']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Call: Method:Word ( "(" < :CallArguments? > ")" )? */
|
/* Call: Method:Word ( "(" < :CallArguments? > ")" )? */
|
||||||
@ -354,7 +348,7 @@ class SSTemplateParser extends Parser {
|
|||||||
|
|
||||||
|
|
||||||
function Lookup__construct(&$res) {
|
function Lookup__construct(&$res) {
|
||||||
$res['php'] = '$item';
|
$res['php'] = '$scope';
|
||||||
$res['LookupSteps'] = array();
|
$res['LookupSteps'] = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -381,7 +375,7 @@ class SSTemplateParser extends Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Lookup_LastLookupStep(&$res, $sub) {
|
function Lookup_LastLookupStep(&$res, $sub) {
|
||||||
$this->Lookup_AddLookupStep($res, $sub, 'XML_val');
|
$this->Lookup_AddLookupStep($res, $sub, '$$FINAL');
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SimpleInjection: '$' :Lookup */
|
/* SimpleInjection: '$' :Lookup */
|
||||||
@ -474,7 +468,7 @@ class SSTemplateParser extends Parser {
|
|||||||
|
|
||||||
|
|
||||||
function Injection_STR(&$res, $sub) {
|
function Injection_STR(&$res, $sub) {
|
||||||
$res['php'] = '$val .= '. $sub['Lookup']['php'] . ';';
|
$res['php'] = '$val .= '. str_replace('$$FINAL', 'XML_val', $sub['Lookup']['php']) . ';';
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DollarMarkedLookup: SimpleInjection */
|
/* DollarMarkedLookup: SimpleInjection */
|
||||||
@ -777,11 +771,11 @@ class SSTemplateParser extends Parser {
|
|||||||
function Comparison_Argument(&$res, $sub) {
|
function Comparison_Argument(&$res, $sub) {
|
||||||
if ($sub['ArgumentMode'] == 'default') {
|
if ($sub['ArgumentMode'] == 'default') {
|
||||||
if (isset($res['php'])) $res['php'] .= $sub['string_php'];
|
if (isset($res['php'])) $res['php'] .= $sub['string_php'];
|
||||||
else $res['php'] = $sub['lookup_php'];
|
else $res['php'] = str_replace('$$FINAL', 'XML_val', $sub['lookup_php']);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (!isset($res['php'])) $res['php'] = '';
|
if (!isset($res['php'])) $res['php'] = '';
|
||||||
$res['php'] .= $sub['php'];
|
$res['php'] .= str_replace('$$FINAL', 'XML_val', $sub['php']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -811,7 +805,7 @@ class SSTemplateParser extends Parser {
|
|||||||
$php = ($sub['ArgumentMode'] == 'default' ? $sub['lookup_php'] : $sub['php']);
|
$php = ($sub['ArgumentMode'] == 'default' ? $sub['lookup_php'] : $sub['php']);
|
||||||
// TODO: kinda hacky - maybe we need a way to pass state down the parse chain so
|
// TODO: kinda hacky - maybe we need a way to pass state down the parse chain so
|
||||||
// Lookup_LastLookupStep and Argument_BareWord can produce hasValue instead of XML_val
|
// Lookup_LastLookupStep and Argument_BareWord can produce hasValue instead of XML_val
|
||||||
$res['php'] = str_replace('->XML_val', '->hasValue', $php);
|
$res['php'] = str_replace('$$FINAL', 'hasValue', $php);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1519,9 +1513,9 @@ class SSTemplateParser extends Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is an example of a block handler function. This one handles the control tag.
|
* This is an example of a block handler function. This one handles the loop tag.
|
||||||
*/
|
*/
|
||||||
function ClosedBlock_Handle_Control(&$res) {
|
function ClosedBlock_Handle_Loop(&$res) {
|
||||||
if ($res['ArgumentCount'] != 1) {
|
if ($res['ArgumentCount'] != 1) {
|
||||||
throw new SSTemplateParseException('Either no or too many arguments in control block. Must be one argument only.', $this);
|
throw new SSTemplateParseException('Either no or too many arguments in control block. Must be one argument only.', $this);
|
||||||
}
|
}
|
||||||
@ -1531,11 +1525,39 @@ class SSTemplateParser extends Parser {
|
|||||||
throw new SSTemplateParseException('Control block cant take string as argument.', $this);
|
throw new SSTemplateParseException('Control block cant take string as argument.', $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
$on = str_replace('->XML_val', '->obj', ($arg['ArgumentMode'] == 'default') ? $arg['lookup_php'] : $arg['php']);
|
$on = str_replace('$$FINAL', 'obj', ($arg['ArgumentMode'] == 'default') ? $arg['lookup_php'] : $arg['php']);
|
||||||
return
|
return
|
||||||
'array_push($itemStack, $item); if($loop = '.$on.') foreach($loop as $key => $item) {' . PHP_EOL .
|
$on . '; $scope->pushScope(); while (($key = $scope->next()) !== false) {' . PHP_EOL .
|
||||||
$res['Template']['php'] . PHP_EOL .
|
$res['Template']['php'] . PHP_EOL .
|
||||||
'} $item = array_pop($itemStack); ';
|
'}; $scope->popScope(); ';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The deprecated closed block handler for control blocks
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
function ClosedBlock_Handle_Control(&$res) {
|
||||||
|
return $this->ClosedBlock_Handle_Loop($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The closed block handler for with blocks
|
||||||
|
*/
|
||||||
|
function ClosedBlock_Handle_With(&$res) {
|
||||||
|
if ($res['ArgumentCount'] != 1) {
|
||||||
|
throw new SSTemplateParseException('Either no or too many arguments in with block. Must be one argument only.', $this);
|
||||||
|
}
|
||||||
|
|
||||||
|
$arg = $res['Arguments'][0];
|
||||||
|
if ($arg['ArgumentMode'] == 'string') {
|
||||||
|
throw new SSTemplateParseException('Control block cant take string as argument.', $this);
|
||||||
|
}
|
||||||
|
|
||||||
|
$on = str_replace('$$FINAL', 'obj', ($arg['ArgumentMode'] == 'default') ? $arg['lookup_php'] : $arg['php']);
|
||||||
|
return
|
||||||
|
$on . '; $scope->pushScope();' . PHP_EOL .
|
||||||
|
$res['Template']['php'] . PHP_EOL .
|
||||||
|
'; $scope->popScope(); ';
|
||||||
}
|
}
|
||||||
|
|
||||||
/* OpenBlock: '<%' < !NotBlockTag BlockName:Word ( [ :BlockArguments ] )? > '%>' */
|
/* OpenBlock: '<%' < !NotBlockTag BlockName:Word ( [ :BlockArguments ] )? > '%>' */
|
||||||
@ -1642,12 +1664,12 @@ class SSTemplateParser extends Parser {
|
|||||||
if($this->includeDebuggingComments) { // Add include filename comments on dev sites
|
if($this->includeDebuggingComments) { // Add include filename comments on dev sites
|
||||||
return
|
return
|
||||||
'$val .= \'<!-- include '.$php.' -->\';'. "\n".
|
'$val .= \'<!-- include '.$php.' -->\';'. "\n".
|
||||||
'$val .= SSViewer::parse_template('.$php.', $item);'. "\n".
|
'$val .= SSViewer::parse_template('.$php.', $scope->getItem());'. "\n".
|
||||||
'$val .= \'<!-- end include '.$php.' -->\';'. "\n";
|
'$val .= \'<!-- end include '.$php.' -->\';'. "\n";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return
|
return
|
||||||
'$val .= SSViewer::execute_template('.$php.', $item);'. "\n";
|
'$val .= SSViewer::execute_template('.$php.', $scope->getItem());'. "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1655,7 +1677,7 @@ class SSTemplateParser extends Parser {
|
|||||||
* This is an open block handler, for the <% debug %> utility tag
|
* This is an open block handler, for the <% debug %> utility tag
|
||||||
*/
|
*/
|
||||||
function OpenBlock_Handle_Debug(&$res) {
|
function OpenBlock_Handle_Debug(&$res) {
|
||||||
if ($res['ArgumentCount'] == 0) return 'Debug::show($item);';
|
if ($res['ArgumentCount'] == 0) return '$scope->debug();';
|
||||||
else if ($res['ArgumentCount'] == 1) {
|
else if ($res['ArgumentCount'] == 1) {
|
||||||
$arg = $res['Arguments'][0];
|
$arg = $res['Arguments'][0];
|
||||||
|
|
||||||
|
@ -81,12 +81,6 @@ class SSTemplateParser extends Parser {
|
|||||||
*/
|
*/
|
||||||
protected $includeDebuggingComments = false;
|
protected $includeDebuggingComments = false;
|
||||||
|
|
||||||
function construct($name) {
|
|
||||||
$result = parent::construct($name);
|
|
||||||
$result['tags'] = array();
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!* SSTemplateParser
|
/*!* SSTemplateParser
|
||||||
|
|
||||||
Word: / [A-Za-z_] [A-Za-z0-9_]* /
|
Word: / [A-Za-z_] [A-Za-z0-9_]* /
|
||||||
@ -107,7 +101,7 @@ class SSTemplateParser extends Parser {
|
|||||||
if (isset($res['php'])) $res['php'] .= ', ';
|
if (isset($res['php'])) $res['php'] .= ', ';
|
||||||
else $res['php'] = '';
|
else $res['php'] = '';
|
||||||
|
|
||||||
$res['php'] .= ($sub['ArgumentMode'] == 'default') ? $sub['string_php'] : $sub['php'];
|
$res['php'] .= ($sub['ArgumentMode'] == 'default') ? $sub['string_php'] : str_replace('$$FINAL', 'XML_val', $sub['php']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!*
|
/*!*
|
||||||
@ -128,7 +122,7 @@ class SSTemplateParser extends Parser {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
function Lookup__construct(&$res) {
|
function Lookup__construct(&$res) {
|
||||||
$res['php'] = '$item';
|
$res['php'] = '$scope';
|
||||||
$res['LookupSteps'] = array();
|
$res['LookupSteps'] = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +149,7 @@ class SSTemplateParser extends Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Lookup_LastLookupStep(&$res, $sub) {
|
function Lookup_LastLookupStep(&$res, $sub) {
|
||||||
$this->Lookup_AddLookupStep($res, $sub, 'XML_val');
|
$this->Lookup_AddLookupStep($res, $sub, '$$FINAL');
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!*
|
/*!*
|
||||||
@ -168,7 +162,7 @@ class SSTemplateParser extends Parser {
|
|||||||
Injection: BracketInjection | SimpleInjection
|
Injection: BracketInjection | SimpleInjection
|
||||||
*/
|
*/
|
||||||
function Injection_STR(&$res, $sub) {
|
function Injection_STR(&$res, $sub) {
|
||||||
$res['php'] = '$val .= '. $sub['Lookup']['php'] . ';';
|
$res['php'] = '$val .= '. str_replace('$$FINAL', 'XML_val', $sub['Lookup']['php']) . ';';
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!*
|
/*!*
|
||||||
@ -261,11 +255,11 @@ class SSTemplateParser extends Parser {
|
|||||||
function Comparison_Argument(&$res, $sub) {
|
function Comparison_Argument(&$res, $sub) {
|
||||||
if ($sub['ArgumentMode'] == 'default') {
|
if ($sub['ArgumentMode'] == 'default') {
|
||||||
if (isset($res['php'])) $res['php'] .= $sub['string_php'];
|
if (isset($res['php'])) $res['php'] .= $sub['string_php'];
|
||||||
else $res['php'] = $sub['lookup_php'];
|
else $res['php'] = str_replace('$$FINAL', 'XML_val', $sub['lookup_php']);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (!isset($res['php'])) $res['php'] = '';
|
if (!isset($res['php'])) $res['php'] = '';
|
||||||
$res['php'] .= $sub['php'];
|
$res['php'] .= str_replace('$$FINAL', 'XML_val', $sub['php']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,7 +283,7 @@ class SSTemplateParser extends Parser {
|
|||||||
$php = ($sub['ArgumentMode'] == 'default' ? $sub['lookup_php'] : $sub['php']);
|
$php = ($sub['ArgumentMode'] == 'default' ? $sub['lookup_php'] : $sub['php']);
|
||||||
// TODO: kinda hacky - maybe we need a way to pass state down the parse chain so
|
// TODO: kinda hacky - maybe we need a way to pass state down the parse chain so
|
||||||
// Lookup_LastLookupStep and Argument_BareWord can produce hasValue instead of XML_val
|
// Lookup_LastLookupStep and Argument_BareWord can produce hasValue instead of XML_val
|
||||||
$res['php'] = str_replace('->XML_val', '->hasValue', $php);
|
$res['php'] = str_replace('$$FINAL', 'hasValue', $php);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -429,9 +423,9 @@ class SSTemplateParser extends Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is an example of a block handler function. This one handles the control tag.
|
* This is an example of a block handler function. This one handles the loop tag.
|
||||||
*/
|
*/
|
||||||
function ClosedBlock_Handle_Control(&$res) {
|
function ClosedBlock_Handle_Loop(&$res) {
|
||||||
if ($res['ArgumentCount'] != 1) {
|
if ($res['ArgumentCount'] != 1) {
|
||||||
throw new SSTemplateParseException('Either no or too many arguments in control block. Must be one argument only.', $this);
|
throw new SSTemplateParseException('Either no or too many arguments in control block. Must be one argument only.', $this);
|
||||||
}
|
}
|
||||||
@ -441,11 +435,39 @@ class SSTemplateParser extends Parser {
|
|||||||
throw new SSTemplateParseException('Control block cant take string as argument.', $this);
|
throw new SSTemplateParseException('Control block cant take string as argument.', $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
$on = str_replace('->XML_val', '->obj', ($arg['ArgumentMode'] == 'default') ? $arg['lookup_php'] : $arg['php']);
|
$on = str_replace('$$FINAL', 'obj', ($arg['ArgumentMode'] == 'default') ? $arg['lookup_php'] : $arg['php']);
|
||||||
return
|
return
|
||||||
'array_push($itemStack, $item); if($loop = '.$on.') foreach($loop as $key => $item) {' . PHP_EOL .
|
$on . '; $scope->pushScope(); while (($key = $scope->next()) !== false) {' . PHP_EOL .
|
||||||
$res['Template']['php'] . PHP_EOL .
|
$res['Template']['php'] . PHP_EOL .
|
||||||
'} $item = array_pop($itemStack); ';
|
'}; $scope->popScope(); ';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The deprecated closed block handler for control blocks
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
function ClosedBlock_Handle_Control(&$res) {
|
||||||
|
return $this->ClosedBlock_Handle_Loop($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The closed block handler for with blocks
|
||||||
|
*/
|
||||||
|
function ClosedBlock_Handle_With(&$res) {
|
||||||
|
if ($res['ArgumentCount'] != 1) {
|
||||||
|
throw new SSTemplateParseException('Either no or too many arguments in with block. Must be one argument only.', $this);
|
||||||
|
}
|
||||||
|
|
||||||
|
$arg = $res['Arguments'][0];
|
||||||
|
if ($arg['ArgumentMode'] == 'string') {
|
||||||
|
throw new SSTemplateParseException('Control block cant take string as argument.', $this);
|
||||||
|
}
|
||||||
|
|
||||||
|
$on = str_replace('$$FINAL', 'obj', ($arg['ArgumentMode'] == 'default') ? $arg['lookup_php'] : $arg['php']);
|
||||||
|
return
|
||||||
|
$on . '; $scope->pushScope();' . PHP_EOL .
|
||||||
|
$res['Template']['php'] . PHP_EOL .
|
||||||
|
'; $scope->popScope(); ';
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!*
|
/*!*
|
||||||
@ -492,12 +514,12 @@ class SSTemplateParser extends Parser {
|
|||||||
if($this->includeDebuggingComments) { // Add include filename comments on dev sites
|
if($this->includeDebuggingComments) { // Add include filename comments on dev sites
|
||||||
return
|
return
|
||||||
'$val .= \'<!-- include '.$php.' -->\';'. "\n".
|
'$val .= \'<!-- include '.$php.' -->\';'. "\n".
|
||||||
'$val .= SSViewer::parse_template('.$php.', $item);'. "\n".
|
'$val .= SSViewer::parse_template('.$php.', $scope->getItem());'. "\n".
|
||||||
'$val .= \'<!-- end include '.$php.' -->\';'. "\n";
|
'$val .= \'<!-- end include '.$php.' -->\';'. "\n";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return
|
return
|
||||||
'$val .= SSViewer::execute_template('.$php.', $item);'. "\n";
|
'$val .= SSViewer::execute_template('.$php.', $scope->getItem());'. "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -505,7 +527,7 @@ class SSTemplateParser extends Parser {
|
|||||||
* This is an open block handler, for the <% debug %> utility tag
|
* This is an open block handler, for the <% debug %> utility tag
|
||||||
*/
|
*/
|
||||||
function OpenBlock_Handle_Debug(&$res) {
|
function OpenBlock_Handle_Debug(&$res) {
|
||||||
if ($res['ArgumentCount'] == 0) return 'Debug::show($item);';
|
if ($res['ArgumentCount'] == 0) return '$scope->debug();';
|
||||||
else if ($res['ArgumentCount'] == 1) {
|
else if ($res['ArgumentCount'] == 1) {
|
||||||
$arg = $res['Arguments'][0];
|
$arg = $res['Arguments'][0];
|
||||||
|
|
||||||
|
@ -1,4 +1,129 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tracks the current scope for an SSViewer instance. It has three goals:
|
||||||
|
* - Handle entering & leaving sub-scopes in loops and withs
|
||||||
|
* - Track Up and Top
|
||||||
|
* - (As a side effect) Inject data that needs to be available globally (used to live in ViewableData)
|
||||||
|
*
|
||||||
|
* In order to handle up, rather than tracking it using a tree, which would involve constructing new objects
|
||||||
|
* for each step, we use indexes into the itemStack (which already has to exist).
|
||||||
|
*
|
||||||
|
* Each item has three indexes associated with it
|
||||||
|
*
|
||||||
|
* - Pop. Which item should become the scope once the current scope is popped out of
|
||||||
|
* - Up. Which item is up from this item
|
||||||
|
* - Current. Which item is the first time this object has appeared in the stack
|
||||||
|
*
|
||||||
|
* We also keep the index of the current starting point for lookups. A lookup is a sequence of obj calls -
|
||||||
|
* when in a loop or with tag the end result becomes the new scope, but for injections, we throw away the lookup
|
||||||
|
* and revert back to the original scope once we've got the value we're after
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class SSViewer_DataPresenter {
|
||||||
|
|
||||||
|
// The stack of previous "global" items
|
||||||
|
// And array of item, itemIterator, pop_index, up_index, current_index
|
||||||
|
private $itemStack = array();
|
||||||
|
|
||||||
|
private $item; // The current "global" item (the one any lookup starts from)
|
||||||
|
private $itemIterator; // If we're looping over the current "global" item, here's the iterator that tracks with item we're up to
|
||||||
|
|
||||||
|
private $popIndex; // A pointer into the item stack for which item should be scope on the next pop call
|
||||||
|
private $upIndex; // A pointer into the item stack for which item is "up" from this one
|
||||||
|
private $currentIndex; // A pointer into the item stack for which item is this one (or null if not in stack yet)
|
||||||
|
|
||||||
|
private $localIndex;
|
||||||
|
|
||||||
|
function __construct($item){
|
||||||
|
$this->item = $item;
|
||||||
|
$this->localIndex=0;
|
||||||
|
$this->itemStack[] = array($this->item, null, null, null, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getItem(){
|
||||||
|
return $this->itemIterator ? $this->itemIterator->current() : $this->item;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetLocalScope(){
|
||||||
|
list($this->item, $this->itemIterator, $this->popIndex, $this->upIndex, $this->currentIndex) = $this->itemStack[$this->localIndex];
|
||||||
|
array_splice($this->itemStack, $this->localIndex+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function obj($name){
|
||||||
|
|
||||||
|
switch ($name) {
|
||||||
|
case 'Up':
|
||||||
|
list($this->item, $this->itemIterator, $unused2, $this->upIndex, $this->currentIndex) = $this->itemStack[$this->upIndex];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Top':
|
||||||
|
list($this->item, $this->itemIterator, $unused2, $this->upIndex, $this->currentIndex) = $this->itemStack[0];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$on = $this->itemIterator ? $this->itemIterator->current() : $this->item;
|
||||||
|
|
||||||
|
$this->item = call_user_func_array(array($on, 'obj'), func_get_args());
|
||||||
|
$this->itemIterator = null;
|
||||||
|
$this->upIndex = $this->currentIndex ? $this->currentIndex : count($this->itemStack)-1;
|
||||||
|
$this->currentIndex = count($this->itemStack);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->itemStack[] = array($this->item, $this->itemIterator, null, $this->upIndex, $this->currentIndex);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushScope(){
|
||||||
|
$newLocalIndex = count($this->itemStack)-1;
|
||||||
|
|
||||||
|
$this->popIndex = $this->itemStack[$newLocalIndex][2] = $this->localIndex;
|
||||||
|
$this->localIndex = $newLocalIndex;
|
||||||
|
|
||||||
|
// We normally keep any previous itemIterator around, so local $Up calls reference the right element. But
|
||||||
|
// once we enter a new global scope, we need to make sure we use a new one
|
||||||
|
$this->itemIterator = $this->itemStack[$newLocalIndex][1] = null;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function popScope(){
|
||||||
|
$this->localIndex = $this->popIndex;
|
||||||
|
$this->resetLocalScope();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function next(){
|
||||||
|
if (!$this->item) return false;
|
||||||
|
|
||||||
|
if (!$this->itemIterator) {
|
||||||
|
if (is_array($this->item)) $this->itemIterator = new ArrayIterator($this->item);
|
||||||
|
else $this->itemIterator = $this->item->getIterator();
|
||||||
|
|
||||||
|
$this->itemStack[$this->localIndex][1] = $this->itemIterator;
|
||||||
|
$this->itemIterator->rewind();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->itemIterator->next();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->resetLocalScope();
|
||||||
|
|
||||||
|
if (!$this->itemIterator->valid()) return false;
|
||||||
|
return $this->itemIterator->key();
|
||||||
|
}
|
||||||
|
|
||||||
|
function __call($name, $arguments) {
|
||||||
|
$on = $this->itemIterator ? $this->itemIterator->current() : $this->item;
|
||||||
|
$retval = call_user_func_array(array($on, $name), $arguments);
|
||||||
|
|
||||||
|
$this->resetLocalScope();
|
||||||
|
return $retval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a template file with an *.ss file extension.
|
* Parses a template file with an *.ss file extension.
|
||||||
*
|
*
|
||||||
@ -422,14 +547,12 @@ class SSViewer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$itemStack = array();
|
$scope = new SSViewer_DataPresenter($item);
|
||||||
$val = "";
|
$val = ""; $valStack = array();
|
||||||
$valStack = array();
|
|
||||||
|
|
||||||
include($cacheFile);
|
include($cacheFile);
|
||||||
|
|
||||||
$output = $val;
|
$output = Requirements::includeInHTML($template, $val);
|
||||||
$output = Requirements::includeInHTML($template, $output);
|
|
||||||
|
|
||||||
array_pop(SSViewer::$topLevel);
|
array_pop(SSViewer::$topLevel);
|
||||||
|
|
||||||
@ -528,7 +651,7 @@ class SSViewer_FromString extends SSViewer {
|
|||||||
echo "</pre>";
|
echo "</pre>";
|
||||||
}
|
}
|
||||||
|
|
||||||
$itemStack = array();
|
$scope = new SSViewer_DataPresenter($item);
|
||||||
$val = "";
|
$val = "";
|
||||||
$valStack = array();
|
$valStack = array();
|
||||||
|
|
||||||
|
@ -329,6 +329,137 @@ after')
|
|||||||
|
|
||||||
$this->assertEquals('A A1 A1 i A1 ii A2 A3', $rationalisedResult);
|
$this->assertEquals('A A1 A1 i A1 ii A2 A3', $rationalisedResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assertEqualIgnoringWhitespace($a, $b) {
|
||||||
|
$this->assertEquals(preg_replace('/\s+/', '', $a), preg_replace('/\s+/', '', $b));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test $Up works when the scope $Up refers to was entered with a "with" block
|
||||||
|
*/
|
||||||
|
function testUpInWith() {
|
||||||
|
|
||||||
|
// Data to run the loop tests on - three levels deep
|
||||||
|
$data = new ArrayData(array(
|
||||||
|
'Name' => 'Top',
|
||||||
|
'Foo' => new ArrayData(array(
|
||||||
|
'Name' => 'Foo',
|
||||||
|
'Bar' => new ArrayData(array(
|
||||||
|
'Name' => 'Bar',
|
||||||
|
'Baz' => new ArrayData(array(
|
||||||
|
'Name' => 'Baz'
|
||||||
|
)),
|
||||||
|
'Qux' => new ArrayData(array(
|
||||||
|
'Name' => 'Qux'
|
||||||
|
))
|
||||||
|
))
|
||||||
|
))
|
||||||
|
));
|
||||||
|
|
||||||
|
// Basic functionality
|
||||||
|
$this->assertEquals('BarFoo',
|
||||||
|
$this->render('<% with Foo %><% with Bar %>{$Name}{$Up.Name}<% end_with %><% end_with %>', $data));
|
||||||
|
|
||||||
|
// Two level with block, up refers to internally referenced Bar
|
||||||
|
$this->assertEquals('BarFoo',
|
||||||
|
$this->render('<% with Foo.Bar %>{$Name}{$Up.Name}<% end_with %>', $data));
|
||||||
|
|
||||||
|
// Stepping up & back down the scope tree
|
||||||
|
$this->assertEquals('BazBarQux',
|
||||||
|
$this->render('<% with Foo.Bar.Baz %>{$Name}{$Up.Name}{$Up.Qux.Name}<% end_with %>', $data));
|
||||||
|
|
||||||
|
// Using $Up in a with block
|
||||||
|
$this->assertEquals('BazBarQux',
|
||||||
|
$this->render('<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}{$Qux.Name}<% end_with %><% end_with %>', $data));
|
||||||
|
|
||||||
|
// Stepping up & back down the scope tree with with blocks
|
||||||
|
$this->assertEquals('BazBarQuxBarBaz',
|
||||||
|
$this->render('<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}<% with Qux %>{$Name}<% end_with %>{$Name}<% end_with %>{$Name}<% end_with %>', $data));
|
||||||
|
|
||||||
|
// Using $Up.Up, where first $Up points to a previous scope entered using $Up, thereby skipping up to Foo
|
||||||
|
$this->assertEquals('Foo',
|
||||||
|
$this->render('<% with Foo.Bar.Baz %><% with Up %><% with Qux %>{$Up.Up.Name}<% end_with %><% end_with %><% end_with %>', $data));
|
||||||
|
|
||||||
|
// Using $Up.Up, where first $Up points to an Up used in a local scope lookup, should still skip to Foo
|
||||||
|
$this->assertEquals('Foo',
|
||||||
|
$this->render('<% with Foo.Bar.Baz.Up.Qux %>{$Up.Up.Name}<% end_with %>', $data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test $Up works when the scope $Up refers to was entered with a "loop" block
|
||||||
|
*/
|
||||||
|
function testUpInLoop(){
|
||||||
|
|
||||||
|
// Data to run the loop tests on - one sequence of three items, each with a subitem
|
||||||
|
$data = new ArrayData(array(
|
||||||
|
'Name' => 'Top',
|
||||||
|
'Foo' => new DataObjectSet(array(
|
||||||
|
new ArrayData(array(
|
||||||
|
'Name' => '1',
|
||||||
|
'Sub' => new ArrayData(array(
|
||||||
|
'Name' => 'Bar'
|
||||||
|
))
|
||||||
|
)),
|
||||||
|
new ArrayData(array(
|
||||||
|
'Name' => '2',
|
||||||
|
'Sub' => new ArrayData(array(
|
||||||
|
'Name' => 'Baz'
|
||||||
|
))
|
||||||
|
)),
|
||||||
|
new ArrayData(array(
|
||||||
|
'Name' => '3',
|
||||||
|
'Sub' => new ArrayData(array(
|
||||||
|
'Name' => 'Qux'
|
||||||
|
))
|
||||||
|
))
|
||||||
|
))
|
||||||
|
));
|
||||||
|
|
||||||
|
// Make sure inside a loop, $Up refers to the current item of the loop
|
||||||
|
$this->assertEqualIgnoringWhitespace(
|
||||||
|
'111 222 333',
|
||||||
|
$this->render(
|
||||||
|
'<% loop $Foo %>$Name<% with $Sub %>$Up.Name<% end_with %>$Name<% end_loop %>',
|
||||||
|
$data
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make sure inside a loop, looping over $Up uses a separate iterator,
|
||||||
|
// and doesn't interfere with the original iterator
|
||||||
|
$this->assertEqualIgnoringWhitespace(
|
||||||
|
'1Bar123Bar1 2Baz123Baz2 3Qux123Qux3',
|
||||||
|
$this->render(
|
||||||
|
'<% loop $Foo %>
|
||||||
|
$Name
|
||||||
|
<% with $Sub %>
|
||||||
|
$Name<
|
||||||
|
% loop $Up %>$Name<% end_loop %>
|
||||||
|
$Name
|
||||||
|
<% end_with %>
|
||||||
|
$Name
|
||||||
|
<% end_loop %>',
|
||||||
|
$data
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make sure inside a loop, looping over $Up uses a separate iterator,
|
||||||
|
// and doesn't interfere with the original iterator or local lookups
|
||||||
|
$this->assertEqualIgnoringWhitespace(
|
||||||
|
'1 Bar1 123 1Bar 1 2 Baz2 123 2Baz 2 3 Qux3 123 3Qux 3',
|
||||||
|
$this->render(
|
||||||
|
'<% loop $Foo %>
|
||||||
|
$Name
|
||||||
|
<% with $Sub %>
|
||||||
|
{$Name}{$Up.Name}
|
||||||
|
<% loop $Up %>$Name<% end_loop %>
|
||||||
|
{$Up.Name}{$Name}
|
||||||
|
<% end_with %>
|
||||||
|
$Name
|
||||||
|
<% end_loop %>',
|
||||||
|
$data
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user