Skip to content

Commit

Permalink
redesigned way to work with HTML attributes (BC break)
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Aug 8, 2023
1 parent f18b094 commit e8a2d00
Show file tree
Hide file tree
Showing 18 changed files with 126 additions and 281 deletions.
27 changes: 6 additions & 21 deletions src/Latte/Compiler/Escaper.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ final class Escaper

private string $state = '';
private string $tag = '';
private string $quote = '';
private string $subType = '';


Expand All @@ -65,8 +64,7 @@ public function getState(): string

public function export(): string
{
return ($this->state === self::HtmlAttribute && $this->quote === '' ? 'html/unquoted-attr' : $this->state)
. ($this->subType ? '/' . $this->subType : '');
return $this->state . ($this->subType ? '/' . $this->subType : '');
}


Expand Down Expand Up @@ -101,10 +99,9 @@ public function enterHtmlTag(string $name): void
}


public function enterHtmlAttribute(?string $name = null, string $quote = ''): void
public function enterHtmlAttribute(?string $name = null): void
{
$this->state = self::HtmlAttribute;
$this->quote = $quote;
$this->subType = '';

if ($this->contentType === ContentType::Html && is_string($name)) {
Expand All @@ -122,12 +119,6 @@ public function enterHtmlAttribute(?string $name = null, string $quote = ''): vo
}


public function enterHtmlAttributeQuote(string $quote = '"'): void
{
$this->quote = $quote;
}


public function enterHtmlBogusTag(): void
{
$this->state = self::HtmlBogusTag;
Expand All @@ -142,16 +133,15 @@ public function enterHtmlComment(): void

public function escape(string $str): string
{
[$lq, $rq] = $this->state === self::HtmlAttribute && !$this->quote ? ["'\"' . ", " . '\"'"] : ['', ''];
return match ($this->contentType) {
ContentType::Html => match ($this->state) {
self::HtmlText => 'LR\Filters::escapeHtmlText(' . $str . ')',
self::HtmlTag => 'LR\Filters::escapeHtmlTag(' . $str . ')',
self::HtmlAttribute => match ($this->subType) {
'',
self::Url => $lq . 'LR\Filters::escapeHtmlAttr(' . $str . ')' . $rq,
self::JavaScript => $lq . 'LR\Filters::escapeHtmlAttr(LR\Filters::escapeJs(' . $str . '))' . $rq,
self::Css => $lq . 'LR\Filters::escapeHtmlAttr(LR\Filters::escapeCss(' . $str . '))' . $rq,
self::Url => 'LR\Filters::escapeHtmlAttr(' . $str . ')',
self::JavaScript => 'LR\Filters::escapeHtmlAttr(LR\Filters::escapeJs(' . $str . '))',
self::Css => 'LR\Filters::escapeHtmlAttr(LR\Filters::escapeCss(' . $str . '))',
},
self::HtmlComment => 'LR\Filters::escapeHtmlComment(' . $str . ')',
self::HtmlBogusTag => 'LR\Filters::escapeHtml(' . $str . ')',
Expand All @@ -162,7 +152,7 @@ public function escape(string $str): string
ContentType::Xml => match ($this->state) {
self::HtmlText,
self::HtmlBogusTag => 'LR\Filters::escapeXml(' . $str . ')',
self::HtmlAttribute => $lq . 'LR\Filters::escapeXml(' . $str . ')' . $rq,
self::HtmlAttribute => 'LR\Filters::escapeXml(' . $str . ')',
self::HtmlComment => 'LR\Filters::escapeHtmlComment(' . $str . ')',
self::HtmlTag => 'LR\Filters::escapeXmlTag(' . $str . ')',
default => throw new \LogicException("Unknown context $this->contentType, $this->state."),
Expand Down Expand Up @@ -218,19 +208,14 @@ public static function getConvertor(string $source, string $dest): ?callable
'html/attr/css' => 'convertHtmlToHtmlAttr',
'html/attr/url' => 'convertHtmlToHtmlAttr',
'html/comment' => 'escapeHtmlComment',
'html/unquoted-attr' => 'convertHtmlToUnquotedAttr',
],
'html/attr' => [
'html' => 'convertHtmlToHtmlAttr',
'html/unquoted-attr' => 'convertHtmlAttrToUnquotedAttr',
],
'html/attr/url' => [
'html' => 'convertHtmlToHtmlAttr',
'html/attr' => 'nop',
],
'html/unquoted-attr' => [
'html' => 'convertHtmlToHtmlAttr',
],
];

if ($source === $dest) {
Expand Down
1 change: 0 additions & 1 deletion src/Latte/Compiler/NodeHelpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ public static function toText(?Node $node): ?string

return match (true) {
$node instanceof Nodes\TextNode => $node->content,
$node instanceof Nodes\Html\QuotedValue => self::toText($node->value),
$node instanceof Nodes\NopNode => '',
default => null,
};
Expand Down
16 changes: 14 additions & 2 deletions src/Latte/Compiler/Nodes/Html/AttributeNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

use Latte\Compiler\NodeHelpers;
use Latte\Compiler\Nodes\AreaNode;
use Latte\Compiler\Nodes\FragmentNode;
use Latte\Compiler\Position;
use Latte\Compiler\PrintContext;

Expand All @@ -20,6 +21,7 @@ class AttributeNode extends AreaNode
public function __construct(
public AreaNode $name,
public ?AreaNode $value = null,
public ?string $quote = null,
public ?Position $position = null,
) {
}
Expand All @@ -29,9 +31,19 @@ public function print(PrintContext $context): string
{
$res = $this->name->print($context);
if ($this->value) {
$context->beginEscape()->enterHtmlAttribute(NodeHelpers::toText($this->name));
$res .= "echo '=';";
$res .= $this->value->print($context);
$res .= $this->quote ? 'echo ' . var_export($this->quote, true) . ';' : '';
$escaper = $context->beginEscape();
$escaper->enterHtmlAttribute(NodeHelpers::toText($this->name));
if ($this->value instanceof FragmentNode && $escaper->export() === 'html/attr/url') {
foreach ($this->value->children as $child) {
$res .= $child->print($context);
$escaper->enterHtmlAttribute(null);
}
} else {
$res .= $this->value->print($context);
}
$res .= $this->quote ? 'echo ' . var_export($this->quote, true) . ';' : '';
$context->restoreEscape();
}
return $res;
Expand Down
53 changes: 0 additions & 53 deletions src/Latte/Compiler/Nodes/Html/QuotedValue.php

This file was deleted.

60 changes: 22 additions & 38 deletions src/Latte/Compiler/TemplateParserHtml.php
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,11 @@ private function parseAttribute(): ?Node
throw $e;
}

$value = $this->parseAttributeValue();
[$value, $quote] = $this->parseAttributeValue();
return new Html\AttributeNode(
name: $name,
value: $value,
quote: $quote,
position: $name->position,
);
}
Expand All @@ -282,7 +283,7 @@ private function parseAttributeName(): ?AreaNode
}


private function parseAttributeValue(): ?AreaNode
private function parseAttributeValue(): ?array
{
$stream = $this->parser->getStream();
$save = $stream->getIndex();
Expand All @@ -293,25 +294,26 @@ private function parseAttributeValue(): ?AreaNode
}

$this->consumeIgnored();
return match ($stream->peek()->type) {
Token::Quote => $this->parseAttributeQuote(),
Token::Html_Name => $this->parser->parseText(),
Token::Latte_TagOpen => $this->parser->parseFragment(
function (FragmentNode $fragment) use ($stream) {
if ($fragment->children) {
return null;
}
return match ($stream->peek()->type) {
Token::Quote => $this->parseAttributeQuote(),
Token::Html_Name => $this->parser->parseText(),
Token::Latte_TagOpen => $this->parser->parseLatteStatement(),
Token::Latte_CommentOpen => $this->parser->parseLatteComment(),
default => null,
};
if ($quoteToken = $stream->tryConsume(Token::Quote)) {
$value = $this->parser->parseFragment(
fn() => match ($stream->peek()->type) {
Token::Quote => null,
default => $this->parser->inTextResolve(),
},
),
default => $stream->throwUnexpectedException(),
};
);
$stream->consume(Token::Quote);
return [$value, $quoteToken->text];
}

$value = $this->parser->parseFragment(
fn() => match ($stream->peek()->type) {
Token::Html_Name => $this->parser->parseText(),
Token::Latte_TagOpen => $this->parser->parseLatteStatement(),
Token::Latte_CommentOpen => $this->parser->parseLatteComment(),
default => null,
},
)->simplify() ?? $stream->throwUnexpectedException();
return [$value, $value instanceof Nodes\TextNode ? null : '"'];
}


Expand Down Expand Up @@ -360,24 +362,6 @@ private function parseNAttribute(): Nodes\TextNode
}


private function parseAttributeQuote(): Html\QuotedValue
{
$stream = $this->parser->getStream();
$quoteToken = $stream->consume(Token::Quote);
$value = $this->parser->parseFragment(fn() => match ($stream->peek()->type) {
Token::Quote => null,
default => $this->parser->inTextResolve(),
});
$node = new Html\QuotedValue(
value: $value,
quote: $quoteToken->text,
position: $quoteToken->position,
);
$stream->consume(Token::Quote);
return $node;
}


private function parseComment(): Html\CommentNode
{
$this->parser->lastIndentation = null;
Expand Down
18 changes: 0 additions & 18 deletions src/Latte/Runtime/Filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -210,24 +210,6 @@ public static function convertHtmlToHtmlAttr(string $s): string
}


/**
* Converts HTML text to unquoted attribute. The quotation marks need to be escaped.
*/
public static function convertHtmlToUnquotedAttr(string $s): string
{
return '"' . self::escapeHtmlAttr($s, false) . '"';
}


/**
* Converts HTML quoted attribute to unquoted.
*/
public static function convertHtmlAttrToUnquotedAttr(string $s): string
{
return '"' . $s . '"';
}


/**
* Converts HTML to plain text.
*/
Expand Down
4 changes: 2 additions & 2 deletions tests/common/Compiler.errors.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ Assert::exception(
);

Assert::exception(
fn() => $latte->compile('<span title={if true}{$a}{$b}{/if}></span>'),
fn() => $latte->compile('<span title={if true}"a"{/if}></span>'),
Latte\CompileException::class,
'Unexpected tag {=$b} (on line 1 at column 26)',
'Unexpected \'"a"{/if\', expecting {/if} (on line 1 at column 22)',
);

Assert::exception(
Expand Down
6 changes: 3 additions & 3 deletions tests/common/Compiler.unquoted.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ $template = <<<'EOD'
<span title={$x} {$x}></span>
<span title={if true}{$x}{else}"item"{/if}></span>
<span title={if true}{$x}{else}item{/if}></span>
<span {='title'}={$x}></span>
<span attr=c{$x}d></span> {* not supported *}
<span attr=c{$x}d></span>
<span onclick={$x} {$x}></span>
<span onclick=c{$x}d></span> {* not supported *}
<span onclick=c{$x}d></span>
<span attr{$x}b=c{$x}d></span> {* not supported *}

Expand Down
4 changes: 0 additions & 4 deletions tests/common/NodeHelpers.toText().phpt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ declare(strict_types=1);
use Latte\Compiler\NodeHelpers;
use Latte\Compiler\Nodes\AuxiliaryNode;
use Latte\Compiler\Nodes\FragmentNode;
use Latte\Compiler\Nodes\Html\QuotedValue;
use Latte\Compiler\Nodes\NopNode;
use Latte\Compiler\Nodes\TextNode;
use Tester\Assert;
Expand All @@ -28,8 +27,5 @@ Assert::same('helloworld!', NodeHelpers::toText($fragment));
$fragment->children[] = new NopNode; // is ignored by append
Assert::same('helloworld!', NodeHelpers::toText($fragment));

$fragment->append(new QuotedValue(new TextNode('quote'), '"'));
Assert::same('helloworld!quote', NodeHelpers::toText($fragment));

$fragment->append(new AuxiliaryNode(fn() => ''));
Assert::null(NodeHelpers::toText($fragment));
Loading

0 comments on commit e8a2d00

Please sign in to comment.