add ParentsUntil...() with tests

This commit is contained in:
Martin Angers
2012-09-04 10:39:59 -04:00
parent cfa964cff5
commit d03482bacd
2 changed files with 64 additions and 14 deletions

View File

@@ -101,14 +101,14 @@ func (this *Selection) ParentFiltered(selector string) *Selection {
// Parents() gets the ancestors of each element in the current Selection. It // Parents() gets the ancestors of each element in the current Selection. It
// returns a new Selection object with the matched elements. // returns a new Selection object with the matched elements.
func (this *Selection) Parents() *Selection { func (this *Selection) Parents() *Selection {
return pushStack(this, getParentsNodes(this.Nodes, nil)) return pushStack(this, getParentsNodes(this.Nodes, "", nil))
} }
// ParentsFiltered() gets the ancestors of each element in the current // ParentsFiltered() gets the ancestors of each element in the current
// Selection. It returns a new Selection object with the matched elements. // Selection. It returns a new Selection object with the matched elements.
func (this *Selection) ParentsFiltered(selector string) *Selection { func (this *Selection) ParentsFiltered(selector string) *Selection {
// Get the Parents() unfiltered // Get the Parents() unfiltered
nodes := getParentsNodes(this.Nodes, nil) nodes := getParentsNodes(this.Nodes, "", nil)
// Create a temporary Selection to filter using winnow // Create a temporary Selection to filter using winnow
sel := &Selection{nodes, this.document, nil} sel := &Selection{nodes, this.document, nil}
// Filter based on selector // Filter based on selector
@@ -116,40 +116,71 @@ func (this *Selection) ParentsFiltered(selector string) *Selection {
return pushStack(this, nodes) return pushStack(this, nodes)
} }
// 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 (this *Selection) ParentsUntil(selector string) *Selection {
return pushStack(this, getParentsNodes(this.Nodes, selector, 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 (this *Selection) ParentsUntilSelection(sel *Selection) *Selection {
if sel == nil {
return this.Parents()
}
return this.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 (this *Selection) ParentsUntilNodes(nodes ...*html.Node) *Selection {
return pushStack(this, getParentsNodes(this.Nodes, "", nodes))
}
// Internal implementation to get all parent nodes, stopping at the specified // Internal implementation to get all parent nodes, stopping at the specified
// node (or nil if no stop). // node (or nil if no stop).
func getParentsNodes(nodes []*html.Node, stopNode *html.Node) []*html.Node { func getParentsNodes(nodes []*html.Node, stopSelector string, stopNodes []*html.Node) []*html.Node {
return mapNodes(nodes, func(i int, n *html.Node, stop *html.Node) (result []*html.Node) { return mapNodes(nodes, func(i int, n *html.Node) (result []*html.Node) {
for p := n.Parent; p != nil; p = p.Parent { for p := n.Parent; p != nil; p = p.Parent {
if p == stopNode { sel := newSingleSelection(p, nil)
break if stopSelector != "" {
if sel.Is(stopSelector) {
break
}
} else if len(stopNodes) > 0 {
if sel.IsNodes(stopNodes...) {
break
}
} }
if p.Type == html.ElementNode { if p.Type == html.ElementNode {
result = append(result, p) result = append(result, p)
} }
} }
return return
}, stopNode) })
} }
// Internal implementation of parent nodes that return a raw slice of Nodes. // Internal implementation of parent nodes that return a raw slice of Nodes.
func getParentNodes(nodes []*html.Node) []*html.Node { func getParentNodes(nodes []*html.Node) []*html.Node {
return mapNodes(nodes, func(i int, n *html.Node, _ *html.Node) []*html.Node { return mapNodes(nodes, func(i int, n *html.Node) []*html.Node {
if n.Parent != nil && n.Parent.Type == html.ElementNode { if n.Parent != nil && n.Parent.Type == html.ElementNode {
return []*html.Node{n.Parent} return []*html.Node{n.Parent}
} }
return nil return nil
}, nil) })
} }
// Internal map function used by many traversing methods. Takes the source nodes // Internal map function used by many traversing methods. Takes the source nodes
// to iterate on, the mapping function that returns an array of nodes, and a // to iterate on and the mapping function that returns an array of nodes.
// stop node used for the *Until methods. Returns an array of nodes mapped by // Returns an array of nodes mapped by calling the callback function once for
// calling the callback function once for each node in the source nodes. // each node in the source nodes.
func mapNodes(nodes []*html.Node, f func(int, *html.Node, *html.Node) []*html.Node, stopNode *html.Node) (result []*html.Node) { func mapNodes(nodes []*html.Node, f func(int, *html.Node) []*html.Node) (result []*html.Node) {
for i, n := range nodes { for i, n := range nodes {
if vals := f(i, n, stopNode); len(vals) > 0 { if vals := f(i, n); len(vals) > 0 {
result = appendWithoutDuplicates(result, vals) result = appendWithoutDuplicates(result, vals)
} }
} }

View File

@@ -74,3 +74,22 @@ func TestParentsFiltered(t *testing.T) {
sel := Doc().Root.Find(".container-fluid").ParentsFiltered("body") sel := Doc().Root.Find(".container-fluid").ParentsFiltered("body")
AssertLength(t, sel.Nodes, 1) AssertLength(t, sel.Nodes, 1)
} }
func TestParentsUntil(t *testing.T) {
sel := Doc().Root.Find(".container-fluid").ParentsUntil("body")
AssertLength(t, sel.Nodes, 6)
}
func TestParentsUntilSelection(t *testing.T) {
sel := Doc().Root.Find(".container-fluid")
sel2 := Doc().Root.Find(".pvk-content")
sel = sel.ParentsUntilSelection(sel2)
AssertLength(t, sel.Nodes, 3)
}
func TestParentsUntilNodes(t *testing.T) {
sel := Doc().Root.Find(".container-fluid")
sel2 := Doc().Root.Find(".pvk-content, .hero-unit")
sel = sel.ParentsUntilNodes(sel2.Nodes...)
AssertLength(t, sel.Nodes, 2)
}