Skip to content

Commit

Permalink
Handle PHP expressions properly #5 (#8)
Browse files Browse the repository at this point in the history
* Expressions in attributes
* Fix parentheses ending
* Fix attributes escaping
  • Loading branch information
kylekatarnls committed Jun 27, 2016
1 parent 392261f commit fdf01d9
Show file tree
Hide file tree
Showing 16 changed files with 106 additions and 51 deletions.
2 changes: 1 addition & 1 deletion src/Jade/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ public function interpolate($text)
protected function interpolateFromCapture($match)
{
if ($match[1] === '') {
return $this->createCode($match[2] === '!' ? static::UNESCAPED : static::ESCAPED, $match[3]);
return $this->escapeIfNeeded($match[2] === '!', $match[3]);
}

return substr($match[0], 1);
Expand Down
12 changes: 6 additions & 6 deletions src/Jade/Compiler/AttributesCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,18 @@ protected function getClassAttribute($value, &$classesCheck)
return $this->keepNullAttributes ? '' : 'null';
}

protected function getUnescapedValueCode($value, &$valueCheck)
protected function getValueCode($escaped, $value, &$valueCheck)
{
if ($this->keepNullAttributes) {
return $this->createCode(static::UNESCAPED, $value);
return $this->escapeIfNeeded($escaped, $value);
}

$valueCheck = $value;

return $this->createCode(static::UNESCAPED, '$__value');
return $this->escapeIfNeeded($escaped, '$__value');
}

protected function getAttributeValue($key, $value, &$classesCheck, &$valueCheck)
protected function getAttributeValue($escaped, $key, $value, &$classesCheck, &$valueCheck)
{
if ($this->isConstant($value) || ($key != 'class' && $this->isArrayOfConstants($value))) {
$value = trim($value, ' \'"');
Expand All @@ -98,7 +98,7 @@ protected function getAttributeValue($key, $value, &$classesCheck, &$valueCheck)
return $this->getClassAttribute($value, $classesCheck);
}

return $this->getUnescapedValueCode($value, $valueCheck);
return $this->getValueCode($escaped, $value, $valueCheck);
}

protected function compileAttributeValue($key, $value, $attr, $valueCheck)
Expand All @@ -125,7 +125,7 @@ protected function getAttributeCode($attr, &$classes, &$classesCheck)
$valueCheck = null;
$value = trim($attr['value']);

$value = $this->getAttributeValue($key, $value, $classesCheck, $valueCheck);
$value = $this->getAttributeValue($attr['escaped'], $key, $value, $classesCheck, $valueCheck);

if ($key === 'class') {
if ($value !== 'false' && $value !== 'null' && $value !== 'undefined') {
Expand Down
22 changes: 4 additions & 18 deletions src/Jade/Compiler/CodeHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public function parse()
return array($this->input);
}

if (strpos('=])},;?', substr($this->input, 0, 1)) !== false) {
if (strpos('=,;?', substr($this->input, 0, 1)) !== false) {
throw new \Exception('Expecting a variable name or an expression, got: ' . $this->input);
}

Expand Down Expand Up @@ -94,13 +94,7 @@ protected function parseBetweenSeparators()
$getMiddleString = function ($start, $end) use ($input) {
$offset = $start[1] + strlen($start[0]);

return substr(
$input,
$offset,
isset($end)
? $end[1] - $offset
: strlen($input)
);
return substr($input, $offset, isset($end) ? $end[1] - $offset : strlen($input));
};

$host = $this;
Expand Down Expand Up @@ -153,7 +147,7 @@ protected function parseBetweenSeparators()
if ($curr[0] === $close) {
$count--;
}
} while ($curr[0] !== null && $count > 0 && $curr[0] !== ',');
} while ($curr[0] !== null && $count >= 0 && $curr[0] !== ',');

$end = current($separators);

Expand All @@ -165,7 +159,7 @@ protected function parseBetweenSeparators()
}
} while ($curr !== false && $count > 0);

if ($close && $count) {
if ($close && $count > 0) {
throw new \Exception($input . "\nMissing closing: " . $close);
}

Expand Down Expand Up @@ -220,14 +214,6 @@ protected function parseBetweenSeparators()
array_push($result, "{$var}={$call}");
break;

// mixin arguments
case ',':
$arguments = $handleCodeInbetween();
if ($arguments) {
$varname .= ', ' . implode(', ', $arguments);
}
break;

case '[':
if (preg_match('/[a-zA-Z0-9\\\\_\\x7f-\\xff]$/', $varname)) {
$varname .= $sep[0] . $innerName;
Expand Down
3 changes: 1 addition & 2 deletions src/Jade/Compiler/CodeVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ protected function visitCodeOpening(Code $node)
$code = trim($node->value);

if ($node->buffer) {
$pattern = $node->escape ? static::ESCAPED : static::UNESCAPED;
$this->buffer($this->createCode($pattern, $code));
$this->buffer($this->escapeIfNeeded($node->escape, $code));

return;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Jade/Compiler/CompilerConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ abstract class CompilerConfig
/**
* @const string
*/
const ESCAPED = 'echo htmlspecialchars(%s)';
const ESCAPED = 'echo \\Jade\\Compiler::getEscapedValue(%s, %s)';

/**
* @const string
*/
const UNESCAPED = 'echo \\Jade\\Compiler::strval(%s)';
const UNESCAPED = 'echo \\Jade\\Compiler::getUnescapedValue(%s)';

/**
* @var array
Expand Down
22 changes: 20 additions & 2 deletions src/Jade/Compiler/CompilerFacade.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,27 @@ abstract class CompilerFacade extends CompilerUtils
*
* @return string
*/
public static function strval($val)
public static function getUnescapedValue($val)
{
return is_array($val) || is_null($val) || is_bool($val) || is_int($val) || is_float($val) ? json_encode($val) : strval($val);
if (is_null($val) || $val === false || $val === '') {
return '';
}

return is_array($val) || is_bool($val) || is_int($val) || is_float($val) ? json_encode($val) : strval($val);
}

/**
* value treatment if it must not be escaped.
*
* @param string input value
*
* @return string
*/
public static function getEscapedValue($val, $quote)
{
$val = htmlspecialchars(static::getUnescapedValue($val), ENT_NOQUOTES);

return str_replace($quote, $quote === '"' ? '"' : ''', $val);
}

/**
Expand Down
25 changes: 20 additions & 5 deletions src/Jade/Compiler/CompilerUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
abstract class CompilerUtils extends Indenter
{
/**
* @param string $call
* @param $call string
*
* @throws \Exception
*
Expand Down Expand Up @@ -78,7 +78,7 @@ protected static function initArgToNull(&$arg)
}

/**
* @param string $value
* @param $value string
*
* @return mixed
*/
Expand All @@ -88,7 +88,7 @@ protected static function parseValue($value)
}

/**
* @param string $value
* @param $value string
*
* @return mixed
*/
Expand All @@ -100,7 +100,7 @@ protected static function decodeValue($value)
}

/**
* @param array $attributes
* @param $attributes array
*
* @return array
*/
Expand All @@ -119,7 +119,7 @@ protected static function decodeAttributes($attributes)
}

/**
* @param $name
* @param $name string
*
* @return callable
*/
Expand All @@ -129,4 +129,19 @@ protected function getFilter($name)

return $helper->getValidFilter($name);
}

/**
* @param $escaped bool need to be escaped
* @param $value mixed to be escaped if $escaped is true
*
* @return callable
*/
protected function escapeIfNeeded($escaped, $value)
{
if ($escaped) {
return $this->createCode(static::ESCAPED, $value, var_export($this->quote, true));
}

return $this->createCode(static::UNESCAPED, $value);
}
}
3 changes: 3 additions & 0 deletions tests/features/cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ protected function emptyDirectory($dir)
public function testMissingDirectory() {

$jade = new Jade(array(
'singleQuote' => false,
'cache' => 'does/not/exists'
));
$jade->render(__DIR__ . '/../templates/attrs.jade');
Expand Down Expand Up @@ -93,6 +94,7 @@ public function testReadOnlyDirectory() {
$dir = $parent;
}
$jade = new Jade(array(
'singleQuote' => false,
'cache' => $dir
));
$jade->cache(__DIR__ . '/../templates/attrs.jade');
Expand All @@ -102,6 +104,7 @@ private function cacheSystem($keepBaseName) {

$file = tempnam(sys_get_temp_dir(), 'jade-test-');
$jade = new Jade(array(
'singleQuote' => false,
'keepBaseName' => $keepBaseName,
'cache' => sys_get_temp_dir(),
));
Expand Down
22 changes: 22 additions & 0 deletions tests/features/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,18 @@ static private function rawHtml($html, $convertSingleQuote = true) {
return trim(preg_replace('`\n{2,}`', "\n", $html));
}

static private function simpleHtml($html) {

return trim(preg_replace('`\r\n|\r`', "\n", $html));
}

/**
* keepNullAttributes setting test
*/
public function testKeepNullAttributes() {

$jade = new Jade(array(
'singleQuote' => false,
'keepNullAttributes' => false,
'prettyprint' => true,
));
Expand All @@ -29,6 +35,7 @@ public function testKeepNullAttributes() {
$this->assertSame(static::rawHtml($actual), static::rawHtml($expected), 'Keep null attributes disabled');

$jade = new Jade(array(
'singleQuote' => false,
'keepNullAttributes' => true,
'prettyprint' => true,
));
Expand Down Expand Up @@ -59,6 +66,7 @@ public function testPrettyprint() {
';

$jade = new Jade(array(
'singleQuote' => true,
'prettyprint' => true,
));
$actual = trim(preg_replace('`\n[\s\n]+`', "\n", str_replace("\r", '', preg_replace('`[ \t]+`', ' ', $jade->render($template)))));
Expand All @@ -70,6 +78,7 @@ public function testPrettyprint() {
$this->assertSame($expected, $actual, 'Pretty print enabled');

$jade = new Jade(array(
'singleQuote' => true,
'prettyprint' => false,
));
$actual = preg_replace('`[ \t]+`', ' ', $jade->render($template));
Expand Down Expand Up @@ -98,6 +107,7 @@ public function testSetOption() {
';

$jade = new Jade(array(
'singleQuote' => true,
'prettyprint' => false,
));
$this->assertFalse($jade->getOption('prettyprint'), 'getOption should return current setting');
Expand Down Expand Up @@ -175,6 +185,7 @@ public function testAllowMixinOverride() {
';

$jade = new Jade(array(
'singleQuote' => false,
'allowMixinOverride' => true,
));
$actual = $jade->render($template);
Expand All @@ -183,6 +194,7 @@ public function testAllowMixinOverride() {
$this->assertSame(static::rawHtml($actual), static::rawHtml($expected), 'Allow mixin override enabled');

$jade = new Jade(array(
'singleQuote' => false,
'allowMixinOverride' => false,
));
$actual = $jade->render($template);
Expand Down Expand Up @@ -236,20 +248,30 @@ public function testSingleQuote() {
$template = 'h1#foo.bar(style="color: red;") Hello';

$jade = new Jade(array(
'prettyprint' => true,
'singleQuote' => true,
));
$actual = $jade->render($template);
$expected = "<h1 id='foo' style='color: red;' class='bar'>Hello</h1>";

$this->assertSame(static::rawHtml($actual, false), static::rawHtml($expected, false), 'Single quote enabled');
$file = __DIR__ . '/../templates/attrs-data.complex';
$this->assertSame(static::simpleHtml($jade->render($file . '.jade')), static::simpleHtml(file_get_contents($file . '.single-quote.html')), 'Single quote enabled');
$file = __DIR__ . '/../templates/attrs-data';
$this->assertSame(static::simpleHtml($jade->render($file . '.jade')), static::simpleHtml(file_get_contents($file . '.single-quote.html')), 'Single quote enabled');

$jade = new Jade(array(
'prettyprint' => true,
'singleQuote' => false,
));
$actual = $jade->render($template);
$expected = '<h1 id="foo" style="color: red;" class="bar">Hello</h1>';

$this->assertSame(static::rawHtml($actual, false), static::rawHtml($expected, false), 'Single quote disabled');
$file = __DIR__ . '/../templates/attrs-data.complex';
$this->assertSame(static::simpleHtml($jade->render($file . '.jade')), static::simpleHtml(file_get_contents($file . '.html')), 'Single quote enabled');
$file = __DIR__ . '/../templates/attrs-data';
$this->assertSame(static::simpleHtml($jade->render($file . '.jade')), static::simpleHtml(file_get_contents($file . '.html')), 'Single quote enabled');
}

/**
Expand Down
2 changes: 2 additions & 0 deletions tests/lib/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,15 @@ function build_list($test_list) {

function get_php_code($file) {
$jade = new Jade(array(
'singleQuote' => false,
'prettyprint' => true,
));
return $jade->render($file);
}

function compile_php($file) {
$jade = new Jade(array(
'singleQuote' => false,
'prettyprint' => true,
));
return $jade->compile(file_get_contents(TEMPLATES_DIRECTORY . DIRECTORY_SEPARATOR . $file . '.jade'));
Expand Down
8 changes: 4 additions & 4 deletions tests/templates/attrs-data.complex.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<foo data-items='{"foo":"bar","yah":42,"yoh":"yah: 42","bar":"a'a\"\\\"\\'\\\\"}'></foo>
<foo data-items='{"foo":"42, #{foo}","bar":"42, #{foo}"}'></foo>
<foo data-items='["42 [a]","42 =>"]'></foo>
<foo data-items='{"a\\\"\\":"a"}'></foo>
<foo data-items="{&quot;foo&quot;:&quot;bar&quot;,&quot;yah&quot;:42,&quot;yoh&quot;:&quot;yah: 42&quot;,&quot;bar&quot;:&quot;a'a\&quot;\\\&quot;\\'\\\\&quot;}"></foo>
<foo data-items="{&quot;foo&quot;:&quot;42, #{foo}&quot;,&quot;bar&quot;:&quot;42, #{foo}&quot;}"></foo>
<foo data-items="[&quot;42 [a]&quot;,&quot;42 =&gt;&quot;]"></foo>
<foo data-items="{&quot;a\\\&quot;\\&quot;:&quot;a&quot;}"></foo>
<foo a\=\a></foo>
5 changes: 5 additions & 0 deletions tests/templates/attrs-data.complex.single-quote.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<foo data-items='{"foo":"bar","yah":42,"yoh":"yah: 42","bar":"a&apos;a\"\\\"\\&apos;\\\\"}'></foo>
<foo data-items='{"foo":"42, #{foo}","bar":"42, #{foo}"}'></foo>
<foo data-items='["42 [a]","42 =&gt;"]'></foo>
<foo data-items='{"a\\\"\\":"a"}'></foo>
<foo a\=\a></foo>
4 changes: 2 additions & 2 deletions tests/templates/attrs-data.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<foo data-user='{"name":"tobi"}'></foo>
<foo data-items='[1,2,3]'></foo>
<foo data-user="{&quot;name&quot;:&quot;tobi&quot;}"></foo>
<foo data-items="[1,2,3]"></foo>
<foo data-username="tobi"></foo>
<foo data-username="tobi"></foo>
<foo data-username="tobi"></foo>
5 changes: 5 additions & 0 deletions tests/templates/attrs-data.single-quote.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<foo data-user='{"name":"tobi"}'></foo>
<foo data-items='[1,2,3]'></foo>
<foo data-username='tobi'></foo>
<foo data-username='tobi'></foo>
<foo data-username='tobi'></foo>
Loading

0 comments on commit fdf01d9

Please sign in to comment.