Skip to content

Commit 6a7b120

Browse files
authored
Merge pull request #27 from mficzel/feature/afxComments
FEATURE: Support html comment syntax in afx
2 parents fe56270 + ddba23c commit 6a7b120

File tree

7 files changed

+299
-17
lines changed

7 files changed

+299
-17
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Neos\Fusion\Afx\Parser\Expression;
5+
6+
/*
7+
* This file is part of the Neos.Fusion.Afx package.
8+
*
9+
* (c) Contributors of the Neos Project - www.neos.io
10+
*
11+
* This package is Open Source Software. For the full copyright and license
12+
* information, please view the LICENSE file which was distributed with this
13+
* source code.
14+
*/
15+
16+
use Neos\Fusion\Afx\Parser\AfxParserException;
17+
use Neos\Fusion\Afx\Parser\Lexer;
18+
19+
/**
20+
* Class Expression
21+
* @package Neos\Fusion\Afx\Parser\Expression
22+
*/
23+
class Comment
24+
{
25+
/**
26+
* @param Lexer $lexer
27+
* @return string
28+
* @throws AfxParserException
29+
*/
30+
public static function parse(Lexer $lexer)
31+
{
32+
if ($lexer->isOpeningBracket() && $lexer->peek(4) === '<!--') {
33+
$lexer->consume();
34+
$lexer->consume();
35+
$lexer->consume();
36+
$lexer->consume();
37+
} else {
38+
throw new AfxParserException(sprintf('Unexpected comment start'));
39+
}
40+
$currentComment = '';
41+
while (true) {
42+
if ($lexer->isMinus() && $lexer->peek(3) === '-->') {
43+
$lexer->consume();
44+
$lexer->consume();
45+
$lexer->consume();
46+
return $currentComment;
47+
}
48+
if ($lexer->isEnd()) {
49+
throw new AfxParserException(sprintf('Comment not closed.'));
50+
}
51+
$currentComment .= $lexer->consume();
52+
}
53+
}
54+
}

Classes/Parser/Expression/Node.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public static function parse(Lexer $lexer): array
4343
while ($lexer->isWhitespace()) {
4444
$lexer->consume();
4545
}
46+
4647
while (!$lexer->isForwardSlash() && !$lexer->isClosingBracket()) {
4748
if ($lexer->isOpeningBrace()) {
4849
$attributes[] = [

Classes/Parser/Expression/NodeList.php

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,25 +34,26 @@ public static function parse(Lexer $lexer): array
3434
while (!$lexer->isEnd()) {
3535
if ($lexer->isOpeningBracket()) {
3636
$lexer->consume();
37-
37+
if ($currentText) {
38+
$contents[] = [
39+
'type' => 'text',
40+
'payload' => $currentText
41+
];
42+
}
3843
if ($lexer->isForwardSlash()) {
3944
$lexer->rewind();
40-
if ($currentText) {
41-
$contents[] = [
42-
'type' => 'text',
43-
'payload' => $currentText
44-
];
45-
}
4645
return $contents;
46+
}
47+
if ($lexer->isExclamationMark()) {
48+
$lexer->rewind();
49+
$contents[] = [
50+
'type' => 'comment',
51+
'payload' => Comment::parse($lexer)
52+
];
53+
$currentText = '';
54+
continue;
4755
} else {
4856
$lexer->rewind();
49-
50-
if ($currentText) {
51-
$contents[] = [
52-
'type' => 'text',
53-
'payload' => $currentText
54-
];
55-
}
5657
$contents[] = [
5758
'type' => 'node',
5859
'payload' => Node::parse($lexer)

Classes/Parser/Lexer.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,16 @@ public function isDoubleQuote(): bool
223223
return $this->currentCharacter === '"';
224224
}
225225

226+
/**
227+
* Checks if the current character is an exclamation mark
228+
*
229+
* @return boolean
230+
*/
231+
public function isExclamationMark(): bool
232+
{
233+
return $this->currentCharacter === '!';
234+
}
235+
226236
/**
227237
* Checks if the iteration has ended
228238
*

Classes/Service/AfxService.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,13 +309,15 @@ protected static function astNodeListToFusion($payload, $indentation = '')
309309
return $astNode;
310310
}, $payload);
311311

312-
// filter empty text nodes
312+
// filter empty text nodes and comments
313313
$payload = array_filter($payload, function ($astNode) {
314314
if ($astNode['type'] === 'text' && $astNode['payload'] == '') {
315315
return false;
316-
} else {
317-
return true;
318316
}
317+
if ($astNode['type'] === 'comment') {
318+
return false;
319+
}
320+
return true;
319321
});
320322

321323
if (count($payload) === 0) {

Tests/Functional/AfxServiceTest.php

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,87 @@ public function textsAreEscaped(): void
713713
$this->assertEquals($expectedFusion, AfxService::convertAfxToFusion($afxCode));
714714
}
715715

716+
/**
717+
* @test
718+
*/
719+
public function commentsAreIgnored(): void
720+
{
721+
$afxCode = <<<'EOF'
722+
<!-- comment before -->
723+
<h1>Example<!-- comment inside -->Content</h1>
724+
<!-- comment after -->
725+
EOF;
726+
727+
$expectedFusion = <<<'EOF'
728+
Neos.Fusion:Tag {
729+
tagName = 'h1'
730+
content = Neos.Fusion:Array {
731+
item_1 = 'Example'
732+
item_2 = 'Content'
733+
}
734+
}
735+
EOF;
736+
$this->assertEquals($expectedFusion, AfxService::convertAfxToFusion($afxCode));
737+
}
738+
739+
/**
740+
* @test
741+
*/
742+
public function standaloneCommentsAreIgnored(): void
743+
{
744+
$afxCode = <<<'EOF'
745+
<!-- comment -->
746+
EOF;
747+
748+
$expectedFusion = <<<'EOF'
749+
''
750+
EOF;
751+
$this->assertEquals($expectedFusion, AfxService::convertAfxToFusion($afxCode));
752+
}
753+
754+
/**
755+
* @test
756+
*/
757+
public function standaloneCommentsChildrenAreIgnored(): void
758+
{
759+
$afxCode = <<<'EOF'
760+
<h1><!-- comment --></h1>
761+
EOF;
762+
763+
$expectedFusion = <<<'EOF'
764+
Neos.Fusion:Tag {
765+
tagName = 'h1'
766+
content = ''
767+
}
768+
EOF;
769+
$this->assertEquals($expectedFusion, AfxService::convertAfxToFusion($afxCode));
770+
}
771+
772+
/**
773+
* @test
774+
*/
775+
public function multilineCommentsAreIgnored(): void
776+
{
777+
$afxCode = <<<'EOF'
778+
<h1>
779+
<!--
780+
comment
781+
with
782+
multiple
783+
lines
784+
-->
785+
</h1>
786+
EOF;
787+
788+
$expectedFusion = <<<'EOF'
789+
Neos.Fusion:Tag {
790+
tagName = 'h1'
791+
content = ''
792+
}
793+
EOF;
794+
$this->assertEquals($expectedFusion, AfxService::convertAfxToFusion($afxCode));
795+
}
796+
716797
/**
717798
* @test
718799
*/

Tests/Functional/ParserTest.php

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,119 @@ public function shouldHandleWhitespace(): void
747747
);
748748
}
749749

750+
/**
751+
* @test
752+
*/
753+
public function shouldParseComments(): void
754+
{
755+
$parser = new Parser('<!-- lorem ipsum -->');
756+
$this->assertEquals(
757+
[
758+
[
759+
'type' => 'comment',
760+
'payload' => ' lorem ipsum '
761+
]
762+
],
763+
$parser->parse()
764+
);
765+
}
766+
767+
/**
768+
* @test
769+
*/
770+
public function shouldIgnoreTagsAndExpressionsInComments(): void
771+
{
772+
$parser = new Parser('<!-- <foo>{bar}</foo> -->');
773+
$this->assertEquals(
774+
[
775+
[
776+
'type' => 'comment',
777+
'payload' => ' <foo>{bar}</foo> '
778+
]
779+
],
780+
$parser->parse()
781+
);
782+
}
783+
784+
/**
785+
* @test
786+
*/
787+
public function shouldParseCommentsBeforeContent(): void
788+
{
789+
$parser = new Parser('<!--lorem ipsum--><div />');
790+
$this->assertEquals(
791+
[
792+
[
793+
'type' => 'comment',
794+
'payload' => 'lorem ipsum'
795+
],
796+
[
797+
'type' => 'node',
798+
'payload' => [
799+
'identifier' => 'div',
800+
'attributes' => [],
801+
'selfClosing' => true,
802+
'children' => []
803+
]
804+
]
805+
],
806+
$parser->parse()
807+
);
808+
}
809+
810+
/**
811+
* @test
812+
*/
813+
public function shouldParseCommentsAfterContent(): void
814+
{
815+
$parser = new Parser('<div/><!--lorem ipsum-->');
816+
$this->assertEquals(
817+
[
818+
[
819+
'type' => 'node',
820+
'payload' => [
821+
'identifier' => 'div',
822+
'attributes' => [],
823+
'selfClosing' => true,
824+
'children' => []
825+
]
826+
],
827+
[
828+
'type' => 'comment',
829+
'payload' => 'lorem ipsum'
830+
]
831+
],
832+
$parser->parse()
833+
);
834+
}
835+
836+
/**
837+
* @test
838+
*/
839+
public function shouldParseCommentsInsideContent(): void
840+
{
841+
$parser = new Parser('<div><!--lorem ipsum--></div>');
842+
$this->assertEquals(
843+
[
844+
[
845+
'type' => 'node',
846+
'payload' => [
847+
'identifier' => 'div',
848+
'attributes' => [],
849+
'selfClosing' => false,
850+
'children' => [
851+
[
852+
'type' => 'comment',
853+
'payload' => 'lorem ipsum'
854+
]
855+
]
856+
]
857+
]
858+
],
859+
$parser->parse()
860+
);
861+
}
862+
750863
/**
751864
* @test
752865
*/
@@ -806,4 +919,24 @@ public function shouldThrowExceptionForUnclosedSpreadExpression(): void
806919
$parser = new Parser('<div {...bar() />');
807920
$parser->parse();
808921
}
922+
923+
/**
924+
* @test
925+
*/
926+
public function shouldThrowExceptionForWronglyStartedComment()
927+
{
928+
$this->expectException(AfxParserException::class);
929+
$parser = new Parser('<div><! foo --></div>');
930+
$parser->parse();
931+
}
932+
933+
/**
934+
* @test
935+
*/
936+
public function shouldThrowExceptionForCommentWithoutProperEnd()
937+
{
938+
$this->expectException(AfxParserException::class);
939+
$parser = new Parser('<div><!-- foo </div>');
940+
$parser->parse();
941+
}
809942
}

0 commit comments

Comments
 (0)