p1: /v2/scrape, /v1/fetch, auth, CLI, contract tests
Single Fetcher.Fetch on the Guard that holds DenyPrefixes. skipTlsVerification is ignored. No 502. keys add writes the secret only to --out. Release binary has no -test-allow-prefix.
This commit is contained in:
+22
@@ -0,0 +1,22 @@
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work.sum
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Johannes Kaufmann
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
# dom
|
||||
|
||||
[](https://pkg.go.dev/github.com/JohannesKaufmann/dom)
|
||||
|
||||
Helper functions for "net/html" that make it easier to interact with `*html.Node`.
|
||||
|
||||
🚀 [Getting Started](#getting-started) - 📚 [Documentation](#documentation) - 🧑💻 [Examples](/examples/)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get -u github.com/JohannesKaufmann/dom
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> This "dom" libary was developed for the needs of the [html-to-markdown](https://github.com/JohannesKaufmann/html-to-markdown) library.
|
||||
> That beeing said, please submit any functions that you need.
|
||||
|
||||
## Getting Started
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/JohannesKaufmann/dom"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
func main() {
|
||||
input := `
|
||||
<ul>
|
||||
<li><a href="github.com/JohannesKaufmann/dom">dom</a></li>
|
||||
<li><a href="github.com/JohannesKaufmann/html-to-markdown">html-to-markdown</a></li>
|
||||
</ul>
|
||||
`
|
||||
|
||||
doc, err := html.Parse(strings.NewReader(input))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// - - - //
|
||||
|
||||
firstLink := dom.FindFirstNode(doc, func(node *html.Node) bool {
|
||||
return dom.NodeName(node) == "a"
|
||||
})
|
||||
|
||||
fmt.Println("href:", dom.GetAttributeOr(firstLink, "href", ""))
|
||||
}
|
||||
```
|
||||
|
||||
## Node vs Element
|
||||
|
||||
The naming scheme in this library is:
|
||||
|
||||
- "Node" means `*html.Node{}`
|
||||
- This means _any_ node in the tree of nodes.
|
||||
- "Element" means `*html.Node{Type: html.ElementNode}`
|
||||
- This means _only_ nodes with the type of `ElementNode`. For example `<p>`, `<span>`, `<a>`, ... but not `#text`, `<!--comment-->`, ...
|
||||
|
||||
For most functions, there are two versions. For example:
|
||||
|
||||
- `FirstChildNode()` and `FirstChildElement()`
|
||||
- `AllChildNodes()` and `AllChildElements()`
|
||||
- ...
|
||||
|
||||
## Documentation
|
||||
|
||||
[](https://pkg.go.dev/github.com/JohannesKaufmann/dom)
|
||||
|
||||
### Attributes & Content
|
||||
|
||||
You can get the attributes of a node using `GetAttribute`, `GetAttributeOr` or the more specialized `GetClasses` that returns a slice of strings.
|
||||
|
||||
For matching nodes, `HasID` and `HasClass` can be used.
|
||||
|
||||
If you want to collect the #text of all the child nodes, you can call `CollectText`.
|
||||
|
||||
```go
|
||||
name := dom.NodeName(node)
|
||||
// "h2"
|
||||
|
||||
href := dom.GetAttributeOr(node, "href", "")
|
||||
// "github.com"
|
||||
|
||||
isHeading := dom.HasClass(node, "repo__name")
|
||||
// `true`
|
||||
|
||||
content := dom.CollectText(node)
|
||||
// "Lorem ipsum"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Children & Siblings
|
||||
|
||||
You can already use `node.FirstChild` to get the first child _node_. For the convenience we added `FirstChildNode()` and `FirstChildElement()` which returns `*html.Node`.
|
||||
|
||||
To get all direct children, use `AllChildNodes` and `AllChildElements` which returns `[]*html.Node`.
|
||||
|
||||
- `PrevSiblingNode` and `PrevSiblingElement`
|
||||
|
||||
- `NextSiblingNode` and `NextSiblingElement`
|
||||
|
||||
### Find Nodes
|
||||
|
||||
Searching for nodes deep in the tree is made easier with:
|
||||
|
||||
```go
|
||||
firstParagraph := dom.FindFirstNode(doc, func(node *html.Node) bool {
|
||||
return dom.NodeName(node) == "p"
|
||||
})
|
||||
// *html.Node
|
||||
|
||||
|
||||
allParagraphs := dom.FindAllNodes(doc, func(node *html.Node) bool {
|
||||
return dom.NodeName(node) == "p"
|
||||
})
|
||||
// []*html.Node
|
||||
```
|
||||
|
||||
- 🧑💻 [Example code, find](/examples/find/main.go)
|
||||
- 🧑💻 [Example code, selectors](/examples/selectors/main.go)
|
||||
|
||||
---
|
||||
|
||||
### Get next/previous neighbors
|
||||
|
||||
What is special about this? The order!
|
||||
|
||||
If you are somewhere in the DOM, you can call `GetNextNeighborNode` to get the next node, even if it is _further up_ the tree. The order is the same as you would see the elements in the DOM.
|
||||
|
||||
```go
|
||||
node := startNode
|
||||
for node != nil {
|
||||
fmt.Println(dom.NodeName(node))
|
||||
|
||||
node = dom.GetNextNeighborNode(node)
|
||||
}
|
||||
```
|
||||
|
||||
If we start the `for` loop at the `<button>` and repeatedly call `GetNextNeighborNode` this would be the _order_ that the nodes are _visited_.
|
||||
|
||||
```text
|
||||
#document
|
||||
├─html
|
||||
│ ├─head
|
||||
│ ├─body
|
||||
│ │ ├─nav
|
||||
│ │ │ ├─p
|
||||
│ │ │ │ ├─#text "up"
|
||||
│ │ ├─main
|
||||
│ │ │ ├─button *️⃣
|
||||
│ │ │ │ ├─span 0️⃣
|
||||
│ │ │ │ │ ├─#text "start" 1️⃣
|
||||
│ │ │ ├─div 2️⃣
|
||||
│ │ │ │ ├─h3 3️⃣
|
||||
│ │ │ │ │ ├─#text "heading" 4️⃣
|
||||
│ │ │ │ ├─p 5️⃣
|
||||
│ │ │ │ │ ├─#text "description" 6️⃣
|
||||
│ │ ├─footer 7️⃣
|
||||
│ │ │ ├─p 8️⃣
|
||||
│ │ │ │ ├─#text "down" 9️⃣
|
||||
```
|
||||
|
||||
If you only want to visit the ElementNode's (and skip the `#text` Nodes) you can use `GetNextNeighborElement` instead.
|
||||
|
||||
If you want to skip the children you can use `GetNextNeighborNodeExcludingOwnChild`. In the example above, when starting at the `<button>` the next node would be the `<div>`.
|
||||
|
||||
The same functions also exist for the previous nodes, e.g. `GetPrevNeighborNode`.
|
||||
|
||||
- 🧑💻 [Example code, next basics](/examples/next_basics/main.go)
|
||||
- 🧑💻 [Example code, next inside a loop](/examples/next_loop/main.go)
|
||||
|
||||
---
|
||||
|
||||
### Remove & Replace Node
|
||||
|
||||
```go
|
||||
if dom.HasClass(node, "lang__old") {
|
||||
newNode := &html.Node{
|
||||
Type: html.TextNode,
|
||||
Data: "🪦",
|
||||
}
|
||||
dom.ReplaceNode(node, newNode)
|
||||
}
|
||||
|
||||
|
||||
for _, node := range emptyTextNodes {
|
||||
dom.RemoveNode(node)
|
||||
}
|
||||
```
|
||||
|
||||
- 🧑💻 [Example code, remove and replace](/examples/remove_replace/main.go)
|
||||
|
||||
### Unwrap Node
|
||||
|
||||
```text
|
||||
#document
|
||||
├─html
|
||||
│ ├─head
|
||||
│ ├─body
|
||||
│ │ ├─article *️⃣
|
||||
│ │ │ ├─h3
|
||||
│ │ │ │ ├─#text "Heading"
|
||||
│ │ │ ├─p
|
||||
│ │ │ │ ├─#text "short description"
|
||||
```
|
||||
|
||||
If we take the input above and run `UnwrapNode(articleNode)` we can "unwrap" the `<article>`. That means removing the `<article>` while _keeping_ the children (`<h3>` and `<p>`).
|
||||
|
||||
```text
|
||||
#document
|
||||
├─html
|
||||
│ ├─head
|
||||
│ ├─body
|
||||
│ │ ├─h3
|
||||
│ │ │ ├─#text "Heading"
|
||||
│ │ ├─p
|
||||
│ │ │ ├─#text "short description"
|
||||
```
|
||||
|
||||
For the reverse you can use `WrapNode(existingNode, newNode)`.
|
||||
|
||||
---
|
||||
|
||||
### RenderRepresentation
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/JohannesKaufmann/dom"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
func main() {
|
||||
input := `<a href="/about">Read More</a>`
|
||||
|
||||
doc, err := html.Parse(strings.NewReader(input))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println(dom.RenderRepresentation(doc))
|
||||
}
|
||||
```
|
||||
|
||||
The tree representation helps to visualize the tree-structure of the DOM.
|
||||
And the `#text` nodes stand out.
|
||||
|
||||
> [!TIP]
|
||||
> This function could be useful for debugging & testcases.
|
||||
> For example in [neighbors_test.go](/neighbors_test.go)
|
||||
|
||||
```text
|
||||
#document
|
||||
├─html
|
||||
│ ├─head
|
||||
│ ├─body
|
||||
│ │ ├─a (href=/about)
|
||||
│ │ │ ├─#text "Read More"
|
||||
```
|
||||
|
||||
While the normal "net/html" [`Render()`](https://pkg.go.dev/golang.org/x/net/html#Render) function would have produced this:
|
||||
|
||||
```
|
||||
<html><head></head><body><a href="/about">Read More</a></body></html>
|
||||
```
|
||||
|
||||
- 🧑💻 [Example code, dom representation](/examples/dom_representation/main.go)
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package dom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
func GetAttribute(node *html.Node, key string) (string, bool) {
|
||||
for _, attr := range node.Attr {
|
||||
if attr.Key == key {
|
||||
return attr.Val, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
func GetAttributeOr(node *html.Node, key string, fallback string) string {
|
||||
for _, attr := range node.Attr {
|
||||
if attr.Key == key {
|
||||
return attr.Val
|
||||
}
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func GetClasses(node *html.Node) []string {
|
||||
val, found := GetAttribute(node, "class")
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
return strings.Fields(val)
|
||||
}
|
||||
|
||||
func HasID(node *html.Node, expectedID string) bool {
|
||||
val, found := GetAttribute(node, "id")
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
return strings.TrimSpace(val) == expectedID
|
||||
}
|
||||
func HasClass(node *html.Node, expectedClass string) bool {
|
||||
classes := GetClasses(node)
|
||||
for _, class := range classes {
|
||||
if class == expectedClass {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// - - - - //
|
||||
|
||||
func collectText(n *html.Node, buf *bytes.Buffer) {
|
||||
if n.Type == html.TextNode {
|
||||
buf.WriteString(n.Data)
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
collectText(c, buf)
|
||||
}
|
||||
}
|
||||
|
||||
func CollectText(node *html.Node) string {
|
||||
var buf bytes.Buffer
|
||||
collectText(node, &buf)
|
||||
return buf.String()
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package dom
|
||||
|
||||
import "golang.org/x/net/html"
|
||||
|
||||
func RemoveNode(node *html.Node) {
|
||||
if node == nil || node.Parent == nil {
|
||||
return
|
||||
}
|
||||
|
||||
node.Parent.RemoveChild(node)
|
||||
}
|
||||
|
||||
func ReplaceNode(node, newNode *html.Node) {
|
||||
if node.Parent == nil || node == newNode {
|
||||
return
|
||||
}
|
||||
|
||||
node.Parent.InsertBefore(newNode, node)
|
||||
node.Parent.RemoveChild(node)
|
||||
}
|
||||
|
||||
func UnwrapNode(node *html.Node) {
|
||||
if node == nil || node.Parent == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// In each iteration, we once again grab the first child, since
|
||||
// the previous first child was just removed.
|
||||
for child := node.FirstChild; child != nil; child = node.FirstChild {
|
||||
node.RemoveChild(child)
|
||||
node.Parent.InsertBefore(child, node)
|
||||
}
|
||||
|
||||
node.Parent.RemoveChild(node)
|
||||
}
|
||||
|
||||
// WrapNode wraps the newNode around the existingNode.
|
||||
func WrapNode(existingNode, newNode *html.Node) *html.Node {
|
||||
if existingNode == nil || existingNode.Parent == nil {
|
||||
return existingNode
|
||||
}
|
||||
|
||||
existingNode.Parent.InsertBefore(newNode, existingNode)
|
||||
existingNode.Parent.RemoveChild(existingNode)
|
||||
|
||||
newNode.AppendChild(existingNode)
|
||||
|
||||
return newNode
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
// dom makes it easier to interact with the html document.
|
||||
//
|
||||
// Node = return all the nodes
|
||||
// Element = return all the nodes that are of type Element. This e.g. excludes #text nodes.
|
||||
package dom
|
||||
|
||||
import "golang.org/x/net/html"
|
||||
|
||||
// AllNodes recursively gets all the nodes in the tree.
|
||||
func AllNodes(startNode *html.Node) (allNodes []*html.Node) {
|
||||
var finder func(*html.Node)
|
||||
finder = func(node *html.Node) {
|
||||
allNodes = append(allNodes, node)
|
||||
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
finder(child)
|
||||
}
|
||||
}
|
||||
|
||||
finder(startNode)
|
||||
|
||||
return allNodes
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - //
|
||||
|
||||
func AllChildNodes(node *html.Node) (children []*html.Node) {
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
children = append(children, child)
|
||||
}
|
||||
return children
|
||||
}
|
||||
|
||||
// AllChildElements is similar to AllChildNodes but only returns
|
||||
// nodes of type `ElementNode`.
|
||||
func AllChildElements(node *html.Node) (children []*html.Node) {
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
if child.Type == html.ElementNode {
|
||||
children = append(children, child)
|
||||
}
|
||||
}
|
||||
return children
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - //
|
||||
|
||||
func FirstChildNode(node *html.Node) *html.Node {
|
||||
return node.FirstChild
|
||||
}
|
||||
func FirstChildElement(node *html.Node) *html.Node {
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
if child.Type == html.ElementNode {
|
||||
return child
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - //
|
||||
|
||||
func PrevSiblingNode(node *html.Node) *html.Node {
|
||||
return node.PrevSibling
|
||||
}
|
||||
func PrevSiblingElement(node *html.Node) *html.Node {
|
||||
for sibling := node.PrevSibling; sibling != nil; sibling = sibling.PrevSibling {
|
||||
if sibling.Type == html.ElementNode {
|
||||
return sibling
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - //
|
||||
|
||||
func NextSiblingNode(node *html.Node) *html.Node {
|
||||
return node.NextSibling
|
||||
}
|
||||
|
||||
// NextSiblingElement returns the element immediately following the passed-in node or nil.
|
||||
// In contrast to `node.NextSibling` this only returns the next `ElementNode`.
|
||||
func NextSiblingElement(node *html.Node) *html.Node {
|
||||
for sibling := node.NextSibling; sibling != nil; sibling = sibling.NextSibling {
|
||||
if sibling.Type == html.ElementNode {
|
||||
return sibling
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package dom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
func writePipeChar(buf *bytes.Buffer, index int) {
|
||||
if index == 0 {
|
||||
return
|
||||
}
|
||||
buf.WriteString(strings.Repeat("│ ", index-1))
|
||||
buf.WriteString("├─")
|
||||
}
|
||||
func writeNode(buf *bytes.Buffer, node *html.Node) {
|
||||
name := NodeName(node)
|
||||
buf.WriteString(name)
|
||||
|
||||
if len(node.Attr) != 0 {
|
||||
buf.WriteString(" (")
|
||||
}
|
||||
for i, attr := range node.Attr {
|
||||
buf.WriteString(fmt.Sprintf("%s=%q", attr.Key, attr.Val))
|
||||
|
||||
if i < len(node.Attr)-1 {
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
}
|
||||
if len(node.Attr) != 0 {
|
||||
buf.WriteString(")")
|
||||
}
|
||||
|
||||
if name == "#text" {
|
||||
buf.WriteString(fmt.Sprintf(" %q", node.Data))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// RenderRepresentation is useful for debugging.
|
||||
// It renders out the *structure* of the dom.
|
||||
func RenderRepresentation(startNode *html.Node) string {
|
||||
var buf bytes.Buffer
|
||||
var finder func(*html.Node, int)
|
||||
finder = func(node *html.Node, index int) {
|
||||
writePipeChar(&buf, index)
|
||||
writeNode(&buf, node)
|
||||
buf.WriteRune('\n')
|
||||
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
finder(child, index+1)
|
||||
}
|
||||
}
|
||||
|
||||
if startNode.Parent == nil {
|
||||
finder(startNode, 0)
|
||||
} else {
|
||||
finder(startNode, 1)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(buf.String())
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package dom
|
||||
|
||||
import "golang.org/x/net/html"
|
||||
|
||||
func ContainsNode(startNode *html.Node, matchFn func(node *html.Node) bool) bool {
|
||||
return FindFirstNode(startNode, matchFn) != nil
|
||||
}
|
||||
|
||||
func FindFirstNode(startNode *html.Node, matchFn func(node *html.Node) bool) *html.Node {
|
||||
nextFunc := UNSTABLE_initGetNeighbor(
|
||||
FirstChildNode,
|
||||
NextSiblingNode,
|
||||
func(node *html.Node) bool {
|
||||
// We should not get higher up than the startNode...
|
||||
return node == startNode
|
||||
},
|
||||
)
|
||||
|
||||
child := startNode.FirstChild
|
||||
for child != nil {
|
||||
if matchFn(child) {
|
||||
return child
|
||||
}
|
||||
|
||||
child = nextFunc(child)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func FindAllNodes(startNode *html.Node, matchFn func(node *html.Node) bool) (foundNodes []*html.Node) {
|
||||
nextFunc := UNSTABLE_initGetNeighbor(
|
||||
FirstChildNode,
|
||||
NextSiblingNode,
|
||||
func(node *html.Node) bool {
|
||||
// We should not get higher up than the startNode...
|
||||
return node == startNode
|
||||
},
|
||||
)
|
||||
|
||||
child := startNode.FirstChild
|
||||
for child != nil {
|
||||
if matchFn(child) {
|
||||
foundNodes = append(foundNodes, child)
|
||||
}
|
||||
|
||||
child = nextFunc(child)
|
||||
}
|
||||
return foundNodes
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package dom
|
||||
|
||||
import "golang.org/x/net/html"
|
||||
|
||||
// Warning: It is not meant to be called directly and may change signature from release to release!
|
||||
func UNSTABLE_initGetNeighbor(
|
||||
firstChildFunc func(node *html.Node) *html.Node,
|
||||
prevNextFunc func(node *html.Node) *html.Node,
|
||||
goUpUntilFunc func(node *html.Node) bool,
|
||||
) func(*html.Node) *html.Node {
|
||||
|
||||
return func(node *html.Node) *html.Node {
|
||||
// First look at the children
|
||||
if child := firstChildFunc(node); child != nil {
|
||||
return child
|
||||
}
|
||||
|
||||
// Otherwise my prev/next sibling
|
||||
if sibling := prevNextFunc(node); sibling != nil {
|
||||
return sibling
|
||||
}
|
||||
|
||||
for {
|
||||
// Finally, continously go upwards until we find an element with a sibling
|
||||
node = node.Parent
|
||||
if node == nil {
|
||||
// We reached the top
|
||||
return nil
|
||||
}
|
||||
|
||||
if goUpUntilFunc(node) {
|
||||
// Don't go too far up...
|
||||
return nil
|
||||
}
|
||||
|
||||
sibling := prevNextFunc(node)
|
||||
if sibling != nil {
|
||||
return sibling
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - //
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - //
|
||||
|
||||
var goUpForever = func(node *html.Node) bool { return false }
|
||||
var skipFirstChild = func(node *html.Node) *html.Node { return nil }
|
||||
|
||||
func GetPrevNeighborNode(node *html.Node) *html.Node {
|
||||
return UNSTABLE_initGetNeighbor(
|
||||
FirstChildNode,
|
||||
PrevSiblingNode,
|
||||
goUpForever,
|
||||
)(node)
|
||||
}
|
||||
func GetPrevNeighborElement(node *html.Node) *html.Node {
|
||||
return UNSTABLE_initGetNeighbor(
|
||||
FirstChildElement,
|
||||
PrevSiblingElement,
|
||||
goUpForever,
|
||||
)(node)
|
||||
}
|
||||
func GetPrevNeighborNodeExcludingOwnChild(node *html.Node) *html.Node {
|
||||
return UNSTABLE_initGetNeighbor(
|
||||
skipFirstChild,
|
||||
PrevSiblingNode,
|
||||
goUpForever,
|
||||
)(node)
|
||||
}
|
||||
func GetPrevNeighborElementExcludingOwnChild(node *html.Node) *html.Node {
|
||||
return UNSTABLE_initGetNeighbor(
|
||||
skipFirstChild,
|
||||
PrevSiblingElement,
|
||||
goUpForever,
|
||||
)(node)
|
||||
}
|
||||
|
||||
// - - - - - - - - //
|
||||
|
||||
func GetNextNeighborNode(node *html.Node) *html.Node {
|
||||
return UNSTABLE_initGetNeighbor(
|
||||
FirstChildNode,
|
||||
NextSiblingNode,
|
||||
goUpForever,
|
||||
)(node)
|
||||
}
|
||||
func GetNextNeighborElement(node *html.Node) *html.Node {
|
||||
return UNSTABLE_initGetNeighbor(
|
||||
FirstChildElement,
|
||||
NextSiblingElement,
|
||||
goUpForever,
|
||||
)(node)
|
||||
}
|
||||
func GetNextNeighborNodeExcludingOwnChild(node *html.Node) *html.Node {
|
||||
return UNSTABLE_initGetNeighbor(
|
||||
skipFirstChild,
|
||||
NextSiblingNode,
|
||||
goUpForever,
|
||||
)(node)
|
||||
}
|
||||
func GetNextNeighborElementExcludingOwnChild(node *html.Node) *html.Node {
|
||||
return UNSTABLE_initGetNeighbor(
|
||||
skipFirstChild,
|
||||
NextSiblingElement,
|
||||
goUpForever,
|
||||
)(node)
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
package dom
|
||||
|
||||
import "golang.org/x/net/html"
|
||||
|
||||
// In order to stay consistent with v1 of the library, this follows
|
||||
// the naming scheme of goquery.
|
||||
// E.g. "#text", "div", ...
|
||||
func NodeName(node *html.Node) string {
|
||||
if node == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch node.Type {
|
||||
case html.ErrorNode:
|
||||
return "#error"
|
||||
case html.TextNode:
|
||||
return "#text"
|
||||
case html.DocumentNode:
|
||||
return "#document"
|
||||
case html.CommentNode:
|
||||
return "#comment"
|
||||
|
||||
case html.DoctypeNode:
|
||||
// E.g. for `<!DOCTYPE html>` it would be "html"
|
||||
return node.Data
|
||||
case html.ElementNode:
|
||||
// E.g. "div" or "p"
|
||||
return node.Data
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func NameIsInlineNode(name string) bool {
|
||||
switch name {
|
||||
case
|
||||
"#text",
|
||||
"a",
|
||||
"abbr",
|
||||
"acronym",
|
||||
"audio",
|
||||
"b",
|
||||
"bdi",
|
||||
"bdo",
|
||||
"big",
|
||||
"br",
|
||||
"button",
|
||||
"canvas",
|
||||
"cite",
|
||||
"code",
|
||||
"data",
|
||||
"datalist",
|
||||
"del",
|
||||
"dfn",
|
||||
"em",
|
||||
"embed",
|
||||
"i",
|
||||
"iframe",
|
||||
"img",
|
||||
"input",
|
||||
"ins",
|
||||
"kbd",
|
||||
"label",
|
||||
"map",
|
||||
"mark",
|
||||
"meter",
|
||||
"noscript",
|
||||
"object",
|
||||
"output",
|
||||
"picture",
|
||||
"progress",
|
||||
"q",
|
||||
"ruby",
|
||||
"s",
|
||||
"samp",
|
||||
"script",
|
||||
"select",
|
||||
"slot",
|
||||
"small",
|
||||
"span",
|
||||
"strong",
|
||||
"sub",
|
||||
"sup",
|
||||
"svg",
|
||||
"template",
|
||||
"textarea",
|
||||
"time",
|
||||
"u",
|
||||
"tt",
|
||||
"var",
|
||||
"video",
|
||||
"wbr":
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func NameIsBlockNode(name string) bool {
|
||||
switch name {
|
||||
case
|
||||
"address",
|
||||
"article",
|
||||
"aside",
|
||||
"blockquote",
|
||||
"details",
|
||||
"dialog",
|
||||
"dd",
|
||||
"div",
|
||||
"dl",
|
||||
"dt",
|
||||
"fieldset",
|
||||
"figcaption",
|
||||
"figure",
|
||||
"footer",
|
||||
"form",
|
||||
"h1", "h2", "h3", "h4", "h5", "h6",
|
||||
"header",
|
||||
"hgroup",
|
||||
"hr",
|
||||
"li",
|
||||
"main",
|
||||
"nav",
|
||||
"ol",
|
||||
"p",
|
||||
"pre",
|
||||
"section",
|
||||
"table",
|
||||
"ul":
|
||||
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func NameIsHeading(name string) bool {
|
||||
switch name {
|
||||
case
|
||||
"h1", "h2", "h3", "h4", "h5", "h6":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user