From e145094fd8bc2bd35c5e7f292fb27204b2ea61d8 Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Tue, 30 May 2017 16:25:56 +0200 Subject: [PATCH] feat: Parser returns nodeList instead of singleNode The parser now returns a nodeList instead of a single node as result. This enables the parsing of multiple elements that are not encapsulated in a container. --- src/Expression/Node.php | 2 +- src/Expression/{Children.php => NodeList.php} | 16 +- src/Parser.php | 2 +- tests/ParserTest.php | 574 +++++++++++++----- 4 files changed, 433 insertions(+), 161 deletions(-) rename src/Expression/{Children.php => NodeList.php} (88%) diff --git a/src/Expression/Node.php b/src/Expression/Node.php index ecb1aec..96a2050 100644 --- a/src/Expression/Node.php +++ b/src/Expression/Node.php @@ -58,7 +58,7 @@ public static function parse(Lexer $lexer) throw new Exception(sprintf('Tag "%s" did not end with closing bracket.', $identifier)); } - $children = Children::parse($lexer); + $children = NodeList::parse($lexer); if ($lexer->isOpeningBracket()) { $lexer->consume(); diff --git a/src/Expression/Children.php b/src/Expression/NodeList.php similarity index 88% rename from src/Expression/Children.php rename to src/Expression/NodeList.php index 78e4508..6d40b55 100644 --- a/src/Expression/Children.php +++ b/src/Expression/NodeList.php @@ -4,16 +4,13 @@ use PackageFactory\Afx\Exception; use PackageFactory\Afx\Lexer; -class Children +class NodeList { public static function parse(Lexer $lexer) { $contents = []; $currentText = ''; - while (true) { - if ($lexer->isEnd()) { - throw new Exception('Unfinished child-list'); - } + while (!$lexer->isEnd()) { if ($lexer->isOpeningBracket()) { $lexer->consume(); @@ -61,11 +58,16 @@ public static function parse(Lexer $lexer) continue; } - - $currentText .= $lexer->consume(); } + if ($lexer->isEnd() && $currentText) { + $contents[] = [ + 'type' => 'text', + 'payload' => $currentText + ]; + } + return $contents; } } diff --git a/src/Parser.php b/src/Parser.php index c9956c4..759d6e6 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -10,6 +10,6 @@ public function __construct($string) public function parse() { - return Expression\Node::parse($this->lexer); + return Expression\NodeList::parse($this->lexer); } } diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 9da352d..51354ec 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -11,26 +11,46 @@ class ParserTest extends TestCase public function shouldParseSingleTag() { $parser = new Parser('
'); - $this->assertEquals([ - 'identifier' => 'div', - 'props' => [], - 'children' => [], - 'selfClosing' => false - ], $parser->parse()); + + $this->assertEquals( + [ + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'div', + 'props' => [], + 'children' => [], + 'selfClosing' => false + ] + ] + ], + $parser->parse() + ); } + + /** * @test */ public function shouldParseSingleSelfClosingTag() { $parser = new Parser('
'); - $this->assertEquals([ - 'identifier' => 'div', - 'props' => [], - 'children' => [], - 'selfClosing' => true - ], $parser->parse()); + + $this->assertEquals( + [ + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'div', + 'props' => [], + 'children' => [], + 'selfClosing' => true + ] + ] + ], + $parser->parse() + ); } /** @@ -39,12 +59,21 @@ public function shouldParseSingleSelfClosingTag() public function shouldParseSingleSelfClosingTagWithWhitespaces() { $parser = new Parser('
'); - $this->assertEquals([ - 'identifier' => 'div', - 'props' => [], - 'children' => [], - 'selfClosing' => true - ], $parser->parse()); + + $this->assertEquals( + [ + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'div', + 'props' => [], + 'children' => [], + 'selfClosing' => true + ] + ] + ], + $parser->parse() + ); } /** @@ -53,12 +82,21 @@ public function shouldParseSingleSelfClosingTagWithWhitespaces() public function shouldParseSingleTagWithWhitespaces() { $parser = new Parser('
'); - $this->assertEquals([ - 'identifier' => 'div', - 'props' => [], - 'children' => [], - 'selfClosing' => false - ], $parser->parse()); + + $this->assertEquals( + [ + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'div', + 'props' => [], + 'children' => [], + 'selfClosing' => false + ] + ] + ], + $parser->parse() + ); } /** @@ -67,17 +105,26 @@ public function shouldParseSingleTagWithWhitespaces() public function shouldParseSingleSelfClosingTagWithSingleAttribute() { $parser = new Parser('
'); - $this->assertEquals([ - 'identifier' => 'div', - 'props' => [ - 'prop' => [ - 'type' => 'string', - 'payload' => 'value' + + $this->assertEquals( + [ + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'div', + 'props' => [ + 'prop' => [ + 'type' => 'string', + 'payload' => 'value' + ] + ], + 'children' => [], + 'selfClosing' => true + ] ] ], - 'children' => [], - 'selfClosing' => true - ], $parser->parse()); + $parser->parse() + ); } /** @@ -86,21 +133,31 @@ public function shouldParseSingleSelfClosingTagWithSingleAttribute() public function shouldParseSingleSelfClosingTagWithMultipleAttributes() { $parser = new Parser('
'); - $this->assertEquals([ - 'identifier' => 'div', - 'props' => [ - 'prop' => [ - 'type' => 'string', - 'payload' => 'value' - ], - 'anotherProp' => [ - 'type' => 'string', - 'payload' => 'Another Value' + + $this->assertEquals( + [ + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'div', + 'props' => [ + 'prop' => [ + 'type' => 'string', + 'payload' => 'value' + ], + 'anotherProp' => [ + 'type' => 'string', + 'payload' => 'Another Value' + ] + ], + 'children' => [], + 'selfClosing' => true + ] ] ], - 'children' => [], - 'selfClosing' => true - ], $parser->parse()); + $parser->parse() + ); + } /** @@ -109,137 +166,264 @@ public function shouldParseSingleSelfClosingTagWithMultipleAttributes() public function shouldParseSingleSelfClosingTagWithMultipleAttributesWrappedByMultipleWhitespaces() { $parser = new Parser('
'); - $this->assertEquals([ - 'identifier' => 'div', - 'props' => [ - 'prop' => [ - 'type' => 'string', - 'payload' => 'value' - ], - 'anotherProp' => [ - 'type' => 'string', - 'payload' => 'Another Value' + + $this->assertEquals( + [ + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'div', + 'props' => [ + 'prop' => [ + 'type' => 'string', + 'payload' => 'value' + ], + 'anotherProp' => [ + 'type' => 'string', + 'payload' => 'Another Value' + ] + ], + 'children' => [], + 'selfClosing' => true + ] ] ], - 'children' => [], - 'selfClosing' => true - ], $parser->parse()); + $parser->parse() + ); } /** * @test */ - public function propsCanHaveDashesInTheirName() + public function shouldParseListOfTags() { - $parser = new Parser('
'); - $this->assertEquals([ - 'identifier' => 'div', - 'props' => [ - 'prop-1' => [ - 'type' => 'string', - 'payload' => 'value' + $parser = new Parser('

'); + + $this->assertEquals( + [ + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'div', + 'props' => [], + 'children' => [], + 'selfClosing' => false + ] + ], + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'span', + 'props' => [], + 'children' => [], + 'selfClosing' => false + ] ], - 'prop-2' => [ - 'type' => 'string', - 'payload' => 'Another Value' + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'h1', + 'props' => [], + 'children' => [], + 'selfClosing' => false + ] ] ], - 'children' => [], - 'selfClosing' => true - ], $parser->parse()); + $parser->parse() + ); } /** * @test */ - public function shouldParseSingleTagWithSeparateClosingTag() + public function shouldParseListOfTagsAndTextsWithTextOutside() { - $parser = new Parser('
'); - $this->assertEquals([ - 'identifier' => 'div', - 'props' => [], - 'children' => [], - 'selfClosing' => false - ], $parser->parse()); + $parser = new Parser('foo
bar'); + + $this->assertEquals( + [ + [ + 'type' => 'text', + 'payload' => 'foo' + ], + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'div', + 'props' => [], + 'children' => [], + 'selfClosing' => false + ] + ], + [ + 'type' => 'text', + 'payload' => 'bar' + ] + ], + $parser->parse() + ); } /** * @test */ - public function shouldParseSingleTagWithSeparateClosingTagAndOneChild() + public function shouldParseListOfTagsAndTextsWithTagsOutside() { - $parser = new Parser('
Hello World!
'); - $this->assertEquals([ - 'identifier' => 'div', - 'props' => [], - 'children' => [ + $parser = new Parser('
foobar'); + + $this->assertEquals( + [ + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'div', + 'props' => [], + 'children' => [], + 'selfClosing' => false + ] + ], [ 'type' => 'text', - 'payload' => 'Hello World!' + 'payload' => 'foobar' + ], + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'span', + 'props' => [], + 'children' => [], + 'selfClosing' => false + ] ] ], - 'selfClosing' => false - ], $parser->parse()); + $parser->parse() + ); } /** * @test */ - public function shouldParseNestedSelfClosingTag() + public function shouldParseListOfTagsAndTextsWithWhitepaceOutside() { - $parser = new Parser('
'); - $this->assertEquals([ - 'identifier' => 'div', - 'props' => [], - 'children' => [ + $parser = new Parser('
'); + + $this->assertEquals( + [ + [ + 'type' => 'text', + 'payload' => ' ' + ], [ 'type' => 'node', 'payload' => [ - 'identifier' => 'input', + 'identifier' => 'div', 'props' => [], 'children' => [], + 'selfClosing' => false + ] + ], + [ + 'type' => 'text', + 'payload' => ' ' + ] + ], + $parser->parse() + ); + } + + /** + * @test + */ + public function propsCanHaveDashesInTheirName() + { + $parser = new Parser('
'); + + $this->assertEquals( + [ + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'div', + 'props' => [ + 'prop-1' => [ + 'type' => 'string', + 'payload' => 'value' + ], + 'prop-2' => [ + 'type' => 'string', + 'payload' => 'Another Value' + ] + ], + 'children' => [], 'selfClosing' => true ] ] ], - 'selfClosing' => false - ], $parser->parse()); + $parser->parse() + ); } /** * @test */ - public function shouldParseNestedTags() + public function shouldParseSingleTagWithSeparateClosingTag() { - $parser = new Parser('
Header
Content
Footer
'); - $this->assertEquals([ - 'identifier' => 'article', - 'props' => [], - 'children' => [ + $parser = new Parser('
'); + + $this->assertEquals( + [ + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'div', + 'props' => [], + 'children' => [], + 'selfClosing' => false + ] + ] + ], + $parser->parse() + ); + + } + + /** + * @test + */ + public function shouldParseSingleTagWithSeparateClosingTagAndOneChild() + { + $parser = new Parser('
Hello World!
'); + + $this->assertEquals( + [ [ 'type' => 'node', 'payload' => [ - 'identifier' => 'header', + 'identifier' => 'div', 'props' => [], 'children' => [ [ - 'type' => 'node', - 'payload' => [ - 'identifier' => 'div', - 'props' => [], - 'children' => [ - [ - 'type' => 'text', - 'payload' => 'Header' - ] - ], - 'selfClosing' => false - ] + 'type' => 'text', + 'payload' => 'Hello World!' ] ], 'selfClosing' => false ] - ], + ] + ], + $parser->parse() + ); + } + + /** + * @test + */ + public function shouldParseNestedSelfClosingTag() + { + $parser = new Parser('
'); + + $this->assertEquals( + [ [ 'type' => 'node', 'payload' => [ @@ -247,19 +431,62 @@ public function shouldParseNestedTags() 'props' => [], 'children' => [ [ - 'type' => 'text', - 'payload' => 'Content' + 'type' => 'node', + 'payload' => [ + 'identifier' => 'input', + 'props' => [], + 'children' => [], + 'selfClosing' => true + ] ] ], 'selfClosing' => false ] - ], + ] + ], + $parser->parse() + ); + } + + /** + * @test + */ + public function shouldParseNestedTags() + { + $parser = new Parser('
Header
Content
'); + + $this->assertEquals( + [ [ 'type' => 'node', 'payload' => [ - 'identifier' => 'footer', + 'identifier' => 'article', 'props' => [], 'children' => [ + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'header', + 'props' => [], + 'children' => [ + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'div', + 'props' => [], + 'children' => [ + [ + 'type' => 'text', + 'payload' => 'Header' + ] + ], + 'selfClosing' => false + ] + ] + ], + 'selfClosing' => false + ] + ], [ 'type' => 'node', 'payload' => [ @@ -268,7 +495,31 @@ public function shouldParseNestedTags() 'children' => [ [ 'type' => 'text', - 'payload' => 'Footer' + 'payload' => 'Content' + ] + ], + 'selfClosing' => false + ] + ], + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'footer', + 'props' => [], + 'children' => [ + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'div', + 'props' => [], + 'children' => [ + [ + 'type' => 'text', + 'payload' => 'Footer' + ] + ], + 'selfClosing' => false + ] ] ], 'selfClosing' => false @@ -279,8 +530,10 @@ public function shouldParseNestedTags() ] ] ], - 'selfClosing' => false - ], $parser->parse()); + $parser->parse() + ); + + } /** @@ -294,40 +547,58 @@ public function shouldHandleWhitespace() Text
'); - $this->assertEquals([ - 'identifier' => 'div', - 'props' => [], - 'children' => [ - [ - 'type' => 'text', - 'payload' => ' - ' - ], - [ - 'type' => 'node', - 'payload' => [ - 'identifier' => 'input', - 'props' => [], - 'children' => [], - 'selfClosing' => true - ] - ], + + $this->assertEquals( + [ [ 'type' => 'text', - 'payload' => ' - ' + 'payload' => ' ' ], [ 'type' => 'node', 'payload' => [ - 'identifier' => 'label', + 'identifier' => 'div', 'props' => [], 'children' => [ [ 'type' => 'text', - 'payload' => 'Some + 'payload' => ' + ' + ], + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'input', + 'props' => [], + 'children' => [], + 'selfClosing' => true + ] + ], + [ + 'type' => 'text', + 'payload' => ' + ' + ], + [ + 'type' => 'node', + 'payload' => [ + 'identifier' => 'label', + 'props' => [], + 'children' => [ + [ + 'type' => 'text', + 'payload' => 'Some Text' + ] + ], + 'selfClosing' => false + ] + ], + [ + 'type' => 'text', + 'payload' => ' + ' ] ], 'selfClosing' => false @@ -335,12 +606,11 @@ public function shouldHandleWhitespace() ], [ 'type' => 'text', - 'payload' => ' - ' + 'payload' => ' ' ] ], - 'selfClosing' => false - ], $parser->parse()); + $parser->parse() + ); } /**