Skip to content

Commit

Permalink
Implement presence and equality attributes.
Browse files Browse the repository at this point in the history
  • Loading branch information
markstory committed Mar 27, 2012
1 parent e87901a commit 6c87be9
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 6 deletions.
42 changes: 41 additions & 1 deletion lib/Cake/Test/Case/Utility/Set2Test.php
Expand Up @@ -54,7 +54,7 @@ public static function articleData() {
),
array(
'Article' => array(
'id' => '3',
'id' => '2',
'user_id' => '1',
'title' => 'Second Article',
'body' => 'Second Article Body',
Expand Down Expand Up @@ -697,4 +697,44 @@ public function testExtractStringKey() {
$this->assertEquals(array('foo'), $result);
}

/**
* Test the attribute presense selector.
*
* @return void
*/
public function testExtractAttributePresence() {
$data = self::articleData();

$result = Set2::extract($data, '{n}.Article[published]');
$expected = array($data['1']['Article']);
$this->assertEquals($expected, $result);

$result = Set2::extract($data, '{n}.Article[id][published]');
$expected = array($data['1']['Article']);
$this->assertEquals($expected, $result);
}

/**
* Test = and != operators.
*
* @return void
*/
public function testExtractAttributeEquality() {
$data = self::articleData();

$result = Set2::extract($data, '{n}.Article[id=3]');
$expected = array($data[2]['Article']);
$this->assertEquals($expected, $result);

$result = Set2::extract($data, '{n}.Article[id = 3]');
$expected = array($data[2]['Article']);
$this->assertEquals($expected, $result, 'Whitespace should not matter.');

$result = Set2::extract($data, '{n}.Article[id!=3]');
$this->assertEquals(1, $result[0]['id']);
$this->assertEquals(2, $result[1]['id']);
$this->assertEquals(4, $result[2]['id']);
$this->assertEquals(5, $result[3]['id']);
}

}
77 changes: 72 additions & 5 deletions lib/Cake/Utility/Set2.php
Expand Up @@ -63,16 +63,21 @@ public static function get(array $data, $path) {
* - `{n}` Matches any numeric key.
* - `{s}` Matches any string key.
* - `[id]` Matches elements with an `id` index.
* - `[id>2]` Matches elements that have an `id` index greater than 2. Other operators
* are `>`, `<`, `<=`, `>=`, `==`, and `=//` which allows you to use regular expression matching.
* - `[id>2]` Matches elements that have an `id` index greater than 2.
*
* There are a number of attribute operators:
*
* - `=`, `!=` Equality.
* - `>`, `<`, `>=`, `<=` Value comparison.
* - `=/.../` Regular expression pattern match.
*
* Given a set of User array data, from a `$User->find('all')` call:
*
* - `1.User.name` Get the name of the user at index 1.
* - `{n}.User.name` Get the name of every user in the set of users.
* - `{n}.User[id]` Get the name of every user with an id key.
* - `{n}.User[id>=2]` Get the name of every user with an id key greater than or equal to 2.
* - `{n}.User[username=/^paul/]` Get User elements with username matching `^paul`.
* - `{n}.User[username=/^paul/]` Get User elements with username containing `^paul`.
*
* @param array $data The data to extract from.
* @param string $path The path to extract.
Expand All @@ -97,10 +102,20 @@ public static function extract(array $data, $path) {
/**
* Traverses $data for $path. $callback is called for each terminal element.
* The results of all the callbacks are returned.
*
* @param array $data The data to traverse.
* @param string $path The set path to walk.
* @param callable $callback to call on the result set.
* @return array Results of the callback mapped over the leaf nodes of the path expression.
*/
protected static function _traverse(array &$data, $path, $callback) {
$result = array();
$tokens = String::tokenize($path, '.', '{', '}');

if (strpos('[', $path) === false) {
$tokens = explode('.', $path);
} else {
$tokens = String::tokenize($path, '.', '[', ']');
}

$_key = '__set_item__';

Expand Down Expand Up @@ -132,14 +147,21 @@ protected static function _traverse(array &$data, $path, $callback) {
$next[] = $v;
}
}
} else {
} elseif (strpos($token, '[') === false) {
// bare string key
foreach ($item as $k => $v) {
// index or key match.
if ($k === $token) {
$next[] = $v;
}
}
} else {
// attributes
foreach ($item as $k => $v) {
if (self::_matches(array($k => $v), $token)) {
$next[] = $v;
}
}
}
}
$context = array($_key => $next);
Expand All @@ -149,6 +171,51 @@ protected static function _traverse(array &$data, $path, $callback) {
return array_map($callback, $context[$_key]);
}

/**
* Checks whether or not $data matches the selector
*
* @param array $data Array of data to match.
* @param string $selector The selector to match.
* @return boolean Fitness of expression.
*/
protected static function _matches(array $data, $selector) {
preg_match_all(
'/(?<key>[^\[]+?)? (\[ (?<attr>.+?) (?: \s* (?<op>[><!]?[=]) \s* (?<val>.*) )? \])+/x',
$selector,
$conditions,
PREG_SET_ORDER
);

foreach ($conditions as $cond) {
$key = $cond['key'];
$attr = $cond['attr'];
$op = isset($cond['op']) ? $cond['op'] : null;
$val = isset($cond['val']) ? $cond['val'] : null;

if ($key && !isset($data[$key])) {
return false;
}

// Presence test.
if (empty($op) && empty($val)) {
return isset($data[$key][$attr]);
}

// Empty attribute = fail.
if (!isset($data[$key][$attr])) {
return false;
}
$prop = $data[$key][$attr];

if ($op === '=') {
return $prop == $val;
} elseif ($op === '!=') {
return $prop != $val;
}
}
return false;
}

public static function insert(array $data, $path, $values = null) {

}
Expand Down

0 comments on commit 6c87be9

Please sign in to comment.