Skip to content

Commit

Permalink
Merge pull request #44 from norzechowicz/text-matcher
Browse files Browse the repository at this point in the history
Added text matcher
  • Loading branch information
Norbert Orzechowicz committed Dec 14, 2014
2 parents d761fb6 + 73c009c commit cb81445
Show file tree
Hide file tree
Showing 11 changed files with 298 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/Coduo/PHPMatcher/AST/Pattern.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public function __construct(Type $type)
}

/**
* @return mixed
* @return Type
*/
public function getType()
{
Expand Down
7 changes: 7 additions & 0 deletions src/Coduo/PHPMatcher/Exception/UnknownTypeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Coduo\PHPMatcher\Exception;

class UnknownTypeException extends Exception
{
}
3 changes: 2 additions & 1 deletion src/Coduo/PHPMatcher/Factory/SimpleFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ protected function buildMatchers()
$scalarMatchers,
$arrayMatcher,
new Matcher\JsonMatcher($arrayMatcher),
new Matcher\XmlMatcher($arrayMatcher)
new Matcher\XmlMatcher($arrayMatcher),
new Matcher\TextMatcher($scalarMatchers, $this->buildParser())
));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function __construct($boundary)
*/
public function match($value)
{
if (!is_float($value) && !is_integer($value) && !is_double($value)) {
if (!is_float($value) && !is_integer($value) && !is_double($value) && !is_numeric($value)) {
$this->error = sprintf("Value \"%s\" is not a valid number.", new String($value));
return false;
}
Expand Down
26 changes: 26 additions & 0 deletions src/Coduo/PHPMatcher/Matcher/Pattern/RegexConverter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Coduo\PHPMatcher\Matcher\Pattern;

use Coduo\PHPMatcher\Exception\UnknownTypeException;

class RegexConverter
{
public function toRegex(TypePattern $type)
{
switch ($type->getType()) {
case 'string':
case 'wildcard':
case '*':
return "(.+)";
case 'number':
return "(\\-?[0-9]*[\\.|\\,]?[0-9]*)";
case 'integer':
return "(\\-?[0-9]*)";
case 'double':
return "(\\-?[0-9]*[\\.|\\,][0-9]*)";
default:
throw new UnknownTypeException();
}
}
}
8 changes: 8 additions & 0 deletions src/Coduo/PHPMatcher/Matcher/Pattern/TypePattern.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ public function is($type)
return strtolower($this->type) === strtolower($type);
}

/**
* @return string
*/
public function getType()
{
return strtolower($this->type);
}

/**
* {@inheritdoc}
*/
Expand Down
147 changes: 147 additions & 0 deletions src/Coduo/PHPMatcher/Matcher/TextMatcher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<?php

namespace Coduo\PHPMatcher\Matcher;

use Coduo\PHPMatcher\Matcher\Pattern\TypePattern;
use Coduo\PHPMatcher\Parser;
use Coduo\PHPMatcher\Matcher\Pattern\RegexConverter;
use Coduo\ToString\String;

class TextMatcher extends Matcher
{
const PATTERN_REGEXP = "/@[a-zA-Z\\.]+@(\\.[a-zA-Z0-9_]+\\([a-zA-Z0-9{},:@\\.\"'\\(\\)]*\\))*/";

const PATTERN_REGEXP_PLACEHOLDER_TEMPLATE = "__PLACEHOLDER%d__";

/**
* @var Parser
*/
private $parser;

/**
* @var ValueMatcher
*/
private $matcher;

/**
* @param ValueMatcher $matcher
* @param Parser $parser
*/
public function __construct(ValueMatcher $matcher, Parser $parser)
{
$this->parser = $parser;
$this->matcher = $matcher;
}

/**
* {@inheritDoc}
*/
public function match($value, $pattern)
{
if (!is_string($value)) {
$this->error = sprintf("%s \"%s\" is not a valid string.", gettype($value), new String($value));
return false;
}

$patternRegex = $pattern;
$patternsReplacedWithRegex = $this->replaceTypePatternsWithPlaceholders($patternRegex);
$patternRegex = $this->prepareRegex($patternRegex);
$patternRegex = $this->replacePlaceholderWithPatternRegexes($patternRegex, $patternsReplacedWithRegex);

if (!preg_match($patternRegex, $value, $matchedValues)) {
$this->error = sprintf("\"%s\" does not match \"%s\" pattern", $value, $pattern);
return false;
}

array_shift($matchedValues); // remove matched string

if (count($patternsReplacedWithRegex) !== count($matchedValues)) {
$this->error = "Unexpected TextMatcher error.";
return false;
}

foreach ($patternsReplacedWithRegex as $index => $typePattern) {
if (!$typePattern->matchExpanders($matchedValues[$index])) {
$this->error = $typePattern->getError();
return false;
}
}

return true;
}

/**
* {@inheritDoc}
*/
public function canMatch($pattern)
{
if (!is_string($pattern)) {
return false;
}

return true;
}

/**
* Reaplce each type pattern (@[email protected]("lorem")) with placeholder, in order
* to use preg_quote without destroying pattern & expanders.
*
* before replacement: "/users/@[email protected](200)/active"
* after replacement: "/users/__PLACEHOLDER0__/active"
*
* @param string $patternRegex
* @return TypePattern[]|array
*/
private function replaceTypePatternsWithPlaceholders(&$patternRegex)
{
$patternsReplacedWithRegex = array();
preg_match_all(self::PATTERN_REGEXP, $patternRegex, $matches);

foreach ($matches[0] as $index => $typePatternString) {
$typePattern = $this->parser->parse($typePatternString);
$patternsReplacedWithRegex[] = $typePattern;
$patternRegex = str_replace(
$typePatternString,
sprintf(self::PATTERN_REGEXP_PLACEHOLDER_TEMPLATE, $index),
$patternRegex
);
}

return $patternsReplacedWithRegex;
}


/**
* Replace placeholders with type pattern regular expressions
* before replacement: "/users/__PLACEHOLDER0__/active"
* after replacement: "/^\/users\/(\-?[0-9]*)\/active$/"
*
* @param $patternRegex
* @return string
* @throws \Coduo\PHPMatcher\Exception\UnknownTypeException
*/
private function replacePlaceholderWithPatternRegexes($patternRegex, array $patternsReplacedWithRegex)
{
$regexConverter = new RegexConverter();
foreach ($patternsReplacedWithRegex as $index => $typePattern) {
$patternRegex = str_replace(
sprintf(self::PATTERN_REGEXP_PLACEHOLDER_TEMPLATE, $index),
$regexConverter->toRegex($typePattern),
$patternRegex
);
}

return $patternRegex;
}

/**
* Prepare regular expression
*
* @param string $patternRegex
* @return string
*/
private function prepareRegex($patternRegex)
{
return "/^" . preg_quote($patternRegex, '/') . "$/";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public static function examplesProvider()
array(-20, -10.5, true),
array(10, 1, false),
array(1, 1, false),
array(10, "20", true)
);
}

Expand Down
27 changes: 27 additions & 0 deletions tests/Coduo/PHPMatcher/Matcher/Pattern/RegexConverterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Coduo\PHPMatcher\Tests\Matcher\Pattern;

use Coduo\PHPMatcher\Matcher\Pattern\TypePattern;
use Coduo\PHPMatcher\Matcher\Pattern\RegexConverter;

class RegexConverterTest extends \PHPUnit_Framework_TestCase
{
/**
* @var RegexConverter
*/
private $converter;

public function setUp()
{
$this->converter = new RegexConverter();
}

/**
* @expectedException \Coduo\PHPMatcher\Exception\UnknownTypeException
*/
public function test_convert_unknown_type()
{
$this->converter->toRegex(new TypePattern("not_a_type"));
}
}
68 changes: 68 additions & 0 deletions tests/Coduo/PHPMatcher/Matcher/TextMatcherTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace Coduo\PHPMatcher\Tests\Matcher;

use Coduo\PHPMatcher\Lexer;
use Coduo\PHPMatcher\Matcher;
use Coduo\PHPMatcher\Parser;

class TextMatcherTest extends \PHPUnit_Framework_TestCase
{
/**
* @var Matcher\TextMatcher
*/
private $matcher;

public function setUp()
{
$parser = new Parser(new Lexer(), new Parser\ExpanderInitializer());
$scalarMatchers = new Matcher\ChainMatcher(array(
new Matcher\CallbackMatcher(),
new Matcher\ExpressionMatcher(),
new Matcher\NullMatcher(),
new Matcher\StringMatcher($parser),
new Matcher\IntegerMatcher($parser),
new Matcher\BooleanMatcher(),
new Matcher\DoubleMatcher($parser),
new Matcher\NumberMatcher(),
new Matcher\ScalarMatcher(),
new Matcher\WildcardMatcher(),
));
$this->matcher = new Matcher\TextMatcher(
new Matcher\ChainMatcher(array(
$scalarMatchers,
new Matcher\ArrayMatcher($scalarMatchers, $parser)
)),
$parser
);
}

/**
* @dataProvider matchingData
*/
public function test_positive_matches($value, $pattern, $expectedResult)
{
$this->assertEquals($expectedResult, $this->matcher->match($value, $pattern));
}

public function matchingData()
{
return array(
array(
"lorem ipsum lol lorem 24 dolorem",
"lorem ipsum @[email protected](\"lo\") lorem @number@ dolorem",
true
),
array(
"lorem ipsum 24 dolorem",
"lorem ipsum @integer@",
false
),
array(
"/users/12345/active",
"/users/@[email protected](0)/active",
true
)
);
}
}
11 changes: 10 additions & 1 deletion tests/Coduo/PHPMatcher/MatcherTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public function setUp()
$scalarMatchers,
$arrayMatcher,
new Matcher\JsonMatcher($arrayMatcher),
new Matcher\XmlMatcher($arrayMatcher)
new Matcher\XmlMatcher($arrayMatcher),
new Matcher\TextMatcher($scalarMatchers, $parser)
)));
}

Expand Down Expand Up @@ -194,6 +195,14 @@ public function test_matcher_with_xml()
$this->assertTrue(match($xml, $xmlPattern));
}

public function test_text_matcher()
{
$value = "lorem ipsum 1234 random text";
$pattern = "@[email protected]('lo') ipsum @[email protected](10) random text";
$this->assertTrue($this->matcher->match($value, $pattern));
$this->assertTrue(match($value, $pattern));
}

public function test_matcher_with_captures()
{
$this->assertTrue($this->matcher->match(
Expand Down

0 comments on commit cb81445

Please sign in to comment.