github.com/godaddy-x/freego@v1.0.156/goquery/utilities.go (about) 1 package goquery 2 3 import ( 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. 12 const minNodesForSet = 1000 13 14 var 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 // 35 func 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). 57 func 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. 71 func 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. 82 func 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. 94 func 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. 99 func 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. 115 func 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. 147 func 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). 158 func pushStack(fromSel *Selection, nodes []*html.Node) *Selection { 159 result := &Selection{nodes, fromSel.document, fromSel} 160 return result 161 }