public function find($selectors, $context = null, $noHistory = false)
{
if (!$noHistory) {
// backup last stack /for end()/
$this->elementsBackup = $this->elements;
}
// allow to define context
// TODO combine code below with phpQuery::pq() context guessing code
// as generic function
if ($context) {
if (!is_array($context) && $context instanceof DOMELEMENT) {
$this->elements = array($context);
} elseif (is_array($context)) {
$this->elements = array();
foreach ($context as $c) {
if ($c instanceof DOMELEMENT) {
$this->elements[] = $c;
}
}
} elseif ($context instanceof self) {
$this->elements = $context->elements;
}
}
$queries = $this->parseSelector($selectors);
$this->debug(array('FIND', $selectors, $queries));
$XQuery = '';
// remember stack state because of multi-queries
$oldStack = $this->elements;
// here we will be keeping found elements
$stack = array();
foreach ($queries as $selector) {
$this->elements = $oldStack;
$delimiterBefore = false;
foreach ($selector as $s) {
// TAG
$isTag = extension_loaded('mbstring') && phpQuery::$mbstringSupport ? mb_ereg_match('^[\\w|\\||-]+$', $s) || $s == '*' : preg_match('@^[\\w|\\||-]+$@', $s) || $s == '*';
if ($isTag) {
if ($this->isXML()) {
// namespace support
if (mb_strpos($s, '|') !== false) {
$ns = $tag = null;
list($ns, $tag) = explode('|', $s);
$XQuery .= "{$ns}:{$tag}";
} elseif ($s == '*') {
$XQuery .= '*';
} else {
$XQuery .= "*[local-name()='{$s}']";
}
} else {
$XQuery .= $s;
}
// ID
} elseif ($s[0] == '#') {
if ($delimiterBefore) {
$XQuery .= '*';
}
$XQuery .= "[@id='" . substr($s, 1) . "']";
// ATTRIBUTES
} elseif ($s[0] == '[') {
if ($delimiterBefore) {
$XQuery .= '*';
}
// strip side brackets
$attr = trim($s, '][');
$execute = false;
// attr with specifed value
if (mb_strpos($s, '=')) {
$value = null;
list($attr, $value) = explode('=', $attr);
$value = trim($value, "'\"");
if ($this->isRegexp($attr)) {
// cut regexp character
$attr = substr($attr, 0, -1);
$execute = true;
$XQuery .= "[@{$attr}]";
} else {
$XQuery .= "[@{$attr}='{$value}']";
}
// attr without specified value
} else {
$XQuery .= "[@{$attr}]";
}
if ($execute) {
$this->runQuery($XQuery, $s, 'is');
$XQuery = '';
if (!$this->length()) {
break;
}
}
// CLASSES
} elseif ($s[0] == '.') {
// TODO use return $this->find("./self::*[contains(concat(\" \",@class,\" \"), \" $class \")]");
// thx wizDom ;)
if ($delimiterBefore) {
$XQuery .= '*';
}
$XQuery .= '[@class]';
$this->runQuery($XQuery, $s, 'matchClasses');
$XQuery = '';
if (!$this->length()) {
break;
}
// ~ General Sibling Selector
} elseif ($s[0] == '~') {
$this->runQuery($XQuery);
$XQuery = '';
$this->elements = $this->siblings(substr($s, 1))->elements;
if (!$this->length()) {
break;
}
// + Adjacent sibling selectors
} elseif ($s[0] == '+') {
// TODO /following-sibling::
$this->runQuery($XQuery);
$XQuery = '';
$subSelector = substr($s, 1);
$subElements = $this->elements;
$this->elements = array();
foreach ($subElements as $node) {
// search first DOMElement sibling
$test = $node->nextSibling;
while ($test && !$test instanceof DOMELEMENT) {
$test = $test->nextSibling;
}
if ($test && $this->is($subSelector, $test)) {
$this->elements[] = $test;
}
}
if (!$this->length()) {
break;
}
// PSEUDO CLASSES
} elseif ($s[0] == ':') {
// TODO optimization for :first :last
if ($XQuery) {
$this->runQuery($XQuery);
$XQuery = '';
}
if (!$this->length()) {
break;
}
$this->pseudoClasses($s);
if (!$this->length()) {
break;
}
// DIRECT DESCENDANDS
} elseif ($s == '>') {
$XQuery .= '/';
$delimiterBefore = 2;
// ALL DESCENDANDS
} elseif ($s == ' ') {
$XQuery .= '//';
$delimiterBefore = 2;
// ERRORS
} else {
phpQuery::debug("Unrecognized token '{$s}'");
}
$delimiterBefore = $delimiterBefore === 2;
}
// run query if any
if ($XQuery && $XQuery != '//') {
$this->runQuery($XQuery);
$XQuery = '';
}
foreach ($this->elements as $node) {
if (!$this->elementsContainsNode($node, $stack)) {
$stack[] = $node;
}
}
}
$this->elements = $stack;
return $this->newInstance();
}