Repository navigation
Skip non-element matches in the attribute and class methods - #105
Merged
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #105 +/- ##
============================================
+ Coverage 90.55% 90.69% +0.13%
+ Complexity 1425 1424 -1
============================================
Files 25 25
Lines 3166 3170 +4
============================================
+ Hits 2867 2875 +8
+ Misses 299 295 -4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
gravitypdf-bot
force-pushed
the
fix/issue-98-non-element-attrs
branch
from
October 25, 2026 23:50
54143b1 to
1b026ed
Compare
attr(), addClass(), removeClass(), css(), hasClass(), hasAttr() and
removeAttr() called element-only DOM methods on every match, so a text,
comment, CDATA or processing instruction node in the set (from contents(),
for example) was a fatal Error.
Add DOM::elements(), which returns the element matches in order, and route
those methods through it. As in jQuery, setters skip non-elements and still
return the DOMQuery, and the predicates consider only the elements. The
attr() getters keep reading the first match and return NULL when it is not
an element, as they do for an empty set; attr('nodeType') still reports the
first match's type, whatever it is.
tag() is left for #97.
Fixes #98
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gravitypdf-bot
force-pushed
the
fix/issue-98-non-element-attrs
branch
from
October 26, 2026 00:10
1b026ed to
6ae6acf
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #98
Problem
attr(),addClass(),removeClass(),css(),hasClass(),hasAttr()andremoveAttr()called element-only DOM methods on every match. When a match set contained a text, comment, CDATA or processing-instruction node, ascontents()returns, they died with errors such asCall to undefined method DOMText::hasAttribute().Fix
Following commit 745a4e2, this is fixed once in
DOMrather than with a guard in each method. Two new protected helpers:elements()yields only the element matches, in document order. It's a generator, so it builds no array, andhasClass()/hasAttr()still stop at the first decisive element.firstElement()returns the first match if it's an element, andnullotherwise, including for an empty set.Every method that needs an element now goes through one of them:
attr(),addClass(),removeClass(),css()elements()/firstElement()hasClass(),hasAttr(),removeAttr()elements()tag(), which #101 fixed with its own inline checkfirstElement()first(),last(), which had their ownnodeTypecheckelements()Behaviour (as in jQuery)
attr($name, $value),attr(array),addClass(),removeClass(), thecss()setter,removeAttr()) skip non-element nodes and still return the query object.attr($name),attr(), andcss()throughattr('style')) read the first node in the set. If it isn't an element they returnnull, the same as for an empty set. Previously,attr()with no arguments warned and returned[]in that case.attr('nodeType')still reports the first node's type even when it isn't an element. It never crashed, and it's a legitimate question to ask of a text node.hasClass()is true if any element has the class.hasAttr()keeps its documented "all matches have the attribute" meaning, now counted over elements only. Changing it to "any" would be a separate behaviour change.dataURL()andval()go throughattr(), so they're fixed too.tag(),first()andlast()behave exactly as before.Tests
nonElementMatchSetProvider()builds five match sets: a text, comment, CDATA or PI node before an<a>element, and the element followed by all four kinds. A sharedcontentsWithElement()helper returns each set'scontents()and its<a>element. Six tests run over the provider:attr(),addClass(),hasAttr()and other attribute/class methods fatal on non-element nodes in the match set #98, one each forattr(),css(), the class methods, andhasAttr()/removeAttr(). They check each getter's return value, that each setter returns the query object, and that the element's attributes really changed. All 20 cases error onmain.testFirstAndLastWithNonElementNodes(new): both methods return the<a>element. It passes onmaintoo, since their behaviour hasn't changed; it pins the refactor.testTagFirstMatchOfContents(from Return '' from tag() when the first match is not an element #101): now runs over the shared provider in place of its own smaller one, which gains a CDATA case.Rebased on
main. The full suite (441 tests) passes locally, and CI passes on PHP 7.1–8.5 along withcomposer run lintandcomposer run lint:min-php.🤖 Generated with Claude Code