new Link(null, 'doc/html'),
* 'strong' => Html::tag('strong')
* ]
* );
* ```
*/
class TemplateString extends FormattedString
{
/** @var array */
protected $templateArgs = [];
/** @var int */
protected $pos = 0;
/** @var string */
protected $string;
/** @var int */
protected $length;
public function __construct($format, $args = null)
{
$parentArgs = [];
foreach ($args ?: [] as $val) {
if (is_array($val) && is_string(key($val))) {
$this->templateArgs += $val;
} else {
$parentArgs[] = $val;
}
}
parent::__construct($format, $parentArgs);
}
/**
* Parse template strings
*
* @param null $for template name
* @return HtmlDocument
* @throws Exception in case of missing template argument or unbounded open or close templates
*/
protected function parseTemplates($for = null)
{
$buffer = '';
while (($char = $this->readChar()) !== false) {
if ($char !== '{') {
$buffer .= $char;
continue;
}
$nextChar = $this->readChar();
if ($nextChar !== '{') {
$buffer .= $char . $nextChar;
continue;
}
$templateHandle = $this->readChar();
$start = $templateHandle === '#';
$end = $templateHandle === '/';
$templateKey = $this->readUntil('}');
// if the string following '{{#' is read up to the last character or (length - 1)th character
// then it is not a template
if ($this->pos >= $this->length - 1) {
$buffer .= $char . $nextChar . $templateHandle . $templateKey;
continue;
}
$this->pos++;
$closeChar = $this->readChar();
if ($closeChar !== '}') {
$buffer .= $char . $nextChar . $templateHandle . $templateKey . '}' . $closeChar;
continue;
}
if ($start) {
if (isset($this->templateArgs[$templateKey])) {
$wrapper = $this->templateArgs[$templateKey];
$buffer .= $this->parseTemplates($templateKey)->prependWrapper($wrapper);
} else {
throw new Exception(sprintf(
'Missing template argument: %s ',
$templateKey
));
}
} elseif ($for === $templateKey && $end) {
// close the template
$for = null;
break;
} else {
// throw exception for unbounded closing of templates
throw new Exception(sprintf(
'Unbound closing of template: %s',
$templateKey
));
}
}
if ($this->pos === $this->length && $for !== null) {
throw new Exception(sprintf(
'Unbound opening of template: %s',
$for
));
}
return (new HtmlDocument())->addHtml(HtmlString::create($buffer));
}
/**
* Read until any of the given chars appears
*
* @param string ...$chars
*
* @return string
*/
protected function readUntil(...$chars)
{
$buffer = '';
while (($c = $this->readChar()) !== false) {
if (in_array($c, $chars, true)) {
$this->pos--;
break;
}
$buffer .= $c;
}
return $buffer;
}
/**
* Read a single character
*
* @return false|string false if there is no character left
*/
protected function readChar()
{
if ($this->length > $this->pos) {
return $this->string[$this->pos++];
}
return false;
}
public function render()
{
$formattedstring = parent::render();
if (empty($this->templateArgs)) {
return $formattedstring;
}
$this->string = $formattedstring;
$this->length = strlen($formattedstring);
return $this->parseTemplates()->render();
}
}