public function filter($selectors, $_skipHistory = false)
{
if ($selectors instanceof Callback or $selectors instanceof Closure) {
return $this->filterCallback($selectors, $_skipHistory);
}
if (!$_skipHistory) {
$this->elementsBackup = $this->elements;
}
$notSimpleSelector = array(' ', '>', '~', '+', '/');
if (!is_array($selectors)) {
$selectors = $this->parseSelector($selectors);
}
if (!$_skipHistory) {
$this->debug(array('Filtering:', $selectors));
}
$finalStack = array();
foreach ($selectors as $selector) {
$stack = array();
if (!$selector) {
break;
}
// avoid first space or /
if (in_array($selector[0], $notSimpleSelector)) {
$selector = array_slice($selector, 1);
}
// PER NODE selector chunks
foreach ($this->stack() as $node) {
$break = false;
foreach ($selector as $s) {
if (!$node instanceof DOMELEMENT) {
// all besides DOMElement
if ($s[0] == '[') {
$attr = trim($s, '[]');
if (mb_strpos($attr, '=')) {
list($attr, $val) = explode('=', $attr);
if ($attr == 'nodeType' && $node->nodeType != $val) {
$break = true;
}
}
} else {
$break = true;
}
} else {
// DOMElement only
// ID
if ($s[0] == '#') {
if ($node->getAttribute('id') != substr($s, 1)) {
$break = true;
}
// CLASSES
} elseif ($s[0] == '.') {
if (!$this->matchClasses($s, $node)) {
$break = true;
}
// ATTRS
} elseif ($s[0] == '[') {
// strip side brackets
$attr = trim($s, '[]');
if (mb_strpos($attr, '=')) {
list($attr, $val) = explode('=', $attr);
$val = self::unQuote($val);
if ($attr == 'nodeType') {
if ($val != $node->nodeType) {
$break = true;
}
} elseif ($this->isRegexp($attr)) {
$val = extension_loaded('mbstring') && phpQuery::$mbstringSupport ? quotemeta(trim($val, '"\'')) : preg_quote(trim($val, '"\''), '@');
// switch last character
switch (substr($attr, -1)) {
// quotemeta used insted of preg_quote
// http://code.google.com/p/phpquery/issues/detail?id=76
case '^':
$pattern = '^' . $val;
break;
case '*':
$pattern = '.*' . $val . '.*';
break;
case '$':
$pattern = '.*' . $val . '$';
break;
}
// cut last character
$attr = substr($attr, 0, -1);
$isMatch = extension_loaded('mbstring') && phpQuery::$mbstringSupport ? mb_ereg_match($pattern, $node->getAttribute($attr)) : preg_match("@{$pattern}@", $node->getAttribute($attr));
if (!$isMatch) {
$break = true;
}
} elseif ($node->getAttribute($attr) != $val) {
$break = true;
}
} elseif (!$node->hasAttribute($attr)) {
$break = true;
}
// PSEUDO CLASSES
} elseif ($s[0] == ':') {
// skip
// TAG
} elseif (trim($s)) {
if ($s != '*') {
// TODO namespaces
if (isset($node->tagName)) {
if ($node->tagName != $s) {
$break = true;
}
} elseif ($s == 'html' && !$this->isRoot($node)) {
$break = true;
}
}
// AVOID NON-SIMPLE SELECTORS
} elseif (in_array($s, $notSimpleSelector)) {
$break = true;
$this->debug(array('Skipping non simple selector', $selector));
}
}
if ($break) {
break;
}
}
// if element passed all chunks of selector - add it to new stack
if (!$break) {
$stack[] = $node;
}
}
$tmpStack = $this->elements;
$this->elements = $stack;
// PER ALL NODES selector chunks
foreach ($selector as $s) {
// PSEUDO CLASSES
if ($s[0] == ':') {
$this->pseudoClasses($s);
}
}
foreach ($this->elements as $node) {
// XXX it should be merged without duplicates
// but jQuery doesnt do that
$finalStack[] = $node;
}
$this->elements = $tmpStack;
}
$this->elements = $finalStack;
if ($_skipHistory) {
return $this;
} else {
$this->debug('Stack length after filter(): ' . count($finalStack));
return $this->newInstance();
}
}