mediawiki-extensions-Discus.../includes/TreeWalker.php
Bartosz Dziewoński 781a33357b Use type hints for properties, remove PHPCS overrides
MediaWiki's PHPCS plugin requires documentation comments on all
properties, unless those properties are typed.

This has potential to introduce bugs – in particular, because typed
properties without a default value will throw an exception if their
value is accessed before it's defined, while previously they defaulted
to null. I fixed this when I found it (making them nullable and null
by default), but I may have missed some cases.

Change-Id: If5b1f4d542ce3e1b69327ee4283f7c3e133a62a0
2023-10-19 19:31:02 +00:00

146 lines
3.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace MediaWiki\Extension\DiscussionTools;
use DOMException;
use Throwable;
use Wikimedia\Parsoid\DOM\Node;
/**
* Partial implementation of W3 DOM4 TreeWalker interface.
*
* See also:
* - https://dom.spec.whatwg.org/#interface-treewalker
*
* Ported from https://github.com/TRowbotham/PHPDOM (MIT)
*/
class TreeWalker {
public Node $root;
public int $whatToShow;
public Node $currentNode;
/** @var callable */
public $filter;
private bool $isActive = false;
/**
* See https://dom.spec.whatwg.org/#interface-treewalker
*
* @param Node $root
* @param int $whatToShow
* @param callable|null $filter
*/
public function __construct(
Node $root,
int $whatToShow = NodeFilter::SHOW_ALL,
callable $filter = null
) {
$this->currentNode = $root;
$this->filter = $filter;
$this->root = $root;
$this->whatToShow = $whatToShow;
}
/**
* See https://dom.spec.whatwg.org/#dom-treewalker-nextnode
*
* @return Node|null The current node
*/
public function nextNode(): ?Node {
$node = $this->currentNode;
$result = NodeFilter::FILTER_ACCEPT;
while ( true ) {
while ( $result !== NodeFilter::FILTER_REJECT && $node->firstChild !== null ) {
$node = $node->firstChild;
$result = $this->filterNode( $node );
if ( $result === NodeFilter::FILTER_ACCEPT ) {
$this->currentNode = $node;
return $node;
}
}
$sibling = null;
$temp = $node;
while ( $temp !== null ) {
if ( $temp === $this->root ) {
return null;
}
$sibling = $temp->nextSibling;
if ( $sibling !== null ) {
$node = $sibling;
break;
}
$temp = $temp->parentNode;
}
'@phan-var Node $node';
$result = $this->filterNode( $node );
if ( $result === NodeFilter::FILTER_ACCEPT ) {
$this->currentNode = $node;
return $node;
}
}
}
/**
* Filters a node.
*
* @internal
*
* @see https://dom.spec.whatwg.org/#concept-node-filter
*
* @param Node $node The node to check.
* @return int Returns one of NodeFilter's FILTER_* constants.
* - NodeFilter::FILTER_ACCEPT
* - NodeFilter::FILTER_REJECT
* - NodeFilter::FILTER_SKIP
*/
private function filterNode( Node $node ): int {
if ( $this->isActive ) {
throw new DOMException( 'InvalidStateError' );
}
// Let n be nodes nodeType attribute value minus 1.
$n = $node->nodeType - 1;
// If the nth bit (where 0 is the least significant bit) of whatToShow
// is not set, return FILTER_SKIP.
if ( !( ( 1 << $n ) & $this->whatToShow ) ) {
return NodeFilter::FILTER_SKIP;
}
// If filter is null, return FILTER_ACCEPT.
if ( !$this->filter ) {
return NodeFilter::FILTER_ACCEPT;
}
$this->isActive = true;
try {
// Let $result be the return value of call a user object's operation
// with traverser's filter, "acceptNode", and Node. If this throws
// an exception, then unset traverser's active flag and rethrow the
// exception.
$result = $this->filter instanceof NodeFilter
? $this->filter->acceptNode( $node )
: ( $this->filter )( $node );
} catch ( Throwable $e ) {
$this->isActive = false;
throw $e;
}
$this->isActive = false;
return $result;
}
}