Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 35
MultiId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 5
182
0.00% covered (danger)
0.00%
0 / 35
 __construct
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 4
 add
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 6
 del
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 6
 get_first
0.00% covered (danger)
0.00%
0 / 1
30
0.00% covered (danger)
0.00%
0 / 12
 downgrade
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 7
<?php
declare( strict_types = 1 );
// phpcs:disable Generic.NamingConventions.CamelCapsFunctionName.ScopeNotCamelCaps
namespace Wikimedia\Dodo;
/*
 * DOM-LS specifies that in the
 * event that two Elements have
 * the same 'id' attribute value,
 * the first one, in document order,
 * shall be returned from getElementById.
 *
 * This data structure makes that
 * as performant as possible, by:
 *
 * 1. Caching the first element in the list, in document order
 * It is updated on move because a move is treated as a
 * removal followed by an insertion, and those two operations
 * will update this table.
 *
 * 2. Elements are looked up by an integer index set when they
 * are adopted by Document. This index gives a canonical
 * integer representation of an Element, so we can operate
 * on integers instead of Elements.
 */
class MultiId {
    /** @var Node[] */
    public $table = [];
    /** @var int */
    public $length = 0;
    /**
     * The first element, in document order.
     *
     * null indicates the cache is not set and the first element must be re-computed.
     *
     * @var Node|null
     */
    public $first = null;
    /**
     * @param Node $node
     */
    public function __construct( Node $node ) {
        $this->table[$node->__document_index] = $node;
        $this->length = 1;
        $this->first = null;
    }
    /**
     * Add a Node to array in O(1) time by using Node::$__document_index
     * as the array index.
     *
     * @param Node $node
     */
    public function add( Node $node ) {
        if ( !isset( $this->table[$node->__document_index] ) ) {
            $this->table[$node->__document_index] = $node;
            $this->length++;
            $this->first = null; /* invalidate cache */
        }
    }
    /**
     * Remove a Node from the array in O(1) time by using Node::$__document_index
     * to perform the lookup.
     *
     * @param Node $node
     */
    public function del( Node $node ) {
        if ( $this->table[$node->__document_index] ) {
            unset( $this->table[$node->__document_index] );
            $this->length--;
            $this->first = null; /* invalidate cache */
        }
    }
    /**
     * Retreive that Node from the array which appears first in document order in
     * the associated document.
     *
     * Cache the value for repeated lookups.
     *
     * The cache is invalidated each time the array is modified. The list
     * is modified when a Node is inserted or removed from a Document, or when
     * the 'id' attribute value of a Node is changed.
     *
     * @return Node|null null if there are no nodes
     */
    public function get_first() {
        if ( $this->first !== null ) {
            return $this->first;
        }
        // No item has been cached. Well, let's find it then.
        foreach ( $this->table as $document_index => $node ) {
            if ( $this->first === null ||
                $this->first->compareDocumentPosition( $node ) & Node::DOCUMENT_POSITION_PRECEDING
            ) {
                $this->first = $node;
            }
        }
        return $this->first;
    }
    /**
     * If there is only one node left, return it. Otherwise return "this".
     *
     * @return Node|MultiId
     */
    public function downgrade() {
        if ( $this->length === 1 ) {
            foreach ( $this->table as $document_index => $node ) {
                return $node;
            }
        }
        return $this;
    }
}