diff --git a/filter.go b/filter.go index d72179e..7d2e8f9 100644 --- a/filter.go +++ b/filter.go @@ -88,27 +88,15 @@ func (s *Selection) Has(selector string) *Selection { func (s *Selection) HasMatcher(m Matcher) *Selection { result := make([]*html.Node, 0, len(s.Nodes)) - // If the matcher can probe for the first match in a subtree without - // building a result slice, use that: it avoids a throwaway one-element - // slice allocation for every matching child subtree. - if mf, ok := m.(interface { - MatchFirst(*html.Node) *html.Node - }); ok { - for _, n := range s.Nodes { - for c := n.FirstChild; c != nil; c = c.NextSibling { - if c.Type == html.ElementNode && mf.MatchFirst(c) != nil { - result = append(result, n) - break - } - } - } - return pushStack(s, result) - } - - m = SingleMatcher(m) + // Manually create a singleMatcher (rather than using SingleMatcher, which + // returns a Matcher interface) so we can call its MatchFirst method + // directly. MatchFirst probes for the first match in a subtree without + // building a result slice, avoiding a throwaway one-element slice + // allocation for every matching child subtree. + sm := singleMatcher{m} for _, n := range s.Nodes { for c := n.FirstChild; c != nil; c = c.NextSibling { - if c.Type == html.ElementNode && len(m.MatchAll(c)) > 0 { + if c.Type == html.ElementNode && sm.MatchFirst(c) != nil { result = append(result, n) break } diff --git a/type.go b/type.go index 6646c14..dd3778d 100644 --- a/type.go +++ b/type.go @@ -175,22 +175,31 @@ type singleMatcher struct { Matcher } -func (m singleMatcher) MatchAll(n *html.Node) []*html.Node { +// MatchFirst returns the first node that matches, or nil if none does. It uses +// the underlying Matcher's MatchFirst method if it provides one (cascadia- +// compiled matchers all do), avoiding building a result slice. Otherwise it +// falls back to MatchAll and returns its first element. +func (m singleMatcher) MatchFirst(n *html.Node) *html.Node { // Optimized version - stops finding at the first match (cascadia-compiled // matchers all use this code path). if mm, ok := m.Matcher.(interface{ MatchFirst(*html.Node) *html.Node }); ok { - node := mm.MatchFirst(n) - if node == nil { - return nil - } - return []*html.Node{node} + return mm.MatchFirst(n) } // Fallback version, for e.g. test mocks that don't provide the MatchFirst // method. nodes := m.Matcher.MatchAll(n) if len(nodes) > 0 { - return nodes[:1:1] + return nodes[0] + } + return nil +} + +func (m singleMatcher) MatchAll(n *html.Node) []*html.Node { + // Wrap MatchFirst's single result in a slice to respect the Matcher + // interface's MatchAll signature. + if node := m.MatchFirst(n); node != nil { + return []*html.Node{node} } return nil }