From f1e54e5311a8f30ecf0fa8f30b1f96ea5979bd21 Mon Sep 17 00:00:00 2001 From: Martin Angers Date: Fri, 31 Aug 2012 10:27:45 -0400 Subject: [PATCH] add Add() and AndSelf(), with tests --- array.go | 29 +++++++++++++----------- doc.go | 2 +- expand.go | 36 +++++++++++++++++------------ expand_test.go | 61 +++++++++++++++++++++++++++++++++++++++----------- filter.go | 32 +++++++++++++++++--------- filter_test.go | 10 +++++++++ utilities.go | 24 +++++++++++++------- 7 files changed, 135 insertions(+), 59 deletions(-) diff --git a/array.go b/array.go index baa00fb..f624e8a 100644 --- a/array.go +++ b/array.go @@ -17,8 +17,9 @@ func (this *Selection) Last() *Selection { } // Eq() reduces the set of matched elements to the one at the specified index. -// If a negative index is given, it counts backwards starting at the end of the set. -// It returns a new Selection object, and an empty Selection object if the index is invalid. +// If a negative index is given, it counts backwards starting at the end of the +// set. It returns a new Selection object, and an empty Selection object if the +// index is invalid. func (this *Selection) Eq(index int) *Selection { if index < 0 { index += len(this.Nodes) @@ -26,9 +27,10 @@ func (this *Selection) Eq(index int) *Selection { return this.Slice(index, index+1) } -// Slice() reduces the set of matched elements to a subset specified by a range of indices. -// At the moment, negative indices are not supported. +// Slice() reduces the set of matched elements to a subset specified by a range +// of indices. At the moment, negative indices are not supported. func (this *Selection) Slice(start int, end int) *Selection { + // TODO : Negative indices, like jQuery return pushStack(this, this.Nodes[start:end]) } @@ -42,8 +44,8 @@ func (this *Selection) Get(index int) *html.Node { return this.Nodes[index] } -// Index() returns the position of the first element within the Selection object relative to -// its sibling elements. +// Index() returns the position of the first element within the Selection object +// relative to its sibling elements. func (this *Selection) Index() int { // TODO : Eventually refactor with prevAll(), like jQuery's code if len(this.Nodes) > 0 { @@ -63,8 +65,9 @@ func (this *Selection) Index() int { return -1 } -// IndexSelector() returns the position of the first element within the Selection object -// relative to the elements matched by the selector, or -1 if not found. +// IndexSelector() returns the position of the first element within the +// Selection object relative to the elements matched by the selector, or -1 if +// not found. func (this *Selection) IndexSelector(selector string) int { if len(this.Nodes) > 0 { sel := this.document.Find(selector) @@ -73,16 +76,16 @@ func (this *Selection) IndexSelector(selector string) int { return -1 } -// IndexOfNode() returns the position of the specified node within the Selection object, -// or -1 if not found. +// IndexOfNode() returns the position of the specified node within the Selection +// object, or -1 if not found. func (this *Selection) IndexOfNode(node *html.Node) int { return indexInSlice(this.Nodes, node) } -// IndexOfSelection() returns the position of the first node in the specified Selection object -// within this Selection object, or -1 if not found. +// IndexOfSelection() returns the position of the first node in the specified +// Selection object within this Selection object, or -1 if not found. func (this *Selection) IndexOfSelection(s *Selection) int { - if len(s.Nodes) > 0 { + if s != nil && len(s.Nodes) > 0 { return indexInSlice(this.Nodes, s.Nodes[0]) } return -1 diff --git a/doc.go b/doc.go index 504eafe..7fc7ed7 100644 --- a/doc.go +++ b/doc.go @@ -41,7 +41,7 @@ https://github.com/puerkitobio/goquery package goquery // DONE array.go : Positional Manipulation: First(), Last(), Eq(), Get(), Index(), Slice() -// TESTS filter.go : Filtering: Filter(), Not(), Has(), End() +// DONE filter.go : Filtering: Filter(), Not(), Has(), End() // expand.go : "Expanding": Add(), AndSelf() // query.go : Reflect (query) node: Is(), Contains(), HasClass() // property.go : Inspect node: Contents(), Html(), Text(), Attr(), Val(), Length(), Size() diff --git a/expand.go b/expand.go index ef282bc..3770269 100644 --- a/expand.go +++ b/expand.go @@ -4,24 +4,32 @@ import ( "exp/html" ) -// TODO : Should return a new Selection object, use pushStack() - -// Adds matching nodes to the current selection. Returns the same Selection object. -// The new selector string is run in the context of the document of the Selection object. +// Add() adds the selector string's matching nodes to those in the current +// selection and returns a new Selection object. +// The selector string is run in the context of the document of the current +// Selection object. func (this *Selection) Add(selector string) *Selection { - this.Nodes = appendWithoutDuplicates(this.Nodes, findWithContext(selector, this.document.Root)) - return this + return this.AddNodes(findWithContext(selector, this.document.Root)...) } -// Adds nodes of the specified Selection object to the current selection. Returns the same Selection object. +// AddSelection() adds the specified Selection object's nodes to those in the +// current selection and returns a new Selection object. func (this *Selection) AddSelection(sel *Selection) *Selection { - this.Nodes = appendWithoutDuplicates(this.Nodes, sel.Nodes) - return this + if sel == nil { + return this.AddNodes() + } + return this.AddNodes(sel.Nodes...) } -// Adds nodes to the current selection. Returns the same Selection object. -// No verification about the same document origin is done. -func (this *Selection) AddNodes(nodes []*html.Node) *Selection { - this.Nodes = appendWithoutDuplicates(this.Nodes, nodes) - return this +// AddNodes() adds the specified nodes to those in the +// current selection and returns a new Selection object. +func (this *Selection) AddNodes(nodes ...*html.Node) *Selection { + return pushStack(this, appendWithoutDuplicates(this.Nodes, nodes)) +} + +// AndSelf() adds the previous set of elements on the stack to the current set. +// It returns a new Selection object containing the current Selection combined +// with the previous one. +func (this *Selection) AndSelf() *Selection { + return this.AddSelection(this.prevSel) } diff --git a/expand_test.go b/expand_test.go index 333d5cc..017bef2 100644 --- a/expand_test.go +++ b/expand_test.go @@ -5,18 +5,53 @@ import ( ) func TestAdd(t *testing.T) { - var cnt int - - EnsureDocLoaded() - - sel := Doc().Find("div.row-fluid") - cnt = len(sel.Nodes) - sel2 := sel.Add("a") - if sel != sel2 { - t.Error("Expected returned Selection from Add() to be same as 'this'.") + sel := Doc().Find("div.row-fluid").Add("a") + if len(sel.Nodes) != 19 { + t.Errorf("Expected 19 nodes, found %v.", len(sel.Nodes)) + } +} + +func TestAddSelection(t *testing.T) { + sel := Doc().Find("div.row-fluid") + sel2 := Doc().Find("a") + sel = sel.AddSelection(sel2) + if len(sel.Nodes) != 19 { + t.Errorf("Expected 19 nodes, found %v.", len(sel.Nodes)) + } +} + +func TestAddSelectionNil(t *testing.T) { + sel := Doc().Find("div.row-fluid") + if len(sel.Nodes) != 9 { + t.Errorf("Expected div.row-fluid to have 9 nodes, found %v.", + len(sel.Nodes)) + } + sel = sel.AddSelection(nil) + if len(sel.Nodes) != 9 { + t.Errorf("Expected add nil to keep it to 9 nodes, found %v.", + len(sel.Nodes)) + } +} + +func TestAddNodes(t *testing.T) { + sel := Doc().Find("div.pvk-gutter") + sel2 := Doc().Find(".pvk-content") + sel = sel.AddNodes(sel2.Nodes...) + if len(sel.Nodes) != 9 { + t.Errorf("Expected 9 nodes, found %v.", len(sel.Nodes)) + } +} + +func TestAddNodesNone(t *testing.T) { + sel := Doc().Find("div.pvk-gutter").AddNodes() + if len(sel.Nodes) != 6 { + t.Errorf("Expected 6 nodes, found %v.", len(sel.Nodes)) + } +} + +func TestAndSelf(t *testing.T) { + sel := Doc().Find(".span12").Last().AndSelf() + if len(sel.Nodes) != 2 { + t.Errorf("Expected 2 nodes, found %v.", len(sel.Nodes)) } - if len(sel.Nodes) <= cnt { - t.Error("Expected nodes to be added to Selection.") - } - t.Logf("%v nodes after div.row-fluid and a were added.", len(sel.Nodes)) } diff --git a/filter.go b/filter.go index d29251b..20b1bef 100644 --- a/filter.go +++ b/filter.go @@ -41,15 +41,23 @@ func (this *Selection) NotNodes(nodes ...*html.Node) *Selection { return pushStack(this, winnowNodes(this, nodes, false)) } -// FilterSelection() reduces the set of matched elements to those that match a node in the specified Selection object. +// FilterSelection() reduces the set of matched elements to those that match a +// node in the specified Selection object. // It returns a new Selection object for this subset of elements. func (this *Selection) FilterSelection(s *Selection) *Selection { + if s == nil { + return pushStack(this, winnowNodes(this, nil, true)) + } return pushStack(this, winnowNodes(this, s.Nodes, true)) } -// Not() removes elements from the Selection that match a node in the specified Selection object. +// Not() removes elements from the Selection that match a node in the specified +// Selection object. // It returns a new Selection object with the matching elements removed. func (this *Selection) NotSelection(s *Selection) *Selection { + if s == nil { + return pushStack(this, winnowNodes(this, nil, false)) + } return pushStack(this, winnowNodes(this, s.Nodes, false)) } @@ -58,14 +66,15 @@ func (this *Selection) Union(s *Selection) *Selection { return this.FilterSelection(s) } -// Has() reduces the set of matched elements to those that have a descendant that matches the selector. +// Has() reduces the set of matched elements to those that have a descendant +// that matches the selector. // It returns a new Selection object with the matching elements. func (this *Selection) Has(selector string) *Selection { - sel := this.document.Find(selector) - return this.HasSelection(sel) + return this.HasSelection(this.document.Find(selector)) } -// HasNodes() reduces the set of matched elements to those that have a descendant that matches one of the nodes. +// HasNodes() reduces the set of matched elements to those that have a +// descendant that matches one of the nodes. // It returns a new Selection object with the matching elements. func (this *Selection) HasNodes(nodes ...*html.Node) *Selection { return this.FilterFunction(func(_ int, s *Selection) bool { @@ -79,15 +88,18 @@ func (this *Selection) HasNodes(nodes ...*html.Node) *Selection { }) } -// HasSelection() reduces the set of matched elements to those that have a descendant that matches one of -// the nodes of the specified Selection object. +// HasSelection() reduces the set of matched elements to those that have a +// descendant that matches one of the nodes of the specified Selection object. // It returns a new Selection object with the matching elements. func (this *Selection) HasSelection(sel *Selection) *Selection { + if sel == nil { + return this.HasNodes() + } return this.HasNodes(sel.Nodes...) } -// End() ends the most recent filtering operation in the current chain and returns -// the set of matched elements to its previous state. +// End() ends the most recent filtering operation in the current chain and +// returns the set of matched elements to its previous state. func (this *Selection) End() *Selection { if this.prevSel != nil { return this.prevSel diff --git a/filter_test.go b/filter_test.go index 69fbcd6..97fb706 100644 --- a/filter_test.go +++ b/filter_test.go @@ -44,6 +44,16 @@ func TestFilterSelection(t *testing.T) { } } +func TestFilterSelectionNil(t *testing.T) { + var sel2 *Selection + + sel := Doc().Find(".link") + sel3 := sel.FilterSelection(sel2) + if len(sel3.Nodes) != 0 { + t.Errorf("Expected no node, found %v.", len(sel3.Nodes)) + } +} + func TestNot(t *testing.T) { sel := Doc().Find(".span12").Not(".alert") if len(sel.Nodes) != 1 { diff --git a/utilities.go b/utilities.go index 7fcb1bb..09961f7 100644 --- a/utilities.go +++ b/utilities.go @@ -19,7 +19,9 @@ func sliceContains(container []*html.Node, contained *html.Node) bool { func nodeContains(container *html.Node, contained *html.Node) bool { // Check if the parent of the contained node is the container node, traversing // upward until the top is reached, or the container is found. - for contained = contained.Parent; contained != nil; contained = contained.Parent { + for contained = contained.Parent; contained != nil; contained = + contained.Parent { + if container == contained { return true } @@ -45,9 +47,12 @@ func indexInSlice(slice []*html.Node, node *html.Node) int { } // Appends the new nodes to the target slice, making sure no duplicate is added. -// There is no check to the original state of the target slice, so it may still contain -// duplicates. The target slice is returned because append() may create a new underlying array. -func appendWithoutDuplicates(target []*html.Node, nodes []*html.Node) []*html.Node { +// There is no check to the original state of the target slice, so it may still +// contain duplicates. The target slice is returned because append() may create +// a new underlying array. +func appendWithoutDuplicates(target []*html.Node, + nodes []*html.Node) []*html.Node { + for _, n := range nodes { if !isInSlice(target, n) { target = append(target, n) @@ -57,8 +62,11 @@ func appendWithoutDuplicates(target []*html.Node, nodes []*html.Node) []*html.No return target } -// Loop through a selection, returning only those nodes that pass the predicate function. -func grep(sel *Selection, predicate func(i int, s *Selection) bool) (result []*html.Node) { +// Loop through a selection, returning only those nodes that pass the predicate +// function. +func grep(sel *Selection, predicate func(i int, + s *Selection) bool,) (result []*html.Node) { + for i, n := range sel.Nodes { if predicate(i, newSingleSelection(n, sel.document)) { result = append(result, n) @@ -67,8 +75,8 @@ func grep(sel *Selection, predicate func(i int, s *Selection) bool) (result []*h return } -// Creates a new Selection object based on the specified nodes, and keeps the source -// Selection object on the stack (linked list). +// Creates a new Selection object based on the specified nodes, and keeps the +// source Selection object on the stack (linked list). func pushStack(fromSel *Selection, nodes []*html.Node) (result *Selection) { result = &Selection{nodes, fromSel.document, fromSel} return