start adding matcher functions

This commit is contained in:
Martin Angers
2014-11-06 21:43:08 -05:00
parent cc9b6f8c6f
commit 251095afb5
8 changed files with 165 additions and 71 deletions
+11
View File
@@ -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 {
+8
View File
@@ -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 {
+20 -12
View File
@@ -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))
})
}
+53 -49
View File
@@ -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()
}
+5 -5
View File
@@ -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)
+12 -4
View File
@@ -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
+47 -1
View File
@@ -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.
+9
View File
@@ -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
}