From e4467e0f048fcf7d8a3aa1a7c734bf71557be16e Mon Sep 17 00:00:00 2001 From: Martin Angers Date: Thu, 30 Aug 2012 15:08:36 -0400 Subject: [PATCH] panic on error, instead of empty selection (like jQuery/Sizzle) --- add.go | 2 ++ children.go | 6 +---- doc.go | 15 ++++++----- filter.go | 71 +++++++++++++++++++++++++------------------------- filter_test.go | 2 +- find.go | 7 +---- find_test.go | 15 ++++++++--- 7 files changed, 60 insertions(+), 58 deletions(-) diff --git a/add.go b/add.go index 4b8d8c4..ef282bc 100644 --- a/add.go +++ b/add.go @@ -4,6 +4,8 @@ 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. func (this *Selection) Add(selector string) *Selection { diff --git a/children.go b/children.go index 64e1cad..dc4cc22 100644 --- a/children.go +++ b/children.go @@ -32,17 +32,13 @@ func childrenWithContext(selector string, nodes ...*html.Node) []*html.Node { var matches []*html.Node var allChildren bool var sel cascadia.Selector - var e error selector = strings.TrimSpace(selector) if selector == "*" || selector == "" { // Get all children allChildren = true } else { - if sel, e = cascadia.Compile(selector); e != nil { - // Selector doesn't compile, empty selection - return nil - } + sel = cascadia.MustCompile(selector) } for _, n := range nodes { diff --git a/doc.go b/doc.go index 22fbc9e..3143eb6 100644 --- a/doc.go +++ b/doc.go @@ -37,11 +37,14 @@ necessary since multiple return values cannot be used to allow a chainable inter */ package goquery -// Positional Filtering: First(), Last(), Eq(), Get(), Index() +// Positional Manipulation: First(), Last(), Eq(), Get(), Index(), Slice() +// Filtering: Filter(), Not(), Has(), End() // "Expanding": Add(), AndSelf() // Reflect (query) node: Is(), Contains(), HasClass() -// Inspect node: Contents(), Html(), Text(), Attr() +// Inspect node: Contents(), Html(), Text(), Attr(), Val() // Selection "properties": Length(), Size() +// Traversal: Find(), Children(), Parents...(), Next...(), Prev...(), Closest(), Siblings() +// Iteration: Each(), Map() // TODO : Benchmarks @@ -73,17 +76,17 @@ package goquery // - Next() - Tree traversal // - NextAll() - Tree traversal // - NextUntil() - Tree traversal -// - Not() - Filtering +// x Not() - Filtering // - Parent() - Tree traversal // - Parents() - Tree traversal // - ParentsUntil() - Tree traversal // - Prev() - Tree traversal // - PrevAll() - Tree traversal // - PrevUntil() - Tree traversal -// - PushStack() ? - Internals +// x PushStack() ? - Internals // - Siblings() - Tree traversal // - Slice() - Filtering // - Text() - DOM Manipulation -// - ToArray() Is not implemented, is Selection.Nodes -// - Unique() ? Or internally only, to remove duplicates and maintain node order? - Utilities +// x ToArray() Is not implemented, is Selection.Nodes +// x Unique() ? Or internally only, to remove duplicates and maintain node order? - Utilities // - Val() ? - Attributes diff --git a/filter.go b/filter.go index 48a658b..09607d5 100644 --- a/filter.go +++ b/filter.go @@ -6,61 +6,60 @@ import ( ) func (this *Selection) Filter(selector string) *Selection { - sel, e := cascadia.Compile(selector) - if e != nil { - // Selector doesn't compile, which means empty selection - return newEmptySelection(this.document) - } + return pushStack(this, winnow(this, selector, true)) +} - return &Selection{sel.Filter(this.Nodes), this.document, nil} +func (this *Selection) Not(selector string) *Selection { + return pushStack(this, winnow(this, selector, false)) } func (this *Selection) FilterFunction(f func(int, *Selection) bool) *Selection { - return &Selection{winnowFunction(this, f, true), this.document, nil} + return pushStack(this, winnowFunction(this, f, true)) } func (this *Selection) NotFunction(f func(int, *Selection) bool) *Selection { - return &Selection{winnowFunction(this, f, false), this.document, nil} + return pushStack(this, winnowFunction(this, f, false)) } -func (this *Selection) FilterNode(node *html.Node) *Selection { - if isInSlice(this.Nodes, node) { - return newSingleSelection(node, this.document) - } - return newEmptySelection(this.document) +func (this *Selection) FilterNodes(nodes ...*html.Node) *Selection { + return pushStack(this, winnowNodes(this, nodes, true)) +} + +func (this *Selection) NotNodes(nodes ...*html.Node) *Selection { + return pushStack(this, winnowNodes(this, nodes, false)) +} + +func (this *Selection) FilterSelection(s *Selection) *Selection { + return pushStack(this, winnowNodes(this, s.Nodes, true)) +} + +func (this *Selection) NotSelection(s *Selection) *Selection { + return pushStack(this, winnowNodes(this, s.Nodes, false)) } func (this *Selection) Union(s *Selection) *Selection { return this.FilterSelection(s) } -func (this *Selection) FilterSelection(s *Selection) *Selection { - var matches []*html.Node +func winnow(sel *Selection, selector string, keep bool) []*html.Node { + cs := cascadia.MustCompile(selector) - if s == nil { - return newEmptySelection(this.document) + // Optimize if keep is requested + if keep { + return cs.Filter(sel.Nodes) + } else { + // Use grep + return grep(sel, func(i int, s *Selection) bool { + return !cs(s.Get(0)) + }) } - - // Check for a match for each current selection - for _, n1 := range this.Nodes { - for _, n2 := range s.Nodes { - if n1 == n2 && !isInSlice(matches, n2) { - matches = append(matches, n1) - break - } - } - } - return &Selection{matches, this.document, nil} + return nil } -func winnow(sel *Selection, selector string) []*html.Node { - cs, e := cascadia.Compile(selector) - if e != nil { - // Selector doesn't compile, which means empty selection - return nil - } - - return cs.Filter(sel.Nodes) +func winnowNodes(sel *Selection, nodes []*html.Node, keep bool) []*html.Node { + return grep(sel, func(i int, s *Selection) bool { + return isInSlice(nodes, s.Get(0)) == keep + }) } // Identical functionality for FilterFunction() and NotFunction(), only keep changes. diff --git a/filter_test.go b/filter_test.go index 322edac..688fba7 100644 --- a/filter_test.go +++ b/filter_test.go @@ -29,7 +29,7 @@ func TestFilterFunction(t *testing.T) { func TestFilterNode(t *testing.T) { sel := doc.Find(".pvk-content") - sel2 := sel.FilterNode(sel.Nodes[2]) + sel2 := sel.FilterNodes(sel.Nodes[2]) if len(sel2.Nodes) != 1 { t.Errorf("Expected 1 node, found %v.", len(sel2.Nodes)) } diff --git a/find.go b/find.go index eb241e0..84bf0a8 100644 --- a/find.go +++ b/find.go @@ -19,12 +19,7 @@ func (this *Selection) Find(selector string) *Selection { func findWithContext(selector string, nodes ...*html.Node) []*html.Node { var matches []*html.Node - sel, e := cascadia.Compile(selector) - if e != nil { - // Selector doesn't compile, which means empty selection - return nil - } - + sel := cascadia.MustCompile(selector) // Match the selector on each node for _, n := range nodes { matches = append(matches, sel.MatchAll(n)...) diff --git a/find_test.go b/find_test.go index a231566..7342d0b 100644 --- a/find_test.go +++ b/find_test.go @@ -12,12 +12,19 @@ func TestFind(t *testing.T) { } func TestFindInvalidSelector(t *testing.T) { + defer func() { + if e := recover(); e == nil { + t.Error("Expected panic due to invalid selector.") + } + }() + + doc.Find(":+ ^") +} + +func TestEachEmptySelection(t *testing.T) { var cnt int - sel := doc.Find(":+ ^") - if sel.Nodes != nil { - t.Error("Expected a Selection object with Nodes == nil.") - } + sel := doc.Find("zzzz") sel.Each(func(i int, n *Selection) { cnt++ })