1package goquery 2 3import ( 4 "bytes" 5 6 "golang.org/x/net/html" 7) 8 9// used to determine if a set (map[*html.Node]bool) should be used 10// instead of iterating over a slice. The set uses more memory and 11// is slower than slice iteration for small N. 12const minNodesForSet = 1000 13 14var nodeNames = []string{ 15 html.ErrorNode: "#error", 16 html.TextNode: "#text", 17 html.DocumentNode: "#document", 18 html.CommentNode: "#comment", 19} 20 21// NodeName returns the node name of the first element in the selection. 22// It tries to behave in a similar way as the DOM's nodeName property 23// (https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName). 24// 25// Go's net/html package defines the following node types, listed with 26// the corresponding returned value from this function: 27// 28// ErrorNode : #error 29// TextNode : #text 30// DocumentNode : #document 31// ElementNode : the element's tag name 32// CommentNode : #comment 33// DoctypeNode : the name of the document type 34// 35func NodeName(s *Selection) string { 36 if s.Length() == 0 { 37 return "" 38 } 39 return nodeName(s.Get(0)) 40} 41 42// nodeName returns the node name of the given html node. 43// See NodeName for additional details on behaviour. 44func nodeName(node *html.Node) string { 45 if node == nil { 46 return "" 47 } 48 49 switch node.Type { 50 case html.ElementNode, html.DoctypeNode: 51 return node.Data 52 default: 53 if node.Type >= 0 && int(node.Type) < len(nodeNames) { 54 return nodeNames[node.Type] 55 } 56 return "" 57 } 58} 59 60// OuterHtml returns the outer HTML rendering of the first item in 61// the selection - that is, the HTML including the first element's 62// tag and attributes. 63// 64// Unlike InnerHtml, this is a function and not a method on the Selection, 65// because this is not a jQuery method (in javascript-land, this is 66// a property provided by the DOM). 67func OuterHtml(s *Selection) (string, error) { 68 var buf bytes.Buffer 69 70 if s.Length() == 0 { 71 return "", nil 72 } 73 n := s.Get(0) 74 if err := html.Render(&buf, n); err != nil { 75 return "", err 76 } 77 return buf.String(), nil 78} 79 80// Loop through all container nodes to search for the target node. 81func sliceContains(container []*html.Node, contained *html.Node) bool { 82 for _, n := range container { 83 if nodeContains(n, contained) { 84 return true 85 } 86 } 87 88 return false 89} 90 91// Checks if the contained node is within the container node. 92func nodeContains(container *html.Node, contained *html.Node) bool { 93 // Check if the parent of the contained node is the container node, traversing 94 // upward until the top is reached, or the container is found. 95 for contained = contained.Parent; contained != nil; contained = contained.Parent { 96 if container == contained { 97 return true 98 } 99 } 100 return false 101} 102 103// Checks if the target node is in the slice of nodes. 104func isInSlice(slice []*html.Node, node *html.Node) bool { 105 return indexInSlice(slice, node) > -1 106} 107 108// Returns the index of the target node in the slice, or -1. 109func indexInSlice(slice []*html.Node, node *html.Node) int { 110 if node != nil { 111 for i, n := range slice { 112 if n == node { 113 return i 114 } 115 } 116 } 117 return -1 118} 119 120// Appends the new nodes to the target slice, making sure no duplicate is added. 121// There is no check to the original state of the target slice, so it may still 122// contain duplicates. The target slice is returned because append() may create 123// a new underlying array. If targetSet is nil, a local set is created with the 124// target if len(target) + len(nodes) is greater than minNodesForSet. 125func appendWithoutDuplicates(target []*html.Node, nodes []*html.Node, targetSet map[*html.Node]bool) []*html.Node { 126 // if there are not that many nodes, don't use the map, faster to just use nested loops 127 // (unless a non-nil targetSet is passed, in which case the caller knows better). 128 if targetSet == nil && len(target)+len(nodes) < minNodesForSet { 129 for _, n := range nodes { 130 if !isInSlice(target, n) { 131 target = append(target, n) 132 } 133 } 134 return target 135 } 136 137 // if a targetSet is passed, then assume it is reliable, otherwise create one 138 // and initialize it with the current target contents. 139 if targetSet == nil { 140 targetSet = make(map[*html.Node]bool, len(target)) 141 for _, n := range target { 142 targetSet[n] = true 143 } 144 } 145 for _, n := range nodes { 146 if !targetSet[n] { 147 target = append(target, n) 148 targetSet[n] = true 149 } 150 } 151 152 return target 153} 154 155// Loop through a selection, returning only those nodes that pass the predicate 156// function. 157func grep(sel *Selection, predicate func(i int, s *Selection) bool) (result []*html.Node) { 158 for i, n := range sel.Nodes { 159 if predicate(i, newSingleSelection(n, sel.document)) { 160 result = append(result, n) 161 } 162 } 163 return result 164} 165 166// Creates a new Selection object based on the specified nodes, and keeps the 167// source Selection object on the stack (linked list). 168func pushStack(fromSel *Selection, nodes []*html.Node) *Selection { 169 result := &Selection{nodes, fromSel.document, fromSel} 170 return result 171} 172