Skip to content

Commit

Permalink
Performance optimizations.
Browse files Browse the repository at this point in the history
  • Loading branch information
ivopetkov committed Dec 8, 2017
1 parent 3f831e5 commit f38f27c
Showing 1 changed file with 40 additions and 27 deletions.
67 changes: 40 additions & 27 deletions src/HTML5DOMDocument/Internal/QuerySelectors.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ trait QuerySelectors

/**
* Returns the first element matching the selector
*
*
* @param string $selector CSS query selector
* @return \DOMElement|null The result DOMElement or null if not found
*/
Expand All @@ -19,33 +19,45 @@ private function internalQuerySelector(string $selector)

/**
* Returns a list of document elements matching the selector
*
*
* @param string $selector CSS query selector
* @param int|null $preferredLimit Preferred maximum number of elements to return
* @return \DOMNodeList Returns a list of DOMElements matching the criteria
* @return DOMNodeList Returns a list of DOMElements matching the criteria
* @throws \InvalidArgumentException
*/
private function internalQuerySelectorAll(string $selector, $preferredLimit = null)
{
$walkChildren = function($element, $callback) use (&$walkChildren) { // $walkChildren is a lot faster than $this->getElementsByTagName('*') for 300+ elements
foreach ($element->childNodes as $child) {
if ($child instanceof \DOMElement) {
$walkChildren = function($element, $tagName, $callback) use (&$walkChildren) { // $walkChildren is a lot faster than $this->getElementsByTagName('*') for 300+ elements
if ($tagName !== null) {
$children = $element->getElementsByTagName($tagName);
foreach ($children as $child) {
if ($callback($child) === true) {
return true;
}
if ($walkChildren($child, $callback) === true) {
return true;
}
} else {
foreach ($element->childNodes as $child) {
if ($child instanceof \DOMElement) {
if ($callback($child) === true) {
return true;
}
if ($walkChildren($child, $tagName, $callback) === true) {
return true;
}
}
}
}
};

$getElementById = function($id) use (&$walkChildren) {
$getElementById = function($id, $tagName) use (&$walkChildren) {
if ($this instanceof \DOMDocument) {
return $this->getElementById($id);
$element = $this->getElementById($id);
if ($element && ($tagName === null || $element->tagName === $tagName)) {
return $element;
}
} else {
$foundElement = null;
$walkChildren($this, function($element) use ($id, &$foundElement) {
$walkChildren($this, $tagName, function($element) use ($id, &$foundElement) {
if ($element->attributes->length > 0 && $element->getAttribute('id') === $id) {
$foundElement = $element;
return true;
Expand All @@ -59,47 +71,48 @@ private function internalQuerySelectorAll(string $selector, $preferredLimit = nu
$matches = null;
if ($selector === '*') { // all
$result = [];
$walkChildren($this, function($element) use (&$result) {
$walkChildren($this, null, function($element) use (&$result, $preferredLimit) {
$result[] = $element;
if ($preferredLimit !== null && sizeof($result) >= $preferredLimit) {
return true;
}
});
return new \IvoPetkov\HTML5DOMNodeList($result);
} elseif (preg_match('/^[a-z0-9]+$/', $selector) === 1) { // tagname
$result = [];
$walkChildren($this, function($element) use (&$result, $selector) {
if ($element->tagName === $selector) {
$result[] = $element;
$walkChildren($this, $selector, function($element) use (&$result, $preferredLimit) {
$result[] = $element;
if ($preferredLimit !== null && sizeof($result) >= $preferredLimit) {
return true;
}
});
return new \IvoPetkov\HTML5DOMNodeList($result);
} elseif (preg_match('/^([a-z0-9]*)\[(.+)\=\"(.+)\"\]$/', $selector, $matches) === 1) { // tagname[attribute="value"] or [attribute="value"]
$result = [];
$tagName = strlen($matches[1]) > 0 ? $matches[1] : null;
$walkChildren($this, function($element) use (&$result, $tagName, $preferredLimit, $matches) {
if ($tagName === null || $element->tagName === $tagName) {
if ($element->attributes->length > 0 && $element->getAttribute($matches[2]) === $matches[3]) {
$result[] = $element;
if ($preferredLimit !== null && sizeof($result) >= $preferredLimit) {
return true;
}
$walkChildren($this, $tagName, function($element) use (&$result, $preferredLimit, $matches) {
if ($element->attributes->length > 0 && $element->getAttribute($matches[2]) === $matches[3]) {
$result[] = $element;
if ($preferredLimit !== null && sizeof($result) >= $preferredLimit) {
return true;
}
}
});
return new \IvoPetkov\HTML5DOMNodeList($result);
} elseif (preg_match('/^([a-z0-9]*)#(.+)$/', $selector, $matches) === 1) { // tagname#id or #id
$tagName = strlen($matches[1]) > 0 ? $matches[1] : null;
$idSelector = $matches[2];
$element = $getElementById($idSelector);
if ($element && ($tagName === null || $element->tagName === $tagName)) {
$element = $getElementById($idSelector, $tagName);
if ($element) {
return new \IvoPetkov\HTML5DOMNodeList([$element]);
}
return new \IvoPetkov\HTML5DOMNodeList();
} elseif (preg_match('/^([a-z0-9]*)\.(.+)$/', $selector, $matches) === 1) { // tagname.classname or .classname
$parts = explode('.', $selector, 2);
$tagName = strlen($matches[1]) > 0 ? $matches[1] : null;
$classSelector = $matches[2];
$result = [];
$walkChildren($this, function($element) use (&$result, $tagName, $classSelector, $preferredLimit) {
if (($tagName === null || $element->tagName === $tagName) && $element->attributes->length > 0) {
$walkChildren($this, $tagName, function($element) use (&$result, $classSelector, $preferredLimit) {
if ($element->attributes->length > 0) {
$classAttribute = $element->getAttribute('class');
if ($classAttribute === $classSelector || strpos($classAttribute, $classSelector . ' ') === 0 || substr($classAttribute, -(strlen($classSelector) + 1)) === ' ' . $classSelector || strpos($classAttribute, ' ' . $classSelector . ' ') !== false) {
$result[] = $element;
Expand Down

0 comments on commit f38f27c

Please sign in to comment.