protected function seek(array $nodes, array $rule, array $options)
{
// XPath index
if (count($rule['tag']) > 0 && count($rule['key']) > 0 && is_numeric($rule['key'])) {
$count = 0;
/** @var AbstractNode $node */
foreach ($nodes as $node) {
if ($rule['tag'] == '*' || $rule['tag'] == $node->getTag()->name()) {
++$count;
if ($count == $rule['key']) {
// found the node we wanted
return [$node];
}
}
}
return [];
}
$options = $this->flattenOptions($options);
$return = [];
/** @var InnerNode $node */
foreach ($nodes as $node) {
// check if we are a leaf
if ($node instanceof LeafNode || !$node->hasChildren()) {
continue;
}
$children = [];
$child = $node->firstChild();
while (!is_null($child)) {
// wild card, grab all
if ($rule['tag'] == '*' && is_null($rule['key'])) {
$return[] = $child;
try {
$child = $node->nextChild($child->id());
} catch (ChildNotFoundException $e) {
// no more children
$child = null;
}
continue;
}
$pass = true;
// check tag
if (!empty($rule['tag']) && $rule['tag'] != $child->getTag()->name() && $rule['tag'] != '*') {
// child failed tag check
$pass = false;
}
// check key
if ($pass && !is_null($rule['key'])) {
if ($rule['noKey']) {
if (!is_null($child->getAttribute($rule['key']))) {
$pass = false;
}
} else {
if ($rule['key'] != 'plaintext' && is_null($child->getAttribute($rule['key']))) {
$pass = false;
}
}
}
// compare values
if ($pass && !is_null($rule['key']) && !is_null($rule['value']) && $rule['value'] != '*') {
if ($rule['key'] == 'plaintext') {
// plaintext search
$nodeValue = $child->text();
} else {
// normal search
$nodeValue = $child->getAttribute($rule['key']);
}
$check = $this->match($rule['operator'], $rule['value'], $nodeValue);
// handle multiple classes
if (!$check && $rule['key'] == 'class') {
$childClasses = explode(' ', $child->getAttribute('class'));
foreach ($childClasses as $class) {
if (!empty($class)) {
$check = $this->match($rule['operator'], $rule['value'], $class);
}
if ($check) {
break;
}
}
}
if (!$check) {
$pass = false;
}
}
if ($pass) {
// it passed all checks
$return[] = $child;
} else {
// this child failed to be matched
if ($child instanceof InnerNode && $child->hasChildren()) {
// we still want to check its children
$children[] = $child;
}
}
try {
// get next child
$child = $node->nextChild($child->id());
} catch (ChildNotFoundException $e) {
// no more children
$child = null;
}
}
if ((!isset($options['checkGrandChildren']) || $options['checkGrandChildren']) && count($children) > 0) {
// we have children that failed but are not leaves.
$matches = $this->seek($children, $rule, $options);
foreach ($matches as $match) {
$return[] = $match;
}
}
}
return $return;
}