mirror of
https://github.com/PuerkitoBio/goquery.git
synced 2026-10-19 16:15:33 +00:00
For the sibling types that collect every match (siblingAll,
siblingAllIncludingNonElements, siblingPrevAll, siblingNextAll), do a
cheap first-pass pointer walk to count the matches, then make the result
slice with that exact capacity. This removes the repeated append-driven
slice growth that dominated allocations on these hot traversal paths.
The Until cases are left untouched because counting would require running
the user predicate twice, and the single-result Next/Prev cases have
nothing to presize.
This drives Children/Contents/Siblings/Next*/Prev* through fewer
allocations:
allocs/op vs base B/op vs base
Siblings -57.23% -9.23%
SiblingsFiltered -54.49% -8.94%
NextAll -59.82% -10.04%
NextAllFiltered -55.83% -9.65%
PrevAll -55.32% -10.66%
PrevAllFiltered -52.53% -10.40%
ChildrenFiltered -15.38% -6.45%
Contents -7.69% -1.73%
geomean -25.07% -3.88%
The Until and single Next/Prev benchmarks are unchanged (bit-identical
allocs/op), confirming the count pre-pass only touches the collect-all
paths.
The quite verbose comment on top of the change is there so that future
generations won't waste time wondering why this weird loop is here.
721 lines
28 KiB
Go
721 lines
28 KiB
Go
package goquery
|
|
|
|
import "golang.org/x/net/html"
|
|
|
|
type siblingType int
|
|
|
|
// Sibling type, used internally when iterating over children at the same
|
|
// level (siblings) to specify which nodes are requested.
|
|
const (
|
|
siblingPrevUntil siblingType = iota - 3
|
|
siblingPrevAll
|
|
siblingPrev
|
|
siblingAll
|
|
siblingNext
|
|
siblingNextAll
|
|
siblingNextUntil
|
|
siblingAllIncludingNonElements
|
|
)
|
|
|
|
// Find gets the descendants of each element in the current set of matched
|
|
// elements, filtered by a selector. It returns a new Selection object
|
|
// containing these matched elements.
|
|
//
|
|
// Note that as for all methods accepting a selector string, the selector is
|
|
// compiled and applied by the cascadia package and inherits its behavior and
|
|
// constraints regarding supported selectors. See the note on cascadia in
|
|
// the goquery documentation here:
|
|
// https://github.com/PuerkitoBio/goquery?tab=readme-ov-file#api
|
|
func (s *Selection) Find(selector string) *Selection {
|
|
return pushStack(s, findWithMatcher(s.Nodes, compileMatcher(selector)))
|
|
}
|
|
|
|
// FindMatcher gets the descendants of each element in the current set of matched
|
|
// elements, filtered by the matcher. It returns a new Selection object
|
|
// containing these matched elements.
|
|
func (s *Selection) FindMatcher(m Matcher) *Selection {
|
|
return pushStack(s, findWithMatcher(s.Nodes, m))
|
|
}
|
|
|
|
// FindSelection gets the descendants of each element in the current
|
|
// Selection, filtered by a Selection. It returns a new Selection object
|
|
// containing these matched elements.
|
|
func (s *Selection) FindSelection(sel *Selection) *Selection {
|
|
if sel == nil {
|
|
return pushStack(s, nil)
|
|
}
|
|
return s.FindNodes(sel.Nodes...)
|
|
}
|
|
|
|
// FindNodes gets the descendants of each element in the current
|
|
// Selection, filtered by some nodes. It returns a new Selection object
|
|
// containing these matched elements.
|
|
func (s *Selection) FindNodes(nodes ...*html.Node) *Selection {
|
|
return pushStack(s, mapNodes(nodes, func(i int, n *html.Node) []*html.Node {
|
|
if sliceContains(s.Nodes, n) {
|
|
return []*html.Node{n}
|
|
}
|
|
return nil
|
|
}))
|
|
}
|
|
|
|
// Contents gets the children of each element in the Selection,
|
|
// including text and comment nodes. It returns a new Selection object
|
|
// containing these elements.
|
|
func (s *Selection) Contents() *Selection {
|
|
return pushStack(s, getChildrenNodes(s.Nodes, siblingAllIncludingNonElements))
|
|
}
|
|
|
|
// ContentsFiltered gets the children of each element in the Selection,
|
|
// filtered by the specified selector. It returns a new Selection
|
|
// object containing these elements. Since selectors only act on Element nodes,
|
|
// this function is an alias to ChildrenFiltered unless the selector is empty,
|
|
// in which case it is an alias to Contents.
|
|
func (s *Selection) ContentsFiltered(selector string) *Selection {
|
|
if selector != "" {
|
|
return s.ChildrenFiltered(selector)
|
|
}
|
|
return s.Contents()
|
|
}
|
|
|
|
// ContentsMatcher gets the children of each element in the Selection,
|
|
// filtered by the specified matcher. It returns a new Selection
|
|
// object containing these elements. Since matchers only act on Element nodes,
|
|
// this function is an alias to ChildrenMatcher.
|
|
func (s *Selection) ContentsMatcher(m Matcher) *Selection {
|
|
return s.ChildrenMatcher(m)
|
|
}
|
|
|
|
// Children gets the child elements of each element in the Selection.
|
|
// It returns a new Selection object containing these elements.
|
|
func (s *Selection) Children() *Selection {
|
|
return pushStack(s, getChildrenNodes(s.Nodes, siblingAll))
|
|
}
|
|
|
|
// ChildrenFiltered gets the child elements of each element in the Selection,
|
|
// filtered by the specified selector. It returns a new
|
|
// Selection object containing these elements.
|
|
func (s *Selection) ChildrenFiltered(selector string) *Selection {
|
|
return filterAndPush(s, getChildrenNodes(s.Nodes, siblingAll), compileMatcher(selector))
|
|
}
|
|
|
|
// ChildrenMatcher gets the child elements of each element in the Selection,
|
|
// filtered by the specified matcher. It returns a new
|
|
// Selection object containing these elements.
|
|
func (s *Selection) ChildrenMatcher(m Matcher) *Selection {
|
|
return filterAndPush(s, getChildrenNodes(s.Nodes, siblingAll), m)
|
|
}
|
|
|
|
// Parent gets the parent of each element in the Selection. It returns a
|
|
// new Selection object containing the matched elements.
|
|
func (s *Selection) Parent() *Selection {
|
|
return pushStack(s, getParentNodes(s.Nodes))
|
|
}
|
|
|
|
// ParentFiltered gets the parent of each element in the Selection filtered by a
|
|
// selector. It returns a new Selection object containing the matched elements.
|
|
func (s *Selection) ParentFiltered(selector string) *Selection {
|
|
return filterAndPush(s, getParentNodes(s.Nodes), compileMatcher(selector))
|
|
}
|
|
|
|
// ParentMatcher gets the parent of each element in the Selection filtered by a
|
|
// matcher. It returns a new Selection object containing the matched elements.
|
|
func (s *Selection) ParentMatcher(m Matcher) *Selection {
|
|
return filterAndPush(s, getParentNodes(s.Nodes), m)
|
|
}
|
|
|
|
// Closest gets the first element that matches the selector by testing the
|
|
// element itself and traversing up through its ancestors in the DOM tree.
|
|
func (s *Selection) Closest(selector string) *Selection {
|
|
cs := compileMatcher(selector)
|
|
return s.ClosestMatcher(cs)
|
|
}
|
|
|
|
// ClosestMatcher gets the first element that matches the matcher by testing the
|
|
// element itself and traversing up through its ancestors in the DOM tree.
|
|
func (s *Selection) ClosestMatcher(m Matcher) *Selection {
|
|
return pushStack(s, mapNodes(s.Nodes, func(i int, n *html.Node) []*html.Node {
|
|
// For each node in the selection, test the node itself, then each parent
|
|
// until a match is found.
|
|
for ; n != nil; n = n.Parent {
|
|
if m.Match(n) {
|
|
return []*html.Node{n}
|
|
}
|
|
}
|
|
return nil
|
|
}))
|
|
}
|
|
|
|
// ClosestNodes gets the first element that matches one of the nodes by testing the
|
|
// element itself and traversing up through its ancestors in the DOM tree.
|
|
func (s *Selection) ClosestNodes(nodes ...*html.Node) *Selection {
|
|
set := make(map[*html.Node]bool, len(nodes))
|
|
for _, n := range nodes {
|
|
set[n] = true
|
|
}
|
|
return pushStack(s, mapNodes(s.Nodes, func(i int, n *html.Node) []*html.Node {
|
|
// For each node in the selection, test the node itself, then each parent
|
|
// until a match is found.
|
|
for ; n != nil; n = n.Parent {
|
|
if set[n] {
|
|
return []*html.Node{n}
|
|
}
|
|
}
|
|
return nil
|
|
}))
|
|
}
|
|
|
|
// ClosestSelection gets the first element that matches one of the nodes in the
|
|
// Selection by testing the element itself and traversing up through its ancestors
|
|
// in the DOM tree.
|
|
func (s *Selection) ClosestSelection(sel *Selection) *Selection {
|
|
if sel == nil {
|
|
return pushStack(s, nil)
|
|
}
|
|
return s.ClosestNodes(sel.Nodes...)
|
|
}
|
|
|
|
// Parents gets the ancestors of each element in the current Selection. It
|
|
// returns a new Selection object with the matched elements.
|
|
func (s *Selection) Parents() *Selection {
|
|
return pushStack(s, getParentsNodes(s.Nodes, nil, nil))
|
|
}
|
|
|
|
// ParentsFiltered gets the ancestors of each element in the current
|
|
// Selection. It returns a new Selection object with the matched elements.
|
|
func (s *Selection) ParentsFiltered(selector string) *Selection {
|
|
return filterAndPush(s, getParentsNodes(s.Nodes, nil, nil), compileMatcher(selector))
|
|
}
|
|
|
|
// ParentsMatcher gets the ancestors of each element in the current
|
|
// Selection. It returns a new Selection object with the matched elements.
|
|
func (s *Selection) ParentsMatcher(m Matcher) *Selection {
|
|
return filterAndPush(s, getParentsNodes(s.Nodes, nil, nil), m)
|
|
}
|
|
|
|
// ParentsUntil gets the ancestors of each element in the Selection, up to but
|
|
// not including the element matched by the selector. It returns a new Selection
|
|
// object containing the matched elements.
|
|
func (s *Selection) ParentsUntil(selector string) *Selection {
|
|
return pushStack(s, getParentsNodes(s.Nodes, compileMatcher(selector), nil))
|
|
}
|
|
|
|
// ParentsUntilMatcher gets the ancestors of each element in the Selection, up to but
|
|
// not including the element matched by the matcher. It returns a new Selection
|
|
// object containing the matched elements.
|
|
func (s *Selection) ParentsUntilMatcher(m Matcher) *Selection {
|
|
return pushStack(s, getParentsNodes(s.Nodes, m, nil))
|
|
}
|
|
|
|
// ParentsUntilSelection gets the ancestors of each element in the Selection,
|
|
// up to but not including the elements in the specified Selection. It returns a
|
|
// new Selection object containing the matched elements.
|
|
func (s *Selection) ParentsUntilSelection(sel *Selection) *Selection {
|
|
if sel == nil {
|
|
return s.Parents()
|
|
}
|
|
return s.ParentsUntilNodes(sel.Nodes...)
|
|
}
|
|
|
|
// ParentsUntilNodes gets the ancestors of each element in the Selection,
|
|
// up to but not including the specified nodes. It returns a
|
|
// new Selection object containing the matched elements.
|
|
func (s *Selection) ParentsUntilNodes(nodes ...*html.Node) *Selection {
|
|
return pushStack(s, getParentsNodes(s.Nodes, nil, nodes))
|
|
}
|
|
|
|
// ParentsFilteredUntil is like ParentsUntil, with the option to filter the
|
|
// results based on a selector string. It returns a new Selection
|
|
// object containing the matched elements.
|
|
func (s *Selection) ParentsFilteredUntil(filterSelector, untilSelector string) *Selection {
|
|
return filterAndPush(s, getParentsNodes(s.Nodes, compileMatcher(untilSelector), nil), compileMatcher(filterSelector))
|
|
}
|
|
|
|
// ParentsFilteredUntilMatcher is like ParentsUntilMatcher, with the option to filter the
|
|
// results based on a matcher. It returns a new Selection object containing the matched elements.
|
|
func (s *Selection) ParentsFilteredUntilMatcher(filter, until Matcher) *Selection {
|
|
return filterAndPush(s, getParentsNodes(s.Nodes, until, nil), filter)
|
|
}
|
|
|
|
// ParentsFilteredUntilSelection is like ParentsUntilSelection, with the
|
|
// option to filter the results based on a selector string. It returns a new
|
|
// Selection object containing the matched elements.
|
|
func (s *Selection) ParentsFilteredUntilSelection(filterSelector string, sel *Selection) *Selection {
|
|
return s.ParentsMatcherUntilSelection(compileMatcher(filterSelector), sel)
|
|
}
|
|
|
|
// ParentsMatcherUntilSelection is like ParentsUntilSelection, with the
|
|
// option to filter the results based on a matcher. It returns a new
|
|
// Selection object containing the matched elements.
|
|
func (s *Selection) ParentsMatcherUntilSelection(filter Matcher, sel *Selection) *Selection {
|
|
if sel == nil {
|
|
return s.ParentsMatcher(filter)
|
|
}
|
|
return s.ParentsMatcherUntilNodes(filter, sel.Nodes...)
|
|
}
|
|
|
|
// ParentsFilteredUntilNodes is like ParentsUntilNodes, with the
|
|
// option to filter the results based on a selector string. It returns a new
|
|
// Selection object containing the matched elements.
|
|
func (s *Selection) ParentsFilteredUntilNodes(filterSelector string, nodes ...*html.Node) *Selection {
|
|
return filterAndPush(s, getParentsNodes(s.Nodes, nil, nodes), compileMatcher(filterSelector))
|
|
}
|
|
|
|
// ParentsMatcherUntilNodes is like ParentsUntilNodes, with the
|
|
// option to filter the results based on a matcher. It returns a new
|
|
// Selection object containing the matched elements.
|
|
func (s *Selection) ParentsMatcherUntilNodes(filter Matcher, nodes ...*html.Node) *Selection {
|
|
return filterAndPush(s, getParentsNodes(s.Nodes, nil, nodes), filter)
|
|
}
|
|
|
|
// Siblings gets the siblings of each element in the Selection. It returns
|
|
// a new Selection object containing the matched elements.
|
|
func (s *Selection) Siblings() *Selection {
|
|
return pushStack(s, getSiblingNodes(s.Nodes, siblingAll, nil, nil))
|
|
}
|
|
|
|
// SiblingsFiltered gets the siblings of each element in the Selection
|
|
// filtered by a selector. It returns a new Selection object containing the
|
|
// matched elements.
|
|
func (s *Selection) SiblingsFiltered(selector string) *Selection {
|
|
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingAll, nil, nil), compileMatcher(selector))
|
|
}
|
|
|
|
// SiblingsMatcher gets the siblings of each element in the Selection
|
|
// filtered by a matcher. It returns a new Selection object containing the
|
|
// matched elements.
|
|
func (s *Selection) SiblingsMatcher(m Matcher) *Selection {
|
|
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingAll, nil, nil), m)
|
|
}
|
|
|
|
// Next gets the immediately following sibling of each element in the
|
|
// Selection. It returns a new Selection object containing the matched elements.
|
|
func (s *Selection) Next() *Selection {
|
|
return pushStack(s, getSiblingNodes(s.Nodes, siblingNext, nil, nil))
|
|
}
|
|
|
|
// NextFiltered gets the immediately following sibling of each element in the
|
|
// Selection filtered by a selector. It returns a new Selection object
|
|
// containing the matched elements.
|
|
func (s *Selection) NextFiltered(selector string) *Selection {
|
|
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNext, nil, nil), compileMatcher(selector))
|
|
}
|
|
|
|
// NextMatcher gets the immediately following sibling of each element in the
|
|
// Selection filtered by a matcher. It returns a new Selection object
|
|
// containing the matched elements.
|
|
func (s *Selection) NextMatcher(m Matcher) *Selection {
|
|
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNext, nil, nil), m)
|
|
}
|
|
|
|
// NextAll gets all the following siblings of each element in the
|
|
// Selection. It returns a new Selection object containing the matched elements.
|
|
func (s *Selection) NextAll() *Selection {
|
|
return pushStack(s, getSiblingNodes(s.Nodes, siblingNextAll, nil, nil))
|
|
}
|
|
|
|
// NextAllFiltered gets all the following siblings of each element in the
|
|
// Selection filtered by a selector. It returns a new Selection object
|
|
// containing the matched elements.
|
|
func (s *Selection) NextAllFiltered(selector string) *Selection {
|
|
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextAll, nil, nil), compileMatcher(selector))
|
|
}
|
|
|
|
// NextAllMatcher gets all the following siblings of each element in the
|
|
// Selection filtered by a matcher. It returns a new Selection object
|
|
// containing the matched elements.
|
|
func (s *Selection) NextAllMatcher(m Matcher) *Selection {
|
|
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextAll, nil, nil), m)
|
|
}
|
|
|
|
// Prev gets the immediately preceding sibling of each element in the
|
|
// Selection. It returns a new Selection object containing the matched elements.
|
|
func (s *Selection) Prev() *Selection {
|
|
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrev, nil, nil))
|
|
}
|
|
|
|
// PrevFiltered gets the immediately preceding sibling of each element in the
|
|
// Selection filtered by a selector. It returns a new Selection object
|
|
// containing the matched elements.
|
|
func (s *Selection) PrevFiltered(selector string) *Selection {
|
|
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrev, nil, nil), compileMatcher(selector))
|
|
}
|
|
|
|
// PrevMatcher gets the immediately preceding sibling of each element in the
|
|
// Selection filtered by a matcher. It returns a new Selection object
|
|
// containing the matched elements.
|
|
func (s *Selection) PrevMatcher(m Matcher) *Selection {
|
|
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrev, nil, nil), m)
|
|
}
|
|
|
|
// PrevAll gets all the preceding siblings of each element in the
|
|
// Selection. It returns a new Selection object containing the matched elements.
|
|
func (s *Selection) PrevAll() *Selection {
|
|
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevAll, nil, nil))
|
|
}
|
|
|
|
// PrevAllFiltered gets all the preceding siblings of each element in the
|
|
// Selection filtered by a selector. It returns a new Selection object
|
|
// containing the matched elements.
|
|
func (s *Selection) PrevAllFiltered(selector string) *Selection {
|
|
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevAll, nil, nil), compileMatcher(selector))
|
|
}
|
|
|
|
// PrevAllMatcher gets all the preceding siblings of each element in the
|
|
// Selection filtered by a matcher. It returns a new Selection object
|
|
// containing the matched elements.
|
|
func (s *Selection) PrevAllMatcher(m Matcher) *Selection {
|
|
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevAll, nil, nil), m)
|
|
}
|
|
|
|
// NextUntil gets all following siblings of each element up to but not
|
|
// including the element matched by the selector. It returns a new Selection
|
|
// object containing the matched elements.
|
|
func (s *Selection) NextUntil(selector string) *Selection {
|
|
return pushStack(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
|
compileMatcher(selector), nil))
|
|
}
|
|
|
|
// NextUntilMatcher gets all following siblings of each element up to but not
|
|
// including the element matched by the matcher. It returns a new Selection
|
|
// object containing the matched elements.
|
|
func (s *Selection) NextUntilMatcher(m Matcher) *Selection {
|
|
return pushStack(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
|
m, nil))
|
|
}
|
|
|
|
// NextUntilSelection gets all following siblings of each element up to but not
|
|
// including the element matched by the Selection. It returns a new Selection
|
|
// object containing the matched elements.
|
|
func (s *Selection) NextUntilSelection(sel *Selection) *Selection {
|
|
if sel == nil {
|
|
return s.NextAll()
|
|
}
|
|
return s.NextUntilNodes(sel.Nodes...)
|
|
}
|
|
|
|
// NextUntilNodes gets all following siblings of each element up to but not
|
|
// including the element matched by the nodes. It returns a new Selection
|
|
// object containing the matched elements.
|
|
func (s *Selection) NextUntilNodes(nodes ...*html.Node) *Selection {
|
|
return pushStack(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
|
nil, nodes))
|
|
}
|
|
|
|
// PrevUntil gets all preceding siblings of each element up to but not
|
|
// including the element matched by the selector. It returns a new Selection
|
|
// object containing the matched elements.
|
|
func (s *Selection) PrevUntil(selector string) *Selection {
|
|
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
|
compileMatcher(selector), nil))
|
|
}
|
|
|
|
// PrevUntilMatcher gets all preceding siblings of each element up to but not
|
|
// including the element matched by the matcher. It returns a new Selection
|
|
// object containing the matched elements.
|
|
func (s *Selection) PrevUntilMatcher(m Matcher) *Selection {
|
|
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
|
m, nil))
|
|
}
|
|
|
|
// PrevUntilSelection gets all preceding siblings of each element up to but not
|
|
// including the element matched by the Selection. It returns a new Selection
|
|
// object containing the matched elements.
|
|
func (s *Selection) PrevUntilSelection(sel *Selection) *Selection {
|
|
if sel == nil {
|
|
return s.PrevAll()
|
|
}
|
|
return s.PrevUntilNodes(sel.Nodes...)
|
|
}
|
|
|
|
// PrevUntilNodes gets all preceding siblings of each element up to but not
|
|
// including the element matched by the nodes. It returns a new Selection
|
|
// object containing the matched elements.
|
|
func (s *Selection) PrevUntilNodes(nodes ...*html.Node) *Selection {
|
|
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
|
nil, nodes))
|
|
}
|
|
|
|
// NextFilteredUntil is like NextUntil, with the option to filter
|
|
// the results based on a selector string.
|
|
// It returns a new Selection object containing the matched elements.
|
|
func (s *Selection) NextFilteredUntil(filterSelector, untilSelector string) *Selection {
|
|
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
|
compileMatcher(untilSelector), nil), compileMatcher(filterSelector))
|
|
}
|
|
|
|
// NextFilteredUntilMatcher is like NextUntilMatcher, with the option to filter
|
|
// the results based on a matcher.
|
|
// It returns a new Selection object containing the matched elements.
|
|
func (s *Selection) NextFilteredUntilMatcher(filter, until Matcher) *Selection {
|
|
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
|
until, nil), filter)
|
|
}
|
|
|
|
// NextFilteredUntilSelection is like NextUntilSelection, with the
|
|
// option to filter the results based on a selector string. It returns a new
|
|
// Selection object containing the matched elements.
|
|
func (s *Selection) NextFilteredUntilSelection(filterSelector string, sel *Selection) *Selection {
|
|
return s.NextMatcherUntilSelection(compileMatcher(filterSelector), sel)
|
|
}
|
|
|
|
// NextMatcherUntilSelection is like NextUntilSelection, with the
|
|
// option to filter the results based on a matcher. It returns a new
|
|
// Selection object containing the matched elements.
|
|
func (s *Selection) NextMatcherUntilSelection(filter Matcher, sel *Selection) *Selection {
|
|
if sel == nil {
|
|
return s.NextMatcher(filter)
|
|
}
|
|
return s.NextMatcherUntilNodes(filter, sel.Nodes...)
|
|
}
|
|
|
|
// NextFilteredUntilNodes is like NextUntilNodes, with the
|
|
// option to filter the results based on a selector string. It returns a new
|
|
// Selection object containing the matched elements.
|
|
func (s *Selection) NextFilteredUntilNodes(filterSelector string, nodes ...*html.Node) *Selection {
|
|
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
|
nil, nodes), compileMatcher(filterSelector))
|
|
}
|
|
|
|
// NextMatcherUntilNodes is like NextUntilNodes, with the
|
|
// option to filter the results based on a matcher. It returns a new
|
|
// Selection object containing the matched elements.
|
|
func (s *Selection) NextMatcherUntilNodes(filter Matcher, nodes ...*html.Node) *Selection {
|
|
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
|
nil, nodes), filter)
|
|
}
|
|
|
|
// PrevFilteredUntil is like PrevUntil, with the option to filter
|
|
// the results based on a selector string.
|
|
// It returns a new Selection object containing the matched elements.
|
|
func (s *Selection) PrevFilteredUntil(filterSelector, untilSelector string) *Selection {
|
|
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
|
compileMatcher(untilSelector), nil), compileMatcher(filterSelector))
|
|
}
|
|
|
|
// PrevFilteredUntilMatcher is like PrevUntilMatcher, with the option to filter
|
|
// the results based on a matcher.
|
|
// It returns a new Selection object containing the matched elements.
|
|
func (s *Selection) PrevFilteredUntilMatcher(filter, until Matcher) *Selection {
|
|
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
|
until, nil), filter)
|
|
}
|
|
|
|
// PrevFilteredUntilSelection is like PrevUntilSelection, with the
|
|
// option to filter the results based on a selector string. It returns a new
|
|
// Selection object containing the matched elements.
|
|
func (s *Selection) PrevFilteredUntilSelection(filterSelector string, sel *Selection) *Selection {
|
|
return s.PrevMatcherUntilSelection(compileMatcher(filterSelector), sel)
|
|
}
|
|
|
|
// PrevMatcherUntilSelection is like PrevUntilSelection, with the
|
|
// option to filter the results based on a matcher. It returns a new
|
|
// Selection object containing the matched elements.
|
|
func (s *Selection) PrevMatcherUntilSelection(filter Matcher, sel *Selection) *Selection {
|
|
if sel == nil {
|
|
return s.PrevMatcher(filter)
|
|
}
|
|
return s.PrevMatcherUntilNodes(filter, sel.Nodes...)
|
|
}
|
|
|
|
// PrevFilteredUntilNodes is like PrevUntilNodes, with the
|
|
// option to filter the results based on a selector string. It returns a new
|
|
// Selection object containing the matched elements.
|
|
func (s *Selection) PrevFilteredUntilNodes(filterSelector string, nodes ...*html.Node) *Selection {
|
|
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
|
nil, nodes), compileMatcher(filterSelector))
|
|
}
|
|
|
|
// PrevMatcherUntilNodes is like PrevUntilNodes, with the
|
|
// option to filter the results based on a matcher. It returns a new
|
|
// Selection object containing the matched elements.
|
|
func (s *Selection) PrevMatcherUntilNodes(filter Matcher, nodes ...*html.Node) *Selection {
|
|
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
|
nil, nodes), filter)
|
|
}
|
|
|
|
// Filter and push filters the nodes based on a matcher, and pushes the results
|
|
// on the stack, with the srcSel as previous selection.
|
|
func filterAndPush(srcSel *Selection, nodes []*html.Node, m Matcher) *Selection {
|
|
// Create a temporary Selection with the specified nodes to filter using winnow
|
|
sel := &Selection{nodes, srcSel.document, nil}
|
|
// Filter based on matcher and push on stack
|
|
return pushStack(srcSel, winnow(sel, m, true))
|
|
}
|
|
|
|
// Internal implementation of Find that return raw nodes.
|
|
func findWithMatcher(nodes []*html.Node, m Matcher) []*html.Node {
|
|
// Map nodes to find the matches within the children of each node
|
|
return mapNodes(nodes, func(i int, n *html.Node) (result []*html.Node) {
|
|
// Go down one level, becausejQuery's Find selects only within descendants
|
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
if c.Type == html.ElementNode {
|
|
result = append(result, m.MatchAll(c)...)
|
|
}
|
|
}
|
|
return
|
|
})
|
|
}
|
|
|
|
// Internal implementation to get all parent nodes, stopping at the specified
|
|
// node (or nil if no stop).
|
|
func getParentsNodes(nodes []*html.Node, stopm Matcher, stopNodes []*html.Node) []*html.Node {
|
|
return mapNodes(nodes, func(i int, n *html.Node) (result []*html.Node) {
|
|
for p := n.Parent; p != nil; p = p.Parent {
|
|
if stopm != nil {
|
|
if stopm.Match(p) {
|
|
break
|
|
}
|
|
} else if len(stopNodes) > 0 {
|
|
if isInSlice(stopNodes, p) {
|
|
break
|
|
}
|
|
}
|
|
if p.Type == html.ElementNode {
|
|
result = append(result, p)
|
|
}
|
|
}
|
|
return
|
|
})
|
|
}
|
|
|
|
// Internal implementation of sibling nodes that return a raw slice of matches.
|
|
func getSiblingNodes(nodes []*html.Node, st siblingType, untilm Matcher, untilNodes []*html.Node) []*html.Node {
|
|
var f func(*html.Node) bool
|
|
|
|
// If the requested siblings are ...Until, create the test function to
|
|
// determine if the until condition is reached (returns true if it is)
|
|
if st == siblingNextUntil || st == siblingPrevUntil {
|
|
f = func(n *html.Node) bool {
|
|
if untilm != nil {
|
|
return untilm.Match(n)
|
|
} else if len(untilNodes) > 0 {
|
|
return isInSlice(untilNodes, n)
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
return mapNodes(nodes, func(i int, n *html.Node) []*html.Node {
|
|
return getChildrenWithSiblingType(n.Parent, st, n, f)
|
|
})
|
|
}
|
|
|
|
// Gets the children nodes of each node in the specified slice of nodes,
|
|
// based on the sibling type request.
|
|
func getChildrenNodes(nodes []*html.Node, st siblingType) []*html.Node {
|
|
return mapNodes(nodes, func(i int, n *html.Node) []*html.Node {
|
|
return getChildrenWithSiblingType(n, st, nil, nil)
|
|
})
|
|
}
|
|
|
|
// Gets the children of the specified parent, based on the requested sibling
|
|
// type, skipping a specified node if required.
|
|
func getChildrenWithSiblingType(parent *html.Node, st siblingType, skipNode *html.Node,
|
|
untilFunc func(*html.Node) bool) (result []*html.Node) {
|
|
|
|
// Create the iterator function
|
|
var iter = func(cur *html.Node) (ret *html.Node) {
|
|
// Based on the sibling type requested, iterate the right way
|
|
for {
|
|
switch st {
|
|
case siblingAll, siblingAllIncludingNonElements:
|
|
if cur == nil {
|
|
// First iteration, start with first child of parent
|
|
// Skip node if required
|
|
if ret = parent.FirstChild; ret == skipNode && skipNode != nil {
|
|
ret = skipNode.NextSibling
|
|
}
|
|
} else {
|
|
// Skip node if required
|
|
if ret = cur.NextSibling; ret == skipNode && skipNode != nil {
|
|
ret = skipNode.NextSibling
|
|
}
|
|
}
|
|
case siblingPrev, siblingPrevAll, siblingPrevUntil:
|
|
if cur == nil {
|
|
// Start with previous sibling of the skip node
|
|
ret = skipNode.PrevSibling
|
|
} else {
|
|
ret = cur.PrevSibling
|
|
}
|
|
case siblingNext, siblingNextAll, siblingNextUntil:
|
|
if cur == nil {
|
|
// Start with next sibling of the skip node
|
|
ret = skipNode.NextSibling
|
|
} else {
|
|
ret = cur.NextSibling
|
|
}
|
|
default:
|
|
panic("Invalid sibling type.")
|
|
}
|
|
if ret == nil || ret.Type == html.ElementNode || st == siblingAllIncludingNonElements {
|
|
return
|
|
}
|
|
// Not a valid node, try again from this one
|
|
cur = ret
|
|
}
|
|
}
|
|
|
|
// For the cases that collect every matching sibling, count them in a
|
|
// cheap pointer walk first so the result slice can be sized exactly,
|
|
// avoiding repeated slice growth. The Until cases are skipped (counting
|
|
// would require running the predicate twice) and so are the single-result
|
|
// Next/Prev cases.
|
|
switch st {
|
|
case siblingAll, siblingAllIncludingNonElements, siblingPrevAll, siblingNextAll:
|
|
n := 0
|
|
for c := iter(nil); c != nil; c = iter(c) {
|
|
n++
|
|
}
|
|
result = make([]*html.Node, 0, n)
|
|
}
|
|
|
|
for c := iter(nil); c != nil; c = iter(c) {
|
|
// If this is an ...Until case, test before append (returns true
|
|
// if the until condition is reached)
|
|
if st == siblingNextUntil || st == siblingPrevUntil {
|
|
if untilFunc(c) {
|
|
return
|
|
}
|
|
}
|
|
result = append(result, c)
|
|
if st == siblingNext || st == siblingPrev {
|
|
// Only one node was requested (immediate next or previous), so exit
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Internal implementation of parent nodes that return a raw slice of Nodes.
|
|
func getParentNodes(nodes []*html.Node) []*html.Node {
|
|
return mapNodes(nodes, func(i int, n *html.Node) []*html.Node {
|
|
if n.Parent != nil && n.Parent.Type == html.ElementNode {
|
|
return []*html.Node{n.Parent}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Internal map function used by many traversing methods. Takes the source nodes
|
|
// to iterate on and the mapping function that returns an array of nodes.
|
|
// Returns an array of nodes mapped by calling the callback function once for
|
|
// each node in the source nodes.
|
|
func mapNodes(nodes []*html.Node, f func(int, *html.Node) []*html.Node) (result []*html.Node) {
|
|
switch len(nodes) {
|
|
case 0:
|
|
return nil
|
|
case 1:
|
|
return f(0, nodes[0])
|
|
}
|
|
|
|
set := make(map[*html.Node]bool, len(nodes))
|
|
for i, n := range nodes {
|
|
if vals := f(i, n); len(vals) > 0 {
|
|
result = appendWithoutDuplicates(result, vals, set)
|
|
}
|
|
}
|
|
return result
|
|
}
|