From 023102b319bf98b7770ed9644a9bb46a98cd0e81 Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Sun, 13 Oct 2019 10:38:33 +0200 Subject: [PATCH 1/3] FEATURE: Support html comments in AFX parser Comments are returned as afx node of type comment and ignore all expressions and tags inside. --- Classes/Parser/Expression/Comment.php | 54 ++++++++++ Classes/Parser/Expression/Node.php | 1 + Classes/Parser/Expression/NodeList.php | 28 +++--- Classes/Parser/Lexer.php | 10 ++ Tests/Functional/ParserTest.php | 133 +++++++++++++++++++++++++ 5 files changed, 212 insertions(+), 14 deletions(-) create mode 100644 Classes/Parser/Expression/Comment.php diff --git a/Classes/Parser/Expression/Comment.php b/Classes/Parser/Expression/Comment.php new file mode 100644 index 0000000..abcd888 --- /dev/null +++ b/Classes/Parser/Expression/Comment.php @@ -0,0 +1,54 @@ +isOpeningBracket() && $lexer->peek(4) === '') { + $lexer->consume(); + $lexer->consume(); + $lexer->consume(); + return $currentComment; + } + if ($lexer->isEnd()) { + throw new AfxParserException(sprintf('Comment not closed.')); + } + $currentComment .= $lexer->consume(); + } + } +} diff --git a/Classes/Parser/Expression/Node.php b/Classes/Parser/Expression/Node.php index 5272cec..01bb384 100644 --- a/Classes/Parser/Expression/Node.php +++ b/Classes/Parser/Expression/Node.php @@ -43,6 +43,7 @@ public static function parse(Lexer $lexer): array while ($lexer->isWhitespace()) { $lexer->consume(); } + while (!$lexer->isForwardSlash() && !$lexer->isClosingBracket()) { if ($lexer->isOpeningBrace()) { $attributes[] = [ diff --git a/Classes/Parser/Expression/NodeList.php b/Classes/Parser/Expression/NodeList.php index 47c732b..3455b32 100644 --- a/Classes/Parser/Expression/NodeList.php +++ b/Classes/Parser/Expression/NodeList.php @@ -34,25 +34,25 @@ public static function parse(Lexer $lexer): array while (!$lexer->isEnd()) { if ($lexer->isOpeningBracket()) { $lexer->consume(); - + if ($currentText) { + $contents[] = [ + 'type' => 'text', + 'payload' => $currentText + ]; + } if ($lexer->isForwardSlash()) { $lexer->rewind(); - if ($currentText) { - $contents[] = [ - 'type' => 'text', - 'payload' => $currentText - ]; - } return $contents; + } elseif ($lexer->isExclamationMark()) { + $lexer->rewind(); + $contents[] = [ + 'type' => 'comment', + 'payload' => Comment::parse($lexer) + ]; + $currentText = ''; + continue; } else { $lexer->rewind(); - - if ($currentText) { - $contents[] = [ - 'type' => 'text', - 'payload' => $currentText - ]; - } $contents[] = [ 'type' => 'node', 'payload' => Node::parse($lexer) diff --git a/Classes/Parser/Lexer.php b/Classes/Parser/Lexer.php index c50d64b..059a4dd 100644 --- a/Classes/Parser/Lexer.php +++ b/Classes/Parser/Lexer.php @@ -223,6 +223,16 @@ public function isDoubleQuote(): bool return $this->currentCharacter === '"'; } + /** + * Checks if the current character is an exclamation mark + * + * @return boolean + */ + public function isExclamationMark(): bool + { + return $this->currentCharacter === '!'; + } + /** * Checks if the iteration has ended * diff --git a/Tests/Functional/ParserTest.php b/Tests/Functional/ParserTest.php index 7edad61..e6ca7c1 100644 --- a/Tests/Functional/ParserTest.php +++ b/Tests/Functional/ParserTest.php @@ -747,6 +747,119 @@ public function shouldHandleWhitespace(): void ); } + /** + * @test + */ + public function shouldParseComments(): void + { + $parser = new Parser(''); + $this->assertEquals( + [ + [ + 'type' => 'comment', + 'payload' => ' lorem ipsum ' + ] + ], + $parser->parse() + ); + } + + /** + * @test + */ + public function shouldIgnoreTagsAndExpressionsInComments(): void + { + $parser = new Parser(''); + $this->assertEquals( + [ + [ + 'type' => 'comment', + 'payload' => ' {bar} ' + ] + ], + $parser->parse() + ); + } + + /** + * @test + */ + public function shouldParseCommentsBeforeContent(): void + { + $parser = new Parser('
'); + $this->assertEquals( + [ + [ + 'type' => 'comment', + 'payload' => 'lorem ipsum' + ], + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'div', + 'attributes' => [], + 'selfClosing' => true, + 'children' => [] + ] + ] + ], + $parser->parse() + ); + } + + /** + * @test + */ + public function shouldParseCommentsAfterContent(): void + { + $parser = new Parser('
'); + $this->assertEquals( + [ + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'div', + 'attributes' => [], + 'selfClosing' => true, + 'children' => [] + ] + ], + [ + 'type' => 'comment', + 'payload' => 'lorem ipsum' + ] + ], + $parser->parse() + ); + } + + /** + * @test + */ + public function shouldParseCommentsInsideContent(): void + { + $parser = new Parser('
'); + $this->assertEquals( + [ + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'div', + 'attributes' => [], + 'selfClosing' => false, + 'children' => [ + [ + 'type' => 'comment', + 'payload' => 'lorem ipsum' + ] + ] + ] + ] + ], + $parser->parse() + ); + } + /** * @test */ @@ -806,4 +919,24 @@ public function shouldThrowExceptionForUnclosedSpreadExpression(): void $parser = new Parser('
'); $parser->parse(); } + + /** + * @test + */ + public function shouldThrowExceptionForWronglyStartedComment() + { + $this->expectException(AfxParserException::class); + $parser = new Parser('
'); + $parser->parse(); + } + + /** + * @test + */ + public function shouldThrowExceptionForCommentWithoutProperEnd() + { + $this->expectException(AfxParserException::class); + $parser = new Parser('
+

ExampleContent

+ +EOF; + + $expectedFusion = <<<'EOF' +Neos.Fusion:Tag { + tagName = 'h1' + content = Neos.Fusion:Array { + item_1 = 'Example' + item_2 = 'Content' + } +} +EOF; + $this->assertEquals($expectedFusion, AfxService::convertAfxToFusion($afxCode)); + } + + /** + * @test + */ + public function standaloneCommentsAreIgnored(): void + { + $afxCode = <<<'EOF' + +EOF; + + $expectedFusion = <<<'EOF' +'' +EOF; + $this->assertEquals($expectedFusion, AfxService::convertAfxToFusion($afxCode)); + } + + /** + * @test + */ + public function standaloneCommentsChildrenAreIgnored(): void + { + $afxCode = <<<'EOF' +

+EOF; + + $expectedFusion = <<<'EOF' +Neos.Fusion:Tag { + tagName = 'h1' + content = '' +} +EOF; + $this->assertEquals($expectedFusion, AfxService::convertAfxToFusion($afxCode)); + } + + /** + * @test + */ + public function multilineCommentsAreIgnored(): void + { + $afxCode = <<<'EOF' +

+ +

+EOF; + + $expectedFusion = <<<'EOF' +Neos.Fusion:Tag { + tagName = 'h1' + content = '' +} +EOF; + $this->assertEquals($expectedFusion, AfxService::convertAfxToFusion($afxCode)); + } + /** * @test */ From ddba23c7084b0848aaa514dd403d9cf8d82d4e42 Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Fri, 22 Nov 2019 23:03:47 +0100 Subject: [PATCH 3/3] TASK: Adjust coding style and avoid pointless elseif after returns --- Classes/Parser/Expression/NodeList.php | 3 ++- Classes/Service/AfxService.php | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Classes/Parser/Expression/NodeList.php b/Classes/Parser/Expression/NodeList.php index 3455b32..ed96229 100644 --- a/Classes/Parser/Expression/NodeList.php +++ b/Classes/Parser/Expression/NodeList.php @@ -43,7 +43,8 @@ public static function parse(Lexer $lexer): array if ($lexer->isForwardSlash()) { $lexer->rewind(); return $contents; - } elseif ($lexer->isExclamationMark()) { + } + if ($lexer->isExclamationMark()) { $lexer->rewind(); $contents[] = [ 'type' => 'comment', diff --git a/Classes/Service/AfxService.php b/Classes/Service/AfxService.php index 8952e01..b766402 100644 --- a/Classes/Service/AfxService.php +++ b/Classes/Service/AfxService.php @@ -313,11 +313,11 @@ protected static function astNodeListToFusion($payload, $indentation = '') $payload = array_filter($payload, function ($astNode) { if ($astNode['type'] === 'text' && $astNode['payload'] == '') { return false; - } elseif ($astNode['type'] === 'comment') { + } + if ($astNode['type'] === 'comment') { return false; - } else { - return true; } + return true; }); if (count($payload) === 0) {