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 switch n := s.Get(0); n.Type { 40 case html.ElementNode, html.DoctypeNode: 41 return n.Data 42 default: 43 if n.Type >= 0 && int(n.Type) < len(nodeNames) { 44 return nodeNames[n.Type] 45 } 46 return "" 47 } 48} 49 50// OuterHtml returns the outer HTML rendering of the first item in 51// the selection - that is, the HTML including the first element's 52// tag and attributes. 53// 54// Unlike InnerHtml, this is a function and not a method on the Selection, 55// because this is not a jQuery method (in javascript-land, this is 56// a property provided by the DOM). 57func OuterHtml(s *Selection) (string, error) { 58 var buf bytes.Buffer 59 60 if s.Length() == 0 { 61 return "", nil 62 } 63 n := s.Get(0) 64 if err := html.Render(&buf, n); err != nil { 65 return "", err 66 } 67 return buf.String(), nil 68} 69 70// Loop through all container nodes to search for the target node. 71func sliceContains(container []*html.Node, contained *html.Node) bool { 72 for _, n := range container { 73 if nodeContains(n, contained) { 74 return true 75 } 76 } 77 78 return false 79} 80 81// Checks if the contained node is within the container node. 82func nodeContains(container *html.Node, contained *html.Node) bool { 83 // Check if the parent of the contained node is the container node, traversing 84 // upward until the top is reached, or the container is found. 85 for contained = contained.Parent; contained != nil; contained = contained.Parent { 86 if container == contained { 87 return true 88 } 89 } 90 return false 91} 92 93// Checks if the target node is in the slice of nodes. 94func isInSlice(slice []*html.Node, node *html.Node) bool { 95 return indexInSlice(slice, node) > -1 96} 97 98// Returns the index of the target node in the slice, or -1. 99func indexInSlice(slice []*html.Node, node *html.Node) int { 100 if node != nil { 101 for i, n := range slice { 102 if n == node { 103 return i 104 } 105 } 106 } 107 return -1 108} 109 110// Appends the new nodes to the target slice, making sure no duplicate is added. 111// There is no check to the original state of the target slice, so it may still 112// contain duplicates. The target slice is returned because append() may create 113// a new underlying array. If targetSet is nil, a local set is created with the 114// target if len(target) + len(nodes) is greater than minNodesForSet. 115func appendWithoutDuplicates(target []*html.Node, nodes []*html.Node, targetSet map[*html.Node]bool) []*html.Node { 116 // if there are not that many nodes, don't use the map, faster to just use nested loops 117 // (unless a non-nil targetSet is passed, in which case the caller knows better). 118 if targetSet == nil && len(target)+len(nodes) < minNodesForSet { 119 for _, n := range nodes { 120 if !isInSlice(target, n) { 121 target = append(target, n) 122 } 123 } 124 return target 125 } 126 127 // if a targetSet is passed, then assume it is reliable, otherwise create one 128 // and initialize it with the current target contents. 129 if targetSet == nil { 130 targetSet = make(map[*html.Node]bool, len(target)) 131 for _, n := range target { 132 targetSet[n] = true 133 } 134 } 135 for _, n := range nodes { 136 if !targetSet[n] { 137 target = append(target, n) 138 targetSet[n] = true 139 } 140 } 141 142 return target 143} 144 145// Loop through a selection, returning only those nodes that pass the predicate 146// function. 147func grep(sel *Selection, predicate func(i int, s *Selection) bool) (result []*html.Node) { 148 for i, n := range sel.Nodes { 149 if predicate(i, newSingleSelection(n, sel.document)) { 150 result = append(result, n) 151 } 152 } 153 return result 154} 155 156// Creates a new Selection object based on the specified nodes, and keeps the 157// source Selection object on the stack (linked list). 158func pushStack(fromSel *Selection, nodes []*html.Node) *Selection { 159 result := &Selection{nodes, fromSel.document, fromSel} 160 return result 161} 162