package goquery import ( "github.com/andybalholm/cascadia" "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. func (s *Selection) Find(selector string) *Selection { return pushStack(s, findWithMatcher(s.Nodes, cascadia.MustCompile(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), cascadia.MustCompile(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), cascadia.MustCompile(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 := cascadia.MustCompile(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 { 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 isInSlice(nodes, 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), cascadia.MustCompile(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, cascadia.MustCompile(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, cascadia.MustCompile(untilSelector), nil), cascadia.MustCompile(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(cascadia.MustCompile(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), cascadia.MustCompile(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), cascadia.MustCompile(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), cascadia.MustCompile(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), cascadia.MustCompile(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), cascadia.MustCompile(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), cascadia.MustCompile(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, cascadia.MustCompile(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, cascadia.MustCompile(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, cascadia.MustCompile(untilSelector), nil), cascadia.MustCompile(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(cascadia.MustCompile(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), cascadia.MustCompile(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, cascadia.MustCompile(untilSelector), nil), cascadia.MustCompile(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(cascadia.MustCompile(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), cascadia.MustCompile(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 { sel := newSingleSelection(p, nil) if stopm != nil { if sel.IsMatcher(stopm) { break } } else if len(stopNodes) > 0 { if sel.IsNodes(stopNodes...) { 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 { // Matcher-based condition sel := newSingleSelection(n, nil) return sel.IsMatcher(untilm) } else if len(untilNodes) > 0 { // Nodes-based condition sel := newSingleSelection(n, nil) return sel.IsNodes(untilNodes...) } 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 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) { for i, n := range nodes { if vals := f(i, n); len(vals) > 0 { result = appendWithoutDuplicates(result, vals) } } return }