Skip to content

remove() and replaceAll() still match the nodes in hand, unlike find() #126

Closed

Description

@gravitypdf-bot

Once #73 lands, find() searches descendants only, as jQuery's .find() does. remove($selector) and replaceAll($selector) still go through the legacy engine, QueryPath\CSS\QueryPathEventHandler, which counts the nodes in the match set as candidates for their own selector. So the two engines disagree about which nodes a selector reaches:

$xml = '<?xml version="1.0"?><root><p><i>a</i><b>x</b></p><p><i>b</i></p><i>c</i></root>';

qp($xml, 'p')->remove('p')->count();   // 2: both <p> themselves
qp($xml, 'p')->detach('p')->count();   // 0: no <p> below a <p>

qp($xml, 'p')->remove('*')->count();   // 5: the two <p> plus their three children
qp($xml, 'p')->detach('*')->count();   // 3: the three children

detach($selector) was fixed in #108 (issue #76) to detach "the matches of $selector, as remove($selector) does". With #73 that only holds for selectors that cannot match the nodes in hand.

Cause

Every matcher in QueryPathEventHandler (element(), elementID(), elementClass(), anyElement(), elementNS(), anyElementInNS(), attribute(), …) iterates candidateList(), which includes each node in the match set as well as its descendants. anyElement() even says // Add self.

Options

DOMQueryTest::testDetachWithSelectorAgreesWithRemove currently skips the self-matching selectors. Restore them once this is fixed.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions