diff --git a/Makefile b/Makefile index 6b23fcd..017c195 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ composer-update: test: docker-compose run --rm php-${PHP} php -v - docker-compose run --rm php-${PHP} php /app/vendor/bin/phpunit -c /app/phpunit.xml.dist + docker-compose run --rm php-${PHP} php /app/vendor/bin/phpunit -c /app/phpunit.xml.dist --filter "${TEST}" test-local: php -v php vendor/bin/phpunit diff --git a/src/Parser/RegularParser.php b/src/Parser/RegularParser.php index d46a36a..fb0b853 100644 --- a/src/Parser/RegularParser.php +++ b/src/Parser/RegularParser.php @@ -35,6 +35,12 @@ final class RegularParser implements ParserInterface const TOKEN_STRING = 6; const TOKEN_WS = 7; + const VALUE_REGULAR = 0x01; + const VALUE_AGGRESSIVE = 0x02; + + /** @var int */ + public $valueMode = self::VALUE_REGULAR; + public function __construct(SyntaxInterface $syntax = null) { $this->lexerRegex = $this->prepareLexer($syntax ?: new CommonSyntax()); @@ -238,10 +244,18 @@ private function value() return $this->match(self::TOKEN_DELIMITER, false) ? $value : false; } - if('' !== $tmp = $this->match(self::TOKEN_STRING, false)) { - $value .= $tmp; - while('' !== $tmp = $this->match(self::TOKEN_STRING, false)) { - $value .= $tmp; + if($this->lookahead(self::TOKEN_STRING) || $this->lookahead(self::TOKEN_MARKER)) { + while(true) { + if($this->lookahead(self::TOKEN_WS) || $this->lookahead(self::TOKEN_CLOSE)) { + break; + } + if($this->lookaheadN(array(self::TOKEN_MARKER, self::TOKEN_CLOSE))) { + if($this->valueMode === self::VALUE_AGGRESSIVE) { + $value .= $this->match(null, false); + } + break; + } + $value .= $this->match(null, false); } return $value; @@ -302,6 +316,33 @@ private function lookahead($type) return $this->position < $this->tokensCount && $this->tokens[$this->position][0] === $type; } + /** + * @param int[] $types + * + * @return bool + */ + private function lookaheadN(array $types) + { + $count = count($types); + if($this->position + $count > $this->tokensCount) { + return false; + } + + $position = $this->position; + foreach($types as $type) { + // note: automatically skips whitespace tokens + if($this->tokens[$position][0] === self::TOKEN_WS) { + $position++; + } + if($type !== $this->tokens[$position][0]) { + return false; + } + $position++; + } + + return true; + } + /** * @param int|null $type * @param bool $ws diff --git a/tests/ParserTest.php b/tests/ParserTest.php index a4b5df9..d9840fe 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -232,6 +232,12 @@ public function provideShortcodes() array($s, '[a=0 b=0]0[/a]', array( new ParsedShortcode(new Shortcode('a', array('b' => '0'), '0', '0'), '[a=0 b=0]0[/a]', 0), )), + array($s, '[x=/[/] [y a=/"//] [z=http://url/] [a=http://url ]', array( + new ParsedShortcode(new Shortcode('x', array(), null, '/['), '[x=/[/]', 0), + new ParsedShortcode(new Shortcode('y', array('a' => '/"/'), null, null), '[y a=/"//]', 8), + new ParsedShortcode(new Shortcode('z', array(), null, 'http://url'), '[z=http://url/]', 19), + new ParsedShortcode(new Shortcode('a', array(), null, 'http://url'), '[a=http://url ]', 35), + )), ); /** @@ -246,7 +252,7 @@ public function provideShortcodes() * * Tests cases from array above with identifiers in the array below must be skipped. */ - $wordpressSkip = array(3, 6, 16, 21, 22, 23, 25, 32, 33, 34, 46, 47, 49, 51); + $wordpressSkip = array(3, 6, 16, 21, 22, 23, 25, 32, 33, 34, 46, 47, 49, 51, 52); $result = array(); foreach($tests as $key => $test) { $syntax = array_shift($test); @@ -270,12 +276,35 @@ public function testIssue77() new ParsedShortcode(new Shortcode('x', array(), '', null), '[x][/x]', 3), new ParsedShortcode(new Shortcode('y', array(), 'x', null), '[y]x[/y]', 22), )); - $this->assertShortcodes($parser->parse('[a k="v][x][/x]'), array( new ParsedShortcode(new Shortcode('x', array(), '', null), '[x][/x]', 8), )); } + public function testValueModeAggressive() + { + $parser = new RegularParser(new CommonSyntax()); + $parser->valueMode = RegularParser::VALUE_AGGRESSIVE; + $parsed = $parser->parse('[x=/[/] [y a=/"//] [z=http://url/] [a=http://url ]'); + $tested = array( + new ParsedShortcode(new Shortcode('x', array(), null, '/[/'), '[x=/[/]', 0), + new ParsedShortcode(new Shortcode('y', array('a' => '/"//'), null, null), '[y a=/"//]', 8), + new ParsedShortcode(new Shortcode('z', array(), null, 'http://url/'), '[z=http://url/]', 19), + new ParsedShortcode(new Shortcode('a', array(), null, 'http://url'), '[a=http://url ]', 35), + ); + + $count = count($tested); + static::assertCount($count, $parsed, 'counts'); + for ($i = 0; $i < $count; $i++) { + static::assertSame($tested[$i]->getName(), $parsed[$i]->getName(), 'name'); + static::assertSame($tested[$i]->getParameters(), $parsed[$i]->getParameters(), 'parameters'); + static::assertSame($tested[$i]->getContent(), $parsed[$i]->getContent(), 'content'); + static::assertSame($tested[$i]->getText(), $parsed[$i]->getText(), 'text'); + static::assertSame($tested[$i]->getOffset(), $parsed[$i]->getOffset(), 'offset'); + static::assertSame($tested[$i]->getBbCode(), $parsed[$i]->getBbCode(), 'bbCode'); + } + } + public function testWordPress() { $parser = new WordpressParser();