start work on PrevUntil() and NextUntil()

This commit is contained in:
Martin Angers
2012-09-06 10:27:42 -04:00
parent bf960f0855
commit a9c4ad4c8f
3 changed files with 61 additions and 22 deletions

View File

@@ -62,7 +62,7 @@ Taken from example_test.go:
title = s.Find("em").Text() title = s.Find("em").Text()
if score, e = strconv.ParseFloat(s.Find(".score").Text(), 64); e != nil { if score, e = strconv.ParseFloat(s.Find(".score").Text(), 64); e != nil {
// Not a valid float, ignore score // Not a valid float, ignore score
fmt.Printf("Review %d: %s - %s", i, band, title) fmt.Printf("Review %d: %s - %s.\n", i, band, title)
} else { } else {
// Print all, including score // Print all, including score
fmt.Printf("Review %d: %s - %s (%2.1f).\n", i, band, title, score) fmt.Printf("Review %d: %s - %s (%2.1f).\n", i, band, title, score)
@@ -79,7 +79,6 @@ Taken from example_test.go:
## TODOs ## TODOs
* Implement NextUntil() and PrevUntil(). * Implement NextUntil() and PrevUntil().
* Fix Prev...() functions to return nodes in the same order as jQuery (starting with the immediately preceding node, up until first child of the parent).
* Benchmarks so that future changes have a baseline to compare to. * Benchmarks so that future changes have a baseline to compare to.
* Add jQuery's `Closest()`? Other missing functions? * Add jQuery's `Closest()`? Other missing functions?
* Support negative indices in `Slice()`, like jQuery. * Support negative indices in `Slice()`, like jQuery.

View File

@@ -28,7 +28,7 @@ func ExampleScrape_MetalReview() {
title = s.Find("em").Text() title = s.Find("em").Text()
if score, e = strconv.ParseFloat(s.Find(".score").Text(), 64); e != nil { if score, e = strconv.ParseFloat(s.Find(".score").Text(), 64); e != nil {
// Not a valid float, ignore score // Not a valid float, ignore score
fmt.Printf("Review %d: %s - %s", i, band, title) fmt.Printf("Review %d: %s - %s.\n", i, band, title)
} else { } else {
// Print all, including score // Print all, including score
fmt.Printf("Review %d: %s - %s (%2.1f).\n", i, band, title, score) fmt.Printf("Review %d: %s - %s (%2.1f).\n", i, band, title, score)

View File

@@ -158,66 +158,70 @@ func (this *Selection) ParentsFilteredUntilNodes(filterSelector string, nodes ..
// Siblings() gets the siblings of each element in the Selection. It returns // Siblings() gets the siblings of each element in the Selection. It returns
// a new Selection object containing the matched elements. // a new Selection object containing the matched elements.
func (this *Selection) Siblings() *Selection { func (this *Selection) Siblings() *Selection {
return pushStack(this, getSiblingNodes(this.Nodes, siblingAll)) return pushStack(this, getSiblingNodes(this.Nodes, siblingAll, "", nil))
} }
// SiblingsFiltered() gets the siblings of each element in the Selection // SiblingsFiltered() gets the siblings of each element in the Selection
// filtered by a selector. It returns a new Selection object containing the // filtered by a selector. It returns a new Selection object containing the
// matched elements. // matched elements.
func (this *Selection) SiblingsFiltered(selector string) *Selection { func (this *Selection) SiblingsFiltered(selector string) *Selection {
return filterAndPush(this, getSiblingNodes(this.Nodes, siblingAll), selector) return filterAndPush(this, getSiblingNodes(this.Nodes, siblingAll, "", nil), selector)
} }
// Next() gets the immediately following sibling of each element in the // Next() gets the immediately following sibling of each element in the
// Selection. It returns a new Selection object containing the matched elements. // Selection. It returns a new Selection object containing the matched elements.
func (this *Selection) Next() *Selection { func (this *Selection) Next() *Selection {
return pushStack(this, getSiblingNodes(this.Nodes, siblingNext)) return pushStack(this, getSiblingNodes(this.Nodes, siblingNext, "", nil))
} }
// NextFiltered() gets the immediately following sibling of each element in the // NextFiltered() gets the immediately following sibling of each element in the
// Selection filtered by a selector. It returns a new Selection object // Selection filtered by a selector. It returns a new Selection object
// containing the matched elements. // containing the matched elements.
func (this *Selection) NextFiltered(selector string) *Selection { func (this *Selection) NextFiltered(selector string) *Selection {
return filterAndPush(this, getSiblingNodes(this.Nodes, siblingNext), selector) return filterAndPush(this, getSiblingNodes(this.Nodes, siblingNext, "", nil), selector)
} }
// NextAll() gets all the following siblings of each element in the // NextAll() gets all the following siblings of each element in the
// Selection. It returns a new Selection object containing the matched elements. // Selection. It returns a new Selection object containing the matched elements.
func (this *Selection) NextAll() *Selection { func (this *Selection) NextAll() *Selection {
return pushStack(this, getSiblingNodes(this.Nodes, siblingNextAll)) return pushStack(this, getSiblingNodes(this.Nodes, siblingNextAll, "", nil))
} }
// NextAllFiltered() gets all the following siblings of each element in the // NextAllFiltered() gets all the following siblings of each element in the
// Selection filtered by a selector. It returns a new Selection object // Selection filtered by a selector. It returns a new Selection object
// containing the matched elements. // containing the matched elements.
func (this *Selection) NextAllFiltered(selector string) *Selection { func (this *Selection) NextAllFiltered(selector string) *Selection {
return filterAndPush(this, getSiblingNodes(this.Nodes, siblingNextAll), selector) return filterAndPush(this, getSiblingNodes(this.Nodes, siblingNextAll, "", nil), selector)
} }
// Prev() gets the immediately preceding sibling of each element in the // Prev() gets the immediately preceding sibling of each element in the
// Selection. It returns a new Selection object containing the matched elements. // Selection. It returns a new Selection object containing the matched elements.
func (this *Selection) Prev() *Selection { func (this *Selection) Prev() *Selection {
return pushStack(this, getSiblingNodes(this.Nodes, siblingPrev)) return pushStack(this, getSiblingNodes(this.Nodes, siblingPrev, "", nil))
} }
// PrevFiltered() gets the immediately preceding sibling of each element in the // PrevFiltered() gets the immediately preceding sibling of each element in the
// Selection filtered by a selector. It returns a new Selection object // Selection filtered by a selector. It returns a new Selection object
// containing the matched elements. // containing the matched elements.
func (this *Selection) PrevFiltered(selector string) *Selection { func (this *Selection) PrevFiltered(selector string) *Selection {
return filterAndPush(this, getSiblingNodes(this.Nodes, siblingPrev), selector) return filterAndPush(this, getSiblingNodes(this.Nodes, siblingPrev, "", nil), selector)
} }
// PrevAll() gets all the preceding siblings of each element in the // PrevAll() gets all the preceding siblings of each element in the
// Selection. It returns a new Selection object containing the matched elements. // Selection. It returns a new Selection object containing the matched elements.
func (this *Selection) PrevAll() *Selection { func (this *Selection) PrevAll() *Selection {
return pushStack(this, getSiblingNodes(this.Nodes, siblingPrevAll)) return pushStack(this, getSiblingNodes(this.Nodes, siblingPrevAll, "", nil))
} }
// PrevAllFiltered() gets all the preceding siblings of each element in the // PrevAllFiltered() gets all the preceding siblings of each element in the
// Selection filtered by a selector. It returns a new Selection object // Selection filtered by a selector. It returns a new Selection object
// containing the matched elements. // containing the matched elements.
func (this *Selection) PrevAllFiltered(selector string) *Selection { func (this *Selection) PrevAllFiltered(selector string) *Selection {
return filterAndPush(this, getSiblingNodes(this.Nodes, siblingPrevAll), selector) return filterAndPush(this, getSiblingNodes(this.Nodes, siblingPrevAll, "", nil), selector)
}
func (this *Selection) PrevUntil(selector string) *Selection {
return nil
} }
// Filter and push filters the nodes based on a selector, and pushes the results // Filter and push filters the nodes based on a selector, and pushes the results
@@ -269,22 +273,50 @@ func getParentsNodes(nodes []*html.Node, stopSelector string, stopNodes []*html.
} }
// Internal implementation of sibling nodes that return a raw slice of matches. // Internal implementation of sibling nodes that return a raw slice of matches.
func getSiblingNodes(nodes []*html.Node, st siblingType) []*html.Node { func getSiblingNodes(nodes []*html.Node, st siblingType, untilSelector string,
untilNodes []*html.Node) []*html.Node {
var f func(*html.Node) bool
// If the requested siblings are ...Until(), create the test function to
// determine if the until condition is reached (returns true if it is)
if st == siblingNextUntil || st == siblingPrevUntil {
f = func(n *html.Node) bool {
if untilSelector != "" {
// Selector-based condition
sel := newSingleSelection(n, nil)
return sel.Is(untilSelector)
} else if len(untilNodes) > 0 {
// Nodes-based condition
sel := newSingleSelection(n, nil)
return sel.IsNodes(untilNodes...)
}
return false
}
}
return mapNodes(nodes, func(i int, n *html.Node) []*html.Node { return mapNodes(nodes, func(i int, n *html.Node) []*html.Node {
// Get the parent and loop through all children // Get the parent and loop through all children
if p := n.Parent; p != nil { if p := n.Parent; p != nil {
// For Prev() calls that may return more than one node (unlike Prev()),
// start at the current node, and work backwards from there. Since
// Prev() returns only one node, no advantage to find the current node
// first and loop backwards to find the previous one. Better to just loop
// from the start and stop once it is found.
if st == siblingPrevAll || st == siblingPrevUntil { if st == siblingPrevAll || st == siblingPrevUntil {
// Find the index of this node // Find the index of this node within its parent's children
for i, c := range p.Child { for i, c := range p.Child {
if c == n { if c == n {
// Looking for previous nodes, so start at index - 1 upwards // Looking for previous nodes, so start at index - 1 backwards
return getChildrenWithSiblingType(p, st, n, i-1, -1) return getChildrenWithSiblingType(p, st, n, i-1, -1, f)
} }
} }
// Should never get here.
panic(errors.New(fmt.Sprintf("Could not find node %+v in his parent's Child slice.", n))) panic(errors.New(fmt.Sprintf("Could not find node %+v in his parent's Child slice.", n)))
} else { } else {
return getChildrenWithSiblingType(p, st, n, 0, 1) // Standard loop from start moving forwards.
return getChildrenWithSiblingType(p, st, n, 0, 1, f)
} }
} }
return nil return nil
@@ -295,14 +327,14 @@ func getSiblingNodes(nodes []*html.Node, st siblingType) []*html.Node {
// based on the sibling type request. // based on the sibling type request.
func getChildrenNodes(nodes []*html.Node, st siblingType) []*html.Node { func getChildrenNodes(nodes []*html.Node, st siblingType) []*html.Node {
return mapNodes(nodes, func(i int, n *html.Node) []*html.Node { return mapNodes(nodes, func(i int, n *html.Node) []*html.Node {
return getChildrenWithSiblingType(n, st, nil, 0, 1) return getChildrenWithSiblingType(n, st, nil, 0, 1, nil)
}) })
} }
// Gets the children of the specified parent, based on the requested sibling // Gets the children of the specified parent, based on the requested sibling
// type, skipping a specified node if required. // type, skipping a specified node if required.
func getChildrenWithSiblingType(parent *html.Node, st siblingType, skipNode *html.Node, func getChildrenWithSiblingType(parent *html.Node, st siblingType, skipNode *html.Node,
startIndex int, increment int) (result []*html.Node) { startIndex int, increment int, untilFunc func(*html.Node) bool) (result []*html.Node) {
var prev *html.Node var prev *html.Node
var nFound bool var nFound bool
@@ -337,8 +369,16 @@ func getChildrenWithSiblingType(parent *html.Node, st siblingType, skipNode *htm
if c != skipNode && if c != skipNode &&
(st == siblingAll || (st == siblingAll ||
st == siblingAllIncludingNonElements || st == siblingAllIncludingNonElements ||
(st == siblingPrevAll && !nFound) || ((st == siblingPrevAll || st == siblingPrevUntil) && !nFound) ||
(st == siblingNextAll && nFound)) { ((st == siblingNextAll || st == siblingNextUntil) && nFound)) {
// If this is an ...Until() case, test before append (returns true
// if the until condition is reached)
if st == siblingNextUntil || st == siblingPrevUntil {
if untilFunc(c) {
return
}
}
result = append(result, c) result = append(result, c)
} }
} }