From 251095afb5c075d3d0a03e69c2edf1967e770346 Mon Sep 17 00:00:00 2001 From: Martin Angers Date: Thu, 6 Nov 2014 21:43:08 -0500 Subject: [PATCH] start adding matcher functions --- array.go | 11 ++++++ expand.go | 8 ++++ filter.go | 32 +++++++++------ manipulation.go | 102 +++++++++++++++++++++++++----------------------- property.go | 10 ++--- query.go | 16 ++++++-- traversal.go | 48 ++++++++++++++++++++++- type.go | 9 +++++ 8 files changed, 165 insertions(+), 71 deletions(-) diff --git a/array.go b/array.go index c32b940..82119d5 100644 --- a/array.go +++ b/array.go @@ -76,6 +76,17 @@ func (s *Selection) IndexSelector(selector string) int { return -1 } +// IndexMatcher returns the position of the first element within the +// Selection object relative to the elements matched by the matcher, or -1 if +// not found. +func (s *Selection) IndexMatcher(m Matcher) int { + if len(s.Nodes) > 0 { + sel := s.document.FindMatcher(m) + return indexInSlice(sel.Nodes, s.Nodes[0]) + } + return -1 +} + // IndexOfNode returns the position of the specified node within the Selection // object, or -1 if not found. func (s *Selection) IndexOfNode(node *html.Node) int { diff --git a/expand.go b/expand.go index b4421a3..7ad38c8 100644 --- a/expand.go +++ b/expand.go @@ -12,6 +12,14 @@ func (s *Selection) Add(selector string) *Selection { return s.AddNodes(findWithSelector([]*html.Node{s.document.rootNode}, selector)...) } +// AddMatcher adds the matcher's matching nodes to those in the current +// selection and returns a new Selection object. +// The matcher is run in the context of the document of the current +// Selection object. +func (s *Selection) AddMatcher(m Matcher) *Selection { + return s.AddNodes(findWithMatcher([]*html.Node{s.document.rootNode}, m)...) +} + // AddSelection adds the specified Selection object's nodes to those in the // current selection and returns a new Selection object. func (s *Selection) AddSelection(sel *Selection) *Selection { diff --git a/filter.go b/filter.go index c065112..bbea3dc 100644 --- a/filter.go +++ b/filter.go @@ -8,12 +8,14 @@ import ( // Filter reduces the set of matched elements to those that match the selector string. // It returns a new Selection object for this subset of matching elements. func (s *Selection) Filter(selector string) *Selection { - return s.FilterSelector(cascadia.MustCompile(selector)) + return s.FilterMatcher(cascadia.MustCompile(selector)) } -// Filter the set of matched elements by the given cascadia selector. -func (s *Selection) FilterSelector(cs cascadia.Selector) *Selection { - return pushStack(s, winnow(s, cs, true)) +// FilterMatcher reduces the set of matched elements to those that match +// the given matcher. +// It returns a new Selection object for this subset of matching elements. +func (s *Selection) FilterMatcher(m Matcher) *Selection { + return pushStack(s, winnow(s, m, true)) } // Not removes elements from the Selection that match the selector string. @@ -22,11 +24,10 @@ func (s *Selection) Not(selector string) *Selection { return s.NotSelector(cascadia.MustCompile(selector)) } -// Not removes elements from the Selection that match the given cascadia -// selector. +// NotMatcher removes elements from the Selection that match the given matcher. // It returns a new Selection object with the matching elements removed. -func (s *Selection) NotSelector(cs cascadia.Selector) *Selection { - return pushStack(s, winnow(s, cs, false)) +func (s *Selection) NotMatcher(m Matcher) *Selection { + return pushStack(s, winnow(s, m, false)) } // FilterFunction reduces the set of matched elements to those that pass the function's test. @@ -84,6 +85,13 @@ func (s *Selection) Has(selector string) *Selection { return s.HasSelection(s.document.Find(selector)) } +// HasMatcher reduces the set of matched elements to those that have a descendant +// that matches the matcher. +// It returns a new Selection object with the matching elements. +func (s *Selection) HasMatcher(m Matcher) *Selection { + return s.HasSelection(s.document.FindMatcher(m)) +} + // 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. @@ -118,16 +126,16 @@ func (s *Selection) End() *Selection { return newEmptySelection(s.document) } -// Filter based on the cascadia selector, and the indicator to keep (Filter) or +// Filter based on the matcher, and the indicator to keep (Filter) or // to get rid of (Not) the matching elements. -func winnow(sel *Selection, cs cascadia.Selector, keep bool) []*html.Node { +func winnow(sel *Selection, m Matcher, keep bool) []*html.Node { // Optimize if keep is requested if keep { - return cs.Filter(sel.Nodes) + return m.Filter(sel.Nodes) } // Use grep return grep(sel, func(i int, s *Selection) bool { - return !cs.Match(s.Get(0)) + return !m.Match(s.Get(0)) }) } diff --git a/manipulation.go b/manipulation.go index fc404b6..3ee0fec 100644 --- a/manipulation.go +++ b/manipulation.go @@ -5,7 +5,7 @@ import ( "strings" "code.google.com/p/cascadia" - "code.google.com/p/go.net/html" + "golang.org/x/net/html" ) func parseHtml(html string) *Selection { @@ -79,39 +79,43 @@ func (s *Selection) manipulateNodes( return s } -// From the root document, apply the selector, and insert the matched elements -// after element in the set of matched elements. +// After applies the selector from the root document and inserts the matched elements +// after the elements in the set of matched elements. // // If one of the matched elements in the selection is not currently in the // document, it's impossible to insert nodes after it, so it will be ignored. // -// This follows the same rules as Selection.Append(). +// This follows the same rules as Selection.Append. func (s *Selection) After(selector string) *Selection { - return s.AfterSelector(cascadia.MustCompile(selector)) + return s.AfterMatcher(cascadia.MustCompile(selector)) } -// From the root document, apply the cascadia selector, and insert the matched -// elements after each element in the set of matched elements. -// This follows the same rules as Selection.After(). -func (s *Selection) AfterSelector(cs cascadia.Selector) *Selection { - return s.AfterNodes(cs.MatchAll(s.document.rootNode)...) +// AfterMatcher applies the matcher from the root document and inserts the matched elements +// after the elements in the set of matched elements. +// +// If one of the matched elements in the selection is not currently in the +// document, it's impossible to insert nodes after it, so it will be ignored. +// +// This follows the same rules as Selection.Append. +func (s *Selection) AfterMatcher(m Matcher) *Selection { + return s.AfterNodes(m.MatchAll(s.document.rootNode)...) } -// Insert the elements in the selection after each element in the set of matched +// AfterSelection inserts the elements in the selection after each element in the set of matched // elements. -// This follows the same rules as Selection.After(). +// This follows the same rules as Selection.After. func (s *Selection) AfterSelection(sel *Selection) *Selection { return s.AfterNodes(sel.Nodes...) } -// Parse the html and insert it after the set of matched elements -// This follows the same rules as Selection.After(). +// AfterHtml parses the html and inserts it after the set of matched elements +// This follows the same rules as Selection.After. func (s *Selection) AfterHtml(html string) *Selection { return s.AfterSelection(parseHtml(html)) } -// Insert the nodes after each element in the set of matched elements. -// This follows the same rules as Selection.After(). +// AfterNodes inserts the nodes after each element in the set of matched elements. +// This follows the same rules as Selection.After. func (s *Selection) AfterNodes(ns ...*html.Node) *Selection { return s.manipulateNodes(ns, true, func(sn *html.Node, n *html.Node) { if sn.Parent != nil { @@ -134,65 +138,65 @@ func (s *Selection) AfterNodes(ns ...*html.Node) *Selection { // appended to all target locations except the last, which will be moved // as noted in (1). func (s *Selection) Append(selector string) *Selection { - return s.AppendSelector(cascadia.MustCompile(selector)) + return s.AppendMatcher(cascadia.MustCompile(selector)) } -// From the root document, apply the cascadia selector, and append those nodes +// AppendMatcher applies the matcher from the root document, and append those nodes // to the set of matched elements. -// This follows the same rules as Selection.Append(). -func (s *Selection) AppendSelector(cs cascadia.Selector) *Selection { - return s.AppendNodes(cs.MatchAll(s.document.rootNode)...) +// This follows the same rules as Selection.Append. +func (s *Selection) AppendMatcher(m Matcher) *Selection { + return s.AppendNodes(m.MatchAll(s.document.rootNode)...) } -// Append the elements in the selection to the end of each element in the +// AppendSelection appends the elements in the selection to the end of each element in the // set of matched elements. -// This follows the same rules as Selection.Append(). +// This follows the same rules as Selection.Append. func (s *Selection) AppendSelection(sel *Selection) *Selection { return s.AppendNodes(sel.Nodes...) } -// Parse the html and append it to the set of matched elements +// AppendHtml parses the html and appends it to the set of matched elements. func (s *Selection) AppendHtml(html string) *Selection { return s.AppendSelection(parseHtml(html)) } -// Append the specified nodes to each node in the set of matched elements. -// This follows the same rules as Selection.Append(). +// AppendNodes appends the specified nodes to each node in the set of matched elements. +// This follows the same rules as Selection.Append. func (s *Selection) AppendNodes(ns ...*html.Node) *Selection { return s.manipulateNodes(ns, false, func(sn *html.Node, n *html.Node) { sn.AppendChild(n) }) } -// From the root document, apply the selector, and insert the matched elements +// Before applies the selector from the root document, and inserts the matched elements // before each element in the set of matched elements. -// This follows the same rules as Selection.After(). +// This follows the same rules as Selection.After. func (s *Selection) Before(selector string) *Selection { - return s.BeforeSelector(cascadia.MustCompile(selector)) + return s.BeforeMatcher(cascadia.MustCompile(selector)) } -// From the root document, apply the cascadia selector, and insert the matched +// BeforeSelector applies the matcher from the root document, and inserts the matched // elements before each element in the set of matched elements. -// This follows the same rules as Selection.After(). -func (s *Selection) BeforeSelector(cs cascadia.Selector) *Selection { - return s.BeforeNodes(cs.MatchAll(s.document.rootNode)...) +// This follows the same rules as Selection.After. +func (s *Selection) BeforeMatcher(m Matcher) *Selection { + return s.BeforeNodes(m.MatchAll(s.document.rootNode)...) } -// Insert the elements in the selection before each element in the set of matched +// BeforeSelection inserts the elements in the selection before each element in the set of matched // elements. -// This follows the same rules as Selection.After(). +// This follows the same rules as Selection.After. func (s *Selection) BeforeSelection(sel *Selection) *Selection { return s.BeforeNodes(sel.Nodes...) } -// Parse the html and insert it before the set of matched elements -// This follows the same rules as Selection.After(). +// BeforeHtml parses the html and inserts it before the set of matched elements. +// This follows the same rules as Selection.After. func (s *Selection) BeforeHtml(html string) *Selection { return s.BeforeSelection(parseHtml(html)) } -// Insert the nodes before each element in the set of matched elements. -// This follows the same rules as Selection.After(). +// BeforeNodes inserts the nodes before each element in the set of matched elements. +// This follows the same rules as Selection.After. func (s *Selection) BeforeNodes(ns ...*html.Node) *Selection { return s.manipulateNodes(ns, false, func(sn *html.Node, n *html.Node) { if sn.Parent != nil { @@ -201,7 +205,7 @@ func (s *Selection) BeforeNodes(ns ...*html.Node) *Selection { }) } -// Create a deep copy of the set of matched nodes. The new nodes will not be +// Clone creates a deep copy of the set of matched nodes. The new nodes will not be // attached to the document. func (s *Selection) Clone() *Selection { ns := newEmptySelection(s.document) @@ -209,7 +213,7 @@ func (s *Selection) Clone() *Selection { return ns } -// Remove all children nodes from the set of matched elements. +// Empty removes all children nodes from the set of matched elements. // Returns the children nodes in a new Selection on the current Selection stack. func (s *Selection) Empty() *Selection { nodes := make([]*html.Node, 0) @@ -224,7 +228,7 @@ func (s *Selection) Empty() *Selection { return pushStack(s, nodes) } -// Remove the set of matched elements from the document. +// Remove removes the set of matched elements from the document. // Returns the same selection, now consisting of nodes not in the document. func (s *Selection) Remove() *Selection { for _, n := range s.Nodes { @@ -236,14 +240,14 @@ func (s *Selection) Remove() *Selection { return s } -// Filter the set of matched elements by selector before removing. -// Returns the filtered Selection. +// RemoveFilter removes the set of matched elements by selector. +// Returns the Selection of removed nodes. func (s *Selection) RemoveFilter(selector string) *Selection { - return s.RemoveFilterSelector(cascadia.MustCompile(selector)) + return s.RemoveMatcher(cascadia.MustCompile(selector)) } -// Filter the set of matched elements by cascadia selector before removing. -// Returns the filtered Selection. -func (s *Selection) RemoveFilterSelector(cs cascadia.Selector) *Selection { - return s.FilterSelector(cs).Remove() +// RemoveMatcher removes the set of matched elements. +// Returns the Selection of removed nodes. +func (s *Selection) RemoveMatcher(m Matcher) *Selection { + return s.FilterMatcher(m).Remove() } diff --git a/property.go b/property.go index 532949f..779f1ae 100644 --- a/property.go +++ b/property.go @@ -20,7 +20,7 @@ func (s *Selection) Attr(attrName string) (val string, exists bool) { return getAttributeValue(attrName, s.Nodes[0]) } -// Remove the named attribute from each element in the set of matched elements. +// RemoveAttr removes the named attribute from each element in the set of matched elements. func (s *Selection) RemoveAttr(attrName string) *Selection { for _, n := range s.Nodes { removeAttr(n, attrName) @@ -29,7 +29,7 @@ func (s *Selection) RemoveAttr(attrName string) *Selection { return s } -// Set the given attribute on each element in the set of matched elements. +// SetAttr sets the given attribute on each element in the set of matched elements. func (s *Selection) SetAttr(attrName string, val string) *Selection { for _, n := range s.Nodes { if attr, ok := getAttribute(attrName, n); ok { @@ -82,7 +82,7 @@ func (s *Selection) Html() (ret string, e error) { return } -// Add the given class(es) to each element in the set of matched elements. +// AddClass adds the given class(es) to each element in the set of matched elements. func (s *Selection) AddClass(class string) *Selection { rclasses := getClassesSlice(class) @@ -113,7 +113,7 @@ func (s *Selection) HasClass(class string) bool { return false } -// Remove the given class(es) from each element in the set of matched elements. +// RemoveClass removes the given class(es) from each element in the set of matched elements. func (s *Selection) RemoveClass(class string) *Selection { rclasses := getClassesSlice(class) @@ -139,7 +139,7 @@ func (s *Selection) RemoveClasses() *Selection { return s } -// Add or remove the given class(es) for each element in the set of matched elements. +// ToggleClass adds or removes the given class(es) for each element in the set of matched elements. func (s *Selection) ToggleClass(class string) *Selection { tcls := getClassesSlice(class) diff --git a/query.go b/query.go index 2d86bc5..6147ad6 100644 --- a/query.go +++ b/query.go @@ -9,12 +9,20 @@ import ( // returns true if at least one of these elements matches. func (s *Selection) Is(selector string) bool { if len(s.Nodes) > 0 { - // Attempt a match with the selector - cs := cascadia.MustCompile(selector) + return s.IsMatcher(cascadia.MustCompile(selector)) + } + + return false +} + +// IsMatcher checks the current matched set of elements against a matcher and +// returns true if at least one of these elements matches. +func (s *Selection) IsMatcher(m Matcher) bool { + if len(s.Nodes) > 0 { if len(s.Nodes) == 1 { - return cs.Match(s.Nodes[0]) + return m.Match(s.Nodes[0]) } - return len(cs.Filter(s.Nodes)) > 0 + return len(m.Filter(s.Nodes)) > 0 } return false diff --git a/traversal.go b/traversal.go index 50e7f78..39c3903 100644 --- a/traversal.go +++ b/traversal.go @@ -27,6 +27,13 @@ func (s *Selection) Find(selector string) *Selection { return pushStack(s, findWithSelector(s.Nodes, 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. @@ -68,6 +75,14 @@ func (s *Selection) ContentsFiltered(selector string) *Selection { 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 { @@ -81,6 +96,13 @@ func (s *Selection) ChildrenFiltered(selector string) *Selection { return filterAndPush(s, getChildrenNodes(s.Nodes, siblingAll), 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 filterAndPushMatcher(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 { @@ -93,16 +115,27 @@ func (s *Selection) ParentFiltered(selector string) *Selection { return filterAndPush(s, getParentNodes(s.Nodes), 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 filterAndPushMatcher(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 cs.Match(n) { + if m.Match(n) { return []*html.Node{n} } } @@ -147,6 +180,12 @@ func (s *Selection) ParentsFiltered(selector string) *Selection { return filterAndPush(s, getParentsNodes(s.Nodes, "", nil), 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 filterAndPushMatcher(s, getParentsNodes(s.Nodes, "", 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. @@ -154,6 +193,13 @@ func (s *Selection) ParentsUntil(selector string) *Selection { return pushStack(s, getParentsNodes(s.Nodes, 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 pushStackMatcher(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. diff --git a/type.go b/type.go index 5f5e27b..28b64f9 100644 --- a/type.go +++ b/type.go @@ -102,3 +102,12 @@ func newEmptySelection(doc *Document) *Selection { func newSingleSelection(node *html.Node, doc *Document) *Selection { return &Selection{[]*html.Node{node}, doc, nil} } + +// Matcher is an interface that defines the methods to match +// HTML nodes against a compiled selector string. Cascadia's +// Selector implements this interface. +type Matcher interface { + Match(*html.Node) bool + MatchAll(*html.Node) []*html.Node + Filter([]*html.Node) []*html.Node +}