Allow users to extend the SSTemplateParser by defining open & closed blocks

Currently the only way the extend SSTemplateParser is to define a class
extension of it and then tell the injector component to use your new
custom class. This new change allows a user to define new "open blocks"
and "closed blocks" for SSTemplateParser to use without needing to
recompile the real SSTemplateParser class.

The following example shows how the functionality can be used
to add a new <% minify %>…<% end_minify %> syntax to the template parser

In a config.yml file, define the new minify closed block to call the
static function "Minifier::minify"

```
Injector:
	SSTemplateParser:
		properties:
			closedBlocks:
				minify: "Minifier::minify"
```

Define a new class with the minify static method that returns the new
template code when regenerating templates:

```
class Minifier {
    public static function minify(&$res) {
        return <<<PHP
{$res['Template']['php']}
\$val = zz\Html\HTMLMinify::minify(\$val, array('optimizationLevel' => zz\Html\HTMLMinify::OPTIMIZATION_ADVANCED));
PHP;
    }
}
```
This commit is contained in:
Cam Spiers 2013-10-24 12:43:20 +13:00
parent 70c23f37de
commit 14486902fb
3 changed files with 272 additions and 24 deletions

View File

@ -1299,6 +1299,38 @@ after')
$this->assertEquals($expected, trim($this->render($template, $data)));
}
}
public function testClosedBlockExtension() {
$count = 0;
$parser = new SSTemplateParser();
$parser->addClosedBlock(
'test',
function (&$res) use (&$count) {
$count++;
}
);
$template = new SSViewer_FromString("<% test %><% end_test %>", $parser);
$template->process(new SSViewerTestFixture());
$this->assertEquals(1, $count);
}
public function testOpenBlockExtension() {
$count = 0;
$parser = new SSTemplateParser();
$parser->addOpenBlock(
'test',
function (&$res) use (&$count) {
$count++;
}
);
$template = new SSViewer_FromString("<% test %>", $parser);
$template->process(new SSViewerTestFixture());
$this->assertEquals(1, $count);
}
}
/**

View File

@ -74,9 +74,33 @@ class SSTemplateParser extends Parser implements TemplateParser {
protected $includeDebuggingComments = false;
/**
* Override the Parser constructor to change the requirement of setting a string
* Stores the user-supplied closed block extension rules in the form:
* array(
* 'name' => function (&$res) {}
* )
* See SSTemplateParser::ClosedBlock_Handle_Loop for an example of what the callable should look like
* @var array
*/
function __construct() {
protected $closedBlocks = array();
/**
* Stores the user-supplied open block extension rules in the form:
* array(
* 'name' => function (&$res) {}
* )
* See SSTemplateParser::OpenBlock_Handle_Base_tag for an example of what the callable should look like
* @var array
*/
protected $openBlocks = array();
/**
* Allow the injection of new closed & open block callables
* @param array $closedBlocks
* @param array $openBlocks
*/
public function __construct($closedBlocks = array(), $openBlocks = array()) {
$this->setClosedBlocks($closedBlocks);
$this->setOpenBlocks($openBlocks);
}
/**
@ -87,6 +111,84 @@ class SSTemplateParser extends Parser implements TemplateParser {
if (!isset($res['php'])) $res['php'] = '';
return $res;
}
/**
* Set the closed blocks that the template parser should use
*
* This method will delete any existing closed blocks, please use addClosedBlock if you don't
* want to overwrite
* @param array $closedBlocks
* @throws InvalidArgumentException
*/
public function setClosedBlocks($closedBlocks) {
$this->closedBlocks = array();
foreach ((array) $closedBlocks as $name => $callable) {
$this->addClosedBlock($name, $callable);
}
}
/**
* Set the open blocks that the template parser should use
*
* This method will delete any existing open blocks, please use addOpenBlock if you don't
* want to overwrite
* @param array $openBlocks
* @throws InvalidArgumentException
*/
public function setOpenBlocks($openBlocks) {
$this->openBlocks = array();
foreach ((array) $openBlocks as $name => $callable) {
$this->addOpenBlock($name, $callable);
}
}
/**
* Add a closed block callable to allow <% name %><% end_name %> syntax
* @param string $name The name of the token to be used in the syntax <% name %><% end_name %>
* @param callable $callable The function that modifies the generation of template code
* @throws InvalidArgumentException
*/
public function addClosedBlock($name, $callable) {
$this->validateExtensionBlock($name, $callable, 'Closed block');
$this->closedBlocks[$name] = $callable;
}
/**
* Add a closed block callable to allow <% name %> syntax
* @param string $name The name of the token to be used in the syntax <% name %>
* @param callable $callable The function that modifies the generation of template code
* @throws InvalidArgumentException
*/
public function addOpenBlock($name, $callable) {
$this->validateExtensionBlock($name, $callable, 'Open block');
$this->openBlocks[$name] = $callable;
}
/**
* Ensures that the arguments to addOpenBlock and addClosedBlock are valid
* @param $name
* @param $callable
* @param $type
* @throws InvalidArgumentException
*/
protected function validateExtensionBlock($name, $callable, $type) {
if (!is_string($name)) {
throw new InvalidArgumentException(
sprintf(
"Name argument for %s must be a string",
$type
)
);
} elseif (!is_callable($callable)) {
throw new InvalidArgumentException(
sprintf(
"Callable %s argument named '%s' is not callable",
$type,
$name
)
);
}
}
/* Template: (Comment | Translate | If | Require | CacheBlock | UncachedBlock | OldI18NTag | Include | ClosedBlock |
OpenBlock | MalformedBlock | Injection | Text)+ */
@ -3586,15 +3688,18 @@ class SSTemplateParser extends Parser implements TemplateParser {
$res['ArgumentCount'] = count($res['Arguments']);
}
}
function ClosedBlock__finalise(&$res) {
$blockname = $res['BlockName']['text'];
$method = 'ClosedBlock_Handle_'.$blockname;
if (method_exists($this, $method)) $res['php'] = $this->$method($res);
else {
if (method_exists($this, $method)) {
$res['php'] = $this->$method($res);
} else if (isset($this->closedBlocks[$blockname])) {
$res['php'] = call_user_func($this->closedBlocks[$blockname], $res);
} else {
throw new SSTemplateParseException('Unknown closed block "'.$blockname.'" encountered. Perhaps you are ' .
'not supposed to close this block, or have mis-spelled it?', $this);
'not supposed to close this block, or have mis-spelled it?', $this);
}
}
@ -3733,15 +3838,18 @@ class SSTemplateParser extends Parser implements TemplateParser {
$res['ArgumentCount'] = count($res['Arguments']);
}
}
function OpenBlock__finalise(&$res) {
$blockname = $res['BlockName']['text'];
$method = 'OpenBlock_Handle_'.$blockname;
if (method_exists($this, $method)) $res['php'] = $this->$method($res);
else {
if (method_exists($this, $method)) {
$res['php'] = $this->$method($res);
} elseif (isset($this->openBlocks[$blockname])) {
$res['php'] = call_user_func($this->openBlocks[$blockname], $res);
} else {
throw new SSTemplateParseException('Unknown open block "'.$blockname.'" encountered. Perhaps you missed ' .
' the closing tag or have mis-spelled it?', $this);
' the closing tag or have mis-spelled it?', $this);
}
}

View File

@ -95,9 +95,33 @@ class SSTemplateParser extends Parser implements TemplateParser {
protected $includeDebuggingComments = false;
/**
* Override the Parser constructor to change the requirement of setting a string
* Stores the user-supplied closed block extension rules in the form:
* array(
* 'name' => function (&$res) {}
* )
* See SSTemplateParser::ClosedBlock_Handle_Loop for an example of what the callable should look like
* @var array
*/
function __construct() {
protected $closedBlocks = array();
/**
* Stores the user-supplied open block extension rules in the form:
* array(
* 'name' => function (&$res) {}
* )
* See SSTemplateParser::OpenBlock_Handle_Base_tag for an example of what the callable should look like
* @var array
*/
protected $openBlocks = array();
/**
* Allow the injection of new closed & open block callables
* @param array $closedBlocks
* @param array $openBlocks
*/
public function __construct($closedBlocks = array(), $openBlocks = array()) {
$this->setClosedBlocks($closedBlocks);
$this->setOpenBlocks($openBlocks);
}
/**
@ -108,6 +132,84 @@ class SSTemplateParser extends Parser implements TemplateParser {
if (!isset($res['php'])) $res['php'] = '';
return $res;
}
/**
* Set the closed blocks that the template parser should use
*
* This method will delete any existing closed blocks, please use addClosedBlock if you don't
* want to overwrite
* @param array $closedBlocks
* @throws InvalidArgumentException
*/
public function setClosedBlocks($closedBlocks) {
$this->closedBlocks = array();
foreach ((array) $closedBlocks as $name => $callable) {
$this->addClosedBlock($name, $callable);
}
}
/**
* Set the open blocks that the template parser should use
*
* This method will delete any existing open blocks, please use addOpenBlock if you don't
* want to overwrite
* @param array $openBlocks
* @throws InvalidArgumentException
*/
public function setOpenBlocks($openBlocks) {
$this->openBlocks = array();
foreach ((array) $openBlocks as $name => $callable) {
$this->addOpenBlock($name, $callable);
}
}
/**
* Add a closed block callable to allow <% name %><% end_name %> syntax
* @param string $name The name of the token to be used in the syntax <% name %><% end_name %>
* @param callable $callable The function that modifies the generation of template code
* @throws InvalidArgumentException
*/
public function addClosedBlock($name, $callable) {
$this->validateExtensionBlock($name, $callable, 'Closed block');
$this->closedBlocks[$name] = $callable;
}
/**
* Add a closed block callable to allow <% name %> syntax
* @param string $name The name of the token to be used in the syntax <% name %>
* @param callable $callable The function that modifies the generation of template code
* @throws InvalidArgumentException
*/
public function addOpenBlock($name, $callable) {
$this->validateExtensionBlock($name, $callable, 'Open block');
$this->openBlocks[$name] = $callable;
}
/**
* Ensures that the arguments to addOpenBlock and addClosedBlock are valid
* @param $name
* @param $callable
* @param $type
* @throws InvalidArgumentException
*/
protected function validateExtensionBlock($name, $callable, $type) {
if (!is_string($name)) {
throw new InvalidArgumentException(
sprintf(
"Name argument for %s must be a string",
$type
)
);
} elseif (!is_callable($callable)) {
throw new InvalidArgumentException(
sprintf(
"Callable %s argument named '%s' is not callable",
$type,
$name
)
);
}
}
/*!* SSTemplateParser
@ -766,15 +868,18 @@ class SSTemplateParser extends Parser implements TemplateParser {
$res['ArgumentCount'] = count($res['Arguments']);
}
}
function ClosedBlock__finalise(&$res) {
$blockname = $res['BlockName']['text'];
$method = 'ClosedBlock_Handle_'.$blockname;
if (method_exists($this, $method)) $res['php'] = $this->$method($res);
else {
if (method_exists($this, $method)) {
$res['php'] = $this->$method($res);
} else if (isset($this->closedBlocks[$blockname])) {
$res['php'] = call_user_func($this->closedBlocks[$blockname], $res);
} else {
throw new SSTemplateParseException('Unknown closed block "'.$blockname.'" encountered. Perhaps you are ' .
'not supposed to close this block, or have mis-spelled it?', $this);
'not supposed to close this block, or have mis-spelled it?', $this);
}
}
@ -856,15 +961,18 @@ class SSTemplateParser extends Parser implements TemplateParser {
$res['ArgumentCount'] = count($res['Arguments']);
}
}
function OpenBlock__finalise(&$res) {
$blockname = $res['BlockName']['text'];
$method = 'OpenBlock_Handle_'.$blockname;
if (method_exists($this, $method)) $res['php'] = $this->$method($res);
else {
if (method_exists($this, $method)) {
$res['php'] = $this->$method($res);
} elseif (isset($this->openBlocks[$blockname])) {
$res['php'] = call_user_func($this->openBlocks[$blockname], $res);
} else {
throw new SSTemplateParseException('Unknown open block "'.$blockname.'" encountered. Perhaps you missed ' .
' the closing tag or have mis-spelled it?', $this);
' the closing tag or have mis-spelled it?', $this);
}
}