Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Implement presence and equality attributes.

  • Loading branch information...
commit 6c87be97ef0900da27603c793f61e4ac0535143c 1 parent e87901a
@markstory markstory authored
Showing with 113 additions and 6 deletions.
  1. +41 −1 lib/Cake/Test/Case/Utility/Set2Test.php
  2. +72 −5 lib/Cake/Utility/Set2.php
View
42 lib/Cake/Test/Case/Utility/Set2Test.php
@@ -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',
@@ -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']);
+ }
+
}
View
77 lib/Cake/Utility/Set2.php
@@ -63,8 +63,13 @@ 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:
*
@@ -72,7 +77,7 @@ public static function get(array $data, $path) {
* - `{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.
@@ -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__';
@@ -132,7 +147,7 @@ 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.
@@ -140,6 +155,13 @@ protected static function _traverse(array &$data, $path, $callback) {
$next[] = $v;
}
}
+ } else {
+ // attributes
+ foreach ($item as $k => $v) {
+ if (self::_matches(array($k => $v), $token)) {
+ $next[] = $v;
+ }
+ }
}
}
$context = array($_key => $next);
@@ -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) {
}
Please sign in to comment.
Something went wrong with that request. Please try again.