Added initial XPath support for Set::extract

git-svn-id: https://svn.cakephp.org/repo/branches/1.2.x.x@6567 3807eeeb-6ff5-0310-8944-8be069107fe0
This commit is contained in:
the_undefined 2008-03-14 01:05:56 +00:00
parent 3a67bdc06d
commit 3daf495488
2 changed files with 268 additions and 4 deletions

View file

@ -359,6 +359,125 @@ class Set extends Object {
} }
return $out; return $out;
} }
/**
* undocumented function
*
* @param string $path
* @param string $data
* @param string $options
* @return void
* @author Felix
*/
function extract($path, $data = null, $options = array()) {
if (is_array($path) || empty($data)) {
return Set::classicExtract($path, $data);
}
$contexts = $data;
$options = am(array('flatten' => true), $options);
if (!isset($contexts[0])) {
$contexts = array($data);
}
if (is_string($path)) {
$last = substr($path, -1, 1);
if ($last == '*' && substr($path, -2, 1) != '/') {
$path = substr($path, 0, -1);
} else {
$last = false;
}
$tokens = array_slice(explode('/', $path), 1);
}
do {
$token = array_shift($tokens);
$conditions = false;
if (preg_match_all('/\[([^\]]+)\]/', $token, $m)) {
$conditions = $m[1];
$token = substr($token, 0, strpos($token, '['));
}
$matches = array();
foreach ($contexts as $i => $context) {
if (!isset($context['trace'])) {
$context = array('trace' => array(), 'item' => $context, 'key' => null);
}
if ($token == '..') {
$context['item'] = Set::extract(join('/', $context['trace']), $data);
$context['key'] = array_pop($context['trace']);
$context['item'] = $context['item'][0][$context['key']];
$matches[] = $context;
continue;
}
if (array_key_exists($token, $context['item']) && (!$conditions || Set::matches($conditions, $context['item'][$token], $i+1))) {
$context['trace'][] = $context['key'];
$context['key'] = $token;
$context['item'] = $context['item'][$token];
$matches[] = $context;
}
}
if (empty($tokens)) {
break;
}
$contexts = $matches;
} while(1);
$r = array();
foreach ($matches as $match) {
if (!$options['flatten'] || is_array($match['item'])) {
$r[] = array($match['key'] => $match['item']);
} else {
$r[] = $match['item'];
}
}
return $r;
}
/**
* This function can be used to see if a single item or a given xpath match certain conditions.
*
* @param mixed $conditions An array of condition strings
* @param array $data
* @param integer $i Optional: The 'nth'-number of the item being matched.
* @return boolean
* @author Felix
*/
function matches($conditions, $data = array(), $i = null) {
if (empty($conditions)) {
return true;
}
if (is_string($conditions)) {
return !!Set::extract($conditions, $data);
}
foreach ($conditions as $condition) {
if (!preg_match('/(.+?)([><!]?[=]|[><])(.+)/', $condition, $match)) {
if (ctype_digit($condition)) {
if ($i != $condition) {
return false;
}
} elseif (preg_match_all('/(?:^[0-9]+|(?<=,)[0-9]+)/', $condition, $matches)) {
return in_array($i, $matches[0]);
} elseif (!array_key_exists($condition, $data)) {
return false;
}
continue;
}
list(,$key,$op,$expected) = $match;
$val = $data[$key];
if ($op == '=' && $val != $expected) {
return false;
} elseif ($op == '!=' && $val == $expected) {
return false;
} elseif ($op == '>' && $val <= $expected) {
return false;
} elseif ($op == '<' && $val >= $expected) {
return false;
} elseif ($op == '<=' && $val > $expected) {
return false;
} elseif ($op == '>=' && $val < $expected) {
return false;
}
}
return true;
}
/** /**
* Gets a value from an array or object that is contained in a given path using an array path syntax, i.e.: * Gets a value from an array or object that is contained in a given path using an array path syntax, i.e.:
* "{n}.Person.{[a-z]+}" - Where "{n}" represents a numeric key, "Person" represents a string literal, * "{n}.Person.{[a-z]+}" - Where "{n}" represents a numeric key, "Person" represents a string literal,
@ -370,7 +489,7 @@ class Set extends Object {
* @return array Extracted data * @return array Extracted data
* @access public * @access public
*/ */
function extract($data, $path = null) { function classicExtract($data, $path = null) {
if ($path === null && is_a($this, 'set')) { if ($path === null && is_a($this, 'set')) {
$path = $data; $path = $data;
$data = $this->get(); $data = $this->get();
@ -405,7 +524,7 @@ class Set extends Object {
if (empty($tmpPath)) { if (empty($tmpPath)) {
$tmp[] = $val; $tmp[] = $val;
} else { } else {
$tmp[] = Set::extract($val, $tmpPath); $tmp[] = Set::classicExtract($val, $tmpPath);
} }
} }
} }
@ -417,7 +536,7 @@ class Set extends Object {
if (empty($tmpPath)) { if (empty($tmpPath)) {
$tmp[] = $val; $tmp[] = $val;
} else { } else {
$tmp[] = Set::extract($val, $tmpPath); $tmp[] = Set::classicExtract($val, $tmpPath);
} }
} }
} }
@ -431,7 +550,7 @@ class Set extends Object {
if (empty($tmpPath)) { if (empty($tmpPath)) {
$tmp[$j] = $val; $tmp[$j] = $val;
} else { } else {
$tmp[$j] = Set::extract($val, $tmpPath); $tmp[$j] = Set::classicExtract($val, $tmpPath);
} }
} }
} }

View file

@ -236,6 +236,151 @@ class SetTest extends UnitTestCase {
} }
function testExtract() { function testExtract() {
$a = array(
array(
'Article' => array('id' => '1', 'user_id' => '1', 'title' => 'First Article', 'body' => 'First Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31'),
'User' => array('id' => '1', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'),
'Comment' => array(
array('id' => '1', 'article_id' => '1', 'user_id' => '2', 'comment' => 'First Comment for First Article', 'published' => 'Y', 'created' => '2007-03-18 10:45:23', 'updated' => '2007-03-18 10:47:31'),
array('id' => '2', 'article_id' => '1', 'user_id' => '4', 'comment' => 'Second Comment for First Article', 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31'),
),
'Tag' => array(
array('id' => '1', 'tag' => 'tag1', 'created' => '2007-03-18 12:22:23', 'updated' => '2007-03-18 12:24:31'),
array('id' => '2', 'tag' => 'tag2', 'created' => '2007-03-18 12:24:23', 'updated' => '2007-03-18 12:26:31')
),
'Deep' => array(
'Nesting' => array(
'test' => array(
1 => 'foo',
2 => array(
'and' => array('more' => 'stuff')
)
)
)
)
),
array(
'Article' => array('id' => '3', 'user_id' => '1', 'title' => 'Third Article', 'body' => 'Third Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31'),
'User' => array('id' => '2', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'),
'Comment' => array(),
'Tag' => array()
),
array(
'Article' => array('id' => '3', 'user_id' => '1', 'title' => 'Third Article', 'body' => 'Third Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31'),
'User' => array('id' => '3', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'),
'Comment' => array(),
'Tag' => array()
),
array(
'Article' => array('id' => '3', 'user_id' => '1', 'title' => 'Third Article', 'body' => 'Third Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31'),
'User' => array('id' => '4', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'),
'Comment' => array(),
'Tag' => array()
),
array(
'Article' => array('id' => '3', 'user_id' => '1', 'title' => 'Third Article', 'body' => 'Third Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31'),
'User' => array('id' => '5', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'),
'Comment' => array(),
'Tag' => array()
)
);
$b = array('Deep' => $a[0]['Deep']);
$c = array(
array(
'a' => array(
'I' => array(
'a' => 1
)
)
),
array(
'a' => array(
2
)
),
array(
'a' => array(
'II' => array(
'a' => 3,
'III' => array(
'a' => array('foo' => 4)
)
)
)
),
);
$expected = array(
$c[0], $c[0]['a']['I'], $c[1], $c[2], array('a' => $c[2]['a']['II']['a']), $c[2]['a']['II']['III']
);
$expected = array(1,2,3,4,5);
$r = Set::extract('/User/id', $a);
$this->assertEqual($r, $expected);
$expected = array(array('id' => 1), array('id' => 2), array('id' => 3), array('id' => 4), array('id' => 5));
$r = Set::extract('/User/id', $a, array('flatten' => false));
$this->assertEqual($r, $expected);
$expected = array(array('test' => $a[0]['Deep']['Nesting']['test']));
$this->assertEqual(Set::extract('/Deep/Nesting/test', $a), $expected);
$this->assertEqual(Set::extract('/Deep/Nesting/test', $b), $expected);
$expected = array(array('test' => $a[0]['Deep']['Nesting']['test']));
$r = Set::extract('/Deep/Nesting/test/1/..', $a);
$this->assertEqual($r, $expected);
$expected = array(array('test' => $a[0]['Deep']['Nesting']['test']));
$r = Set::extract('/Deep/Nesting/test/2/and/../..', $a);
$this->assertEqual($r, $expected);
$expected = array(array('test' => $a[0]['Deep']['Nesting']['test']));
$r = Set::extract('/Deep/Nesting/test/2/../../../Nesting/test/2/..', $a);
$this->assertEqual($r, $expected);
$expected = array(2);
$r = Set::extract('/User[2]/id', $a);
$this->assertEqual($r, $expected);
$expected = array(4, 5);
$r = Set::extract('/User[id>3]/id', $a);
$this->assertEqual($r, $expected);
$expected = array(2, 3);
$r = Set::extract('/User[id>1][id<=3]/id', $a);
$this->assertEqual($r, $expected);
}
/**
* undocumented function
*
* @return void
* @author Felix
*/
function testMatches() {
$a = array(
array('Article' => array('id' => 1, 'title' => 'Article 1')),
array('Article' => array('id' => 2, 'title' => 'Article 2')),
array('Article' => array('id' => 3, 'title' => 'Article 3')));
$this->assertTrue(Set::matches(array('id=2'), $a[1]['Article']));
$this->assertFalse(Set::matches(array('id>2'), $a[1]['Article']));
$this->assertTrue(Set::matches(array('id>=2'), $a[1]['Article']));
$this->assertTrue(Set::matches(array('id>1'), $a[1]['Article']));
$this->assertTrue(Set::matches(array('id>1', 'id<3', 'id!=0'), $a[1]['Article']));
$this->assertTrue(Set::matches(array('3'), null, 3));
$this->assertTrue(Set::matches(array('5'), null, 5));
$this->assertTrue(Set::matches(array('id'), $a[1]['Article']));
$this->assertTrue(Set::matches(array('id', 'title'), $a[1]['Article']));
$this->assertFalse(Set::matches(array('non-existant'), $a[1]['Article']));
$this->assertTrue(Set::matches('/Article[id=2]', $a));
$this->assertFalse(Set::matches('/Article[id=4]', $a));
}
function testClassicExtract() {
$a = array( $a = array(
array('Article' => array('id' => 1, 'title' => 'Article 1')), array('Article' => array('id' => 1, 'title' => 'Article 1')),
array('Article' => array('id' => 2, 'title' => 'Article 2')), array('Article' => array('id' => 2, 'title' => 'Article 2')),