Mark text sections as to be stripped, but do not strip them right away. Combine equivalent sections in chunks at the template level and strip combined chunks where possible.

- simplified Smarty_Internal_TemplateCompilerBase::processText along the way
Fixes !447
This commit is contained in:
Simon Wisselink
2020-04-12 01:19:07 +02:00
parent 17d4d43624
commit d43fc158ee
4 changed files with 163 additions and 105 deletions

View File

@@ -85,45 +85,85 @@ class Smarty_Internal_ParseTree_Template extends Smarty_Internal_ParseTree
public function to_smarty_php(Smarty_Internal_Templateparser $parser) public function to_smarty_php(Smarty_Internal_Templateparser $parser)
{ {
$code = ''; $code = '';
for ($key = 0, $cnt = count($this->subtrees); $key < $cnt; $key++) {
if ($this->subtrees[ $key ] instanceof Smarty_Internal_ParseTree_Text) { foreach ($this->getChunkedSubtrees() as $chunk) {
$subtree = $this->subtrees[ $key ]->to_smarty_php($parser); $text = '';
while ($key + 1 < $cnt && ($this->subtrees[ $key + 1 ] instanceof Smarty_Internal_ParseTree_Text || switch ($chunk['mode']) {
$this->subtrees[ $key + 1 ]->data === '')) { case 'textstripped':
$key++; foreach ($chunk['subtrees'] as $subtree) {
if ($this->subtrees[ $key ]->data === '') { $text .= $subtree->to_smarty_php($parser);
continue; }
} $code .= preg_replace(
$subtree .= $this->subtrees[ $key ]->to_smarty_php($parser); '/((<%)|(%>)|(<\?php)|(<\?)|(\?>)|(<\/?script))/',
} "<?php echo '\$1'; ?>\n",
if ($subtree === '') { $parser->compiler->processText($text)
continue; );
} break;
$code .= preg_replace( case 'text':
'/((<%)|(%>)|(<\?php)|(<\?)|(\?>)|(<\/?script))/', foreach ($chunk['subtrees'] as $subtree) {
"<?php echo '\$1'; ?>\n", $text .= $subtree->to_smarty_php($parser);
$subtree }
); $code .= preg_replace(
continue; '/((<%)|(%>)|(<\?php)|(<\?)|(\?>)|(<\/?script))/',
} "<?php echo '\$1'; ?>\n",
if ($this->subtrees[ $key ] instanceof Smarty_Internal_ParseTree_Tag) { $text
$subtree = $this->subtrees[ $key ]->to_smarty_php($parser); );
while ($key + 1 < $cnt && ($this->subtrees[ $key + 1 ] instanceof Smarty_Internal_ParseTree_Tag || break;
$this->subtrees[ $key + 1 ]->data === '')) { case 'tag':
$key++; foreach ($chunk['subtrees'] as $subtree) {
if ($this->subtrees[ $key ]->data === '') { $text = $parser->compiler->appendCode($text, $subtree->to_smarty_php($parser));
continue; }
} $code .= $text;
$subtree = $parser->compiler->appendCode($subtree, $this->subtrees[ $key ]->to_smarty_php($parser)); break;
} default:
if ($subtree === '') { foreach ($chunk['subtrees'] as $subtree) {
continue; $text = $subtree->to_smarty_php($parser);
} }
$code .= $subtree; $code .= $text;
continue;
} }
$code .= $this->subtrees[ $key ]->to_smarty_php($parser);
} }
return $code; return $code;
} }
private function getChunkedSubtrees() {
$chunks = [];
$currentMode = null;
$currentChunk = [];
for ($key = 0, $cnt = count($this->subtrees); $key < $cnt; $key++) {
if ($this->subtrees[ $key ]->data === '' && in_array($currentMode, ['textstripped', 'text', 'tag'])) {
continue;
}
if ($this->subtrees[ $key ] instanceof Smarty_Internal_ParseTree_Text
&& $this->subtrees[ $key ]->isToBeStripped()) {
$newMode = 'textstripped';
} elseif ($this->subtrees[ $key ] instanceof Smarty_Internal_ParseTree_Text) {
$newMode = 'text';
} elseif ($this->subtrees[ $key ] instanceof Smarty_Internal_ParseTree_Tag) {
$newMode = 'tag';
} else {
$newMode = 'other';
}
if ($newMode == $currentMode) {
$currentChunk[] = $this->subtrees[ $key ];
} else {
$chunks[] = [
'mode' => $currentMode,
'subtrees' => $currentChunk
];
$currentMode = $newMode;
$currentChunk = [$this->subtrees[ $key ]];
}
}
if ($currentMode && $currentChunk) {
$chunks[] = [
'mode' => $currentMode,
'subtrees' => $currentChunk
];
}
return $chunks;
}
} }

View File

@@ -16,14 +16,31 @@
*/ */
class Smarty_Internal_ParseTree_Text extends Smarty_Internal_ParseTree class Smarty_Internal_ParseTree_Text extends Smarty_Internal_ParseTree
{ {
/**
* Create template text buffer /**
* * Wether this section should be stripped on output to smarty php
* @param string $data text * @var bool
*/ */
public function __construct($data) private $toBeStripped = false;
/**
* Create template text buffer
*
* @param string $data text
* @param bool $toBeStripped wether this section should be stripped on output to smarty php
*/
public function __construct($data, $toBeStripped = false)
{ {
$this->data = $data; $this->data = $data;
$this->toBeStripped = $toBeStripped;
}
/**
* Wether this section should be stripped on output to smarty php
* @return bool
*/
public function isToBeStripped() {
return $this->toBeStripped;
} }
/** /**

View File

@@ -672,73 +672,69 @@ abstract class Smarty_Internal_TemplateCompilerBase
} }
/** /**
* This method is called from parser to process a text content section * This method is called from parser to process a text content section if strip is enabled
* - remove text from inheritance child templates as they may generate output * - remove text from inheritance child templates as they may generate output
* - strip text if strip is enabled
* *
* @param string $text * @param string $text
* *
* @return null|\Smarty_Internal_ParseTree_Text * @return string
*/ */
public function processText($text) public function processText($text)
{ {
if ((string)$text != '') {
$store = array(); if (strpos($text, '<') === false) {
$_store = 0; return preg_replace($this->stripRegEx, '', $text);
if ($this->parser->strip) {
if (strpos($text, '<') !== false) {
// capture html elements not to be messed with
$_offset = 0;
if (preg_match_all(
'#(<script[^>]*>.*?</script[^>]*>)|(<textarea[^>]*>.*?</textarea[^>]*>)|(<pre[^>]*>.*?</pre[^>]*>)#is',
$text,
$matches,
PREG_OFFSET_CAPTURE | PREG_SET_ORDER
)
) {
foreach ($matches as $match) {
$store[] = $match[ 0 ][ 0 ];
$_length = strlen($match[ 0 ][ 0 ]);
$replace = '@!@SMARTY:' . $_store . ':SMARTY@!@';
$text = substr_replace($text, $replace, $match[ 0 ][ 1 ] - $_offset, $_length);
$_offset += $_length - strlen($replace);
$_store++;
}
}
$expressions = array(// replace multiple spaces between tags by a single space
'#(:SMARTY@!@|>)[\040\011]+(?=@!@SMARTY:|<)#s' => '\1 \2',
// remove newline between tags
'#(:SMARTY@!@|>)[\040\011]*[\n]\s*(?=@!@SMARTY:|<)#s' => '\1\2',
// remove multiple spaces between attributes (but not in attribute values!)
'#(([a-z0-9]\s*=\s*("[^"]*?")|(\'[^\']*?\'))|<[a-z0-9_]+)\s+([a-z/>])#is' => '\1 \5',
'#>[\040\011]+$#Ss' => '> ',
'#>[\040\011]*[\n]\s*$#Ss' => '>',
$this->stripRegEx => '',
);
$text = preg_replace(array_keys($expressions), array_values($expressions), $text);
$_offset = 0;
if (preg_match_all(
'#@!@SMARTY:([0-9]+):SMARTY@!@#is',
$text,
$matches,
PREG_OFFSET_CAPTURE | PREG_SET_ORDER
)
) {
foreach ($matches as $match) {
$_length = strlen($match[ 0 ][ 0 ]);
$replace = $store[ $match[ 1 ][ 0 ] ];
$text = substr_replace($text, $replace, $match[ 0 ][ 1 ] + $_offset, $_length);
$_offset += strlen($replace) - $_length;
$_store++;
}
}
} else {
$text = preg_replace($this->stripRegEx, '', $text);
}
}
return new Smarty_Internal_ParseTree_Text($text);
} }
return null;
$store = array();
$_store = 0;
// capture html elements not to be messed with
$_offset = 0;
if (preg_match_all(
'#(<script[^>]*>.*?</script[^>]*>)|(<textarea[^>]*>.*?</textarea[^>]*>)|(<pre[^>]*>.*?</pre[^>]*>)#is',
$text,
$matches,
PREG_OFFSET_CAPTURE | PREG_SET_ORDER
)
) {
foreach ($matches as $match) {
$store[] = $match[ 0 ][ 0 ];
$_length = strlen($match[ 0 ][ 0 ]);
$replace = '@!@SMARTY:' . $_store . ':SMARTY@!@';
$text = substr_replace($text, $replace, $match[ 0 ][ 1 ] - $_offset, $_length);
$_offset += $_length - strlen($replace);
$_store++;
}
}
$expressions = array(// replace multiple spaces between tags by a single space
'#(:SMARTY@!@|>)[\040\011]+(?=@!@SMARTY:|<)#s' => '\1 \2',
// remove newline between tags
'#(:SMARTY@!@|>)[\040\011]*[\n]\s*(?=@!@SMARTY:|<)#s' => '\1\2',
// remove multiple spaces between attributes (but not in attribute values!)
'#(([a-z0-9]\s*=\s*("[^"]*?")|(\'[^\']*?\'))|<[a-z0-9_]+)\s+([a-z/>])#is' => '\1 \5',
'#>[\040\011]+$#Ss' => '> ',
'#>[\040\011]*[\n]\s*$#Ss' => '>',
$this->stripRegEx => '',
);
$text = preg_replace(array_keys($expressions), array_values($expressions), $text);
$_offset = 0;
if (preg_match_all(
'#@!@SMARTY:([0-9]+):SMARTY@!@#is',
$text,
$matches,
PREG_OFFSET_CAPTURE | PREG_SET_ORDER
)
) {
foreach ($matches as $match) {
$_length = strlen($match[ 0 ][ 0 ]);
$replace = $store[ $match[ 1 ][ 0 ] ];
$text = substr_replace($text, $replace, $match[ 0 ][ 1 ] + $_offset, $_length);
$_offset += strlen($replace) - $_length;
$_store++;
}
}
return $text;
} }
/** /**

View File

@@ -2169,8 +2169,13 @@ class Smarty_Internal_Templateparser
// line 255 "../smarty/lexer/smarty_internal_templateparser.y" // line 255 "../smarty/lexer/smarty_internal_templateparser.y"
public function yy_r2() public function yy_r2()
{ {
$this->current_buffer->append_subtree($this, $text = $this->yystack[ $this->yyidx + 0 ]->minor;
$this->compiler->processText($this->yystack[ $this->yyidx + 0 ]->minor));
if ((string)$text == '') {
$this->current_buffer->append_subtree($this, null);
}
$this->current_buffer->append_subtree($this, new Smarty_Internal_ParseTree_Text($text, $this->strip));
} }
// line 259 "../smarty/lexer/smarty_internal_templateparser.y" // line 259 "../smarty/lexer/smarty_internal_templateparser.y"