github.com/godaddy-x/freego@v1.0.156/goquery/property.go (about) 1 package goquery 2 3 import ( 4 "bytes" 5 "regexp" 6 "strings" 7 8 "golang.org/x/net/html" 9 ) 10 11 var rxClassTrim = regexp.MustCompile("[\t\r\n]") 12 13 // Attr gets the specified attribute's value for the first element in the 14 // Selection. To get the value for each element individually, use a looping 15 // construct such as Each or Map method. 16 func (s *Selection) Attr(attrName string) (val string, exists bool) { 17 if len(s.Nodes) == 0 { 18 return 19 } 20 return getAttributeValue(attrName, s.Nodes[0]) 21 } 22 23 // AttrOr works like Attr but returns default value if attribute is not present. 24 func (s *Selection) AttrOr(attrName, defaultValue string) string { 25 if len(s.Nodes) == 0 { 26 return defaultValue 27 } 28 29 val, exists := getAttributeValue(attrName, s.Nodes[0]) 30 if !exists { 31 return defaultValue 32 } 33 34 return val 35 } 36 37 // RemoveAttr removes the named attribute from each element in the set of matched elements. 38 func (s *Selection) RemoveAttr(attrName string) *Selection { 39 for _, n := range s.Nodes { 40 removeAttr(n, attrName) 41 } 42 43 return s 44 } 45 46 // SetAttr sets the given attribute on each element in the set of matched elements. 47 func (s *Selection) SetAttr(attrName, val string) *Selection { 48 for _, n := range s.Nodes { 49 attr := getAttributePtr(attrName, n) 50 if attr == nil { 51 n.Attr = append(n.Attr, html.Attribute{Key: attrName, Val: val}) 52 } else { 53 attr.Val = val 54 } 55 } 56 57 return s 58 } 59 60 // Text gets the combined text contents of each element in the set of matched 61 // elements, including their descendants. 62 func (s *Selection) Text() string { 63 var buf bytes.Buffer 64 65 // Slightly optimized vs calling Each: no single selection object created 66 var f func(*html.Node) 67 f = func(n *html.Node) { 68 if n.Type == html.TextNode { 69 // Keep newlines and spaces, like jQuery 70 buf.WriteString(n.Data) 71 } 72 if n.FirstChild != nil { 73 for c := n.FirstChild; c != nil; c = c.NextSibling { 74 f(c) 75 } 76 } 77 } 78 for _, n := range s.Nodes { 79 f(n) 80 } 81 82 return buf.String() 83 } 84 85 // Size is an alias for Length. 86 func (s *Selection) Size() int { 87 return s.Length() 88 } 89 90 // Length returns the number of elements in the Selection object. 91 func (s *Selection) Length() int { 92 return len(s.Nodes) 93 } 94 95 // Html gets the HTML contents of the first element in the set of matched 96 // elements. It includes text and comment nodes. 97 func (s *Selection) Html() (ret string, e error) { 98 // Since there is no .innerHtml, the HTML content must be re-created from 99 // the nodes using html.Render. 100 var buf bytes.Buffer 101 102 if len(s.Nodes) > 0 { 103 for c := s.Nodes[0].FirstChild; c != nil; c = c.NextSibling { 104 e = html.Render(&buf, c) 105 if e != nil { 106 return 107 } 108 } 109 ret = buf.String() 110 } 111 112 return 113 } 114 115 // AddClass adds the given class(es) to each element in the set of matched elements. 116 // Multiple class names can be specified, separated by a space or via multiple arguments. 117 func (s *Selection) AddClass(class ...string) *Selection { 118 classStr := strings.TrimSpace(strings.Join(class, " ")) 119 120 if classStr == "" { 121 return s 122 } 123 124 tcls := getClassesSlice(classStr) 125 for _, n := range s.Nodes { 126 curClasses, attr := getClassesAndAttr(n, true) 127 for _, newClass := range tcls { 128 if !strings.Contains(curClasses, " "+newClass+" ") { 129 curClasses += newClass + " " 130 } 131 } 132 133 setClasses(n, attr, curClasses) 134 } 135 136 return s 137 } 138 139 // HasClass determines whether any of the matched elements are assigned the 140 // given class. 141 func (s *Selection) HasClass(class string) bool { 142 class = " " + class + " " 143 for _, n := range s.Nodes { 144 classes, _ := getClassesAndAttr(n, false) 145 if strings.Contains(classes, class) { 146 return true 147 } 148 } 149 return false 150 } 151 152 // RemoveClass removes the given class(es) from each element in the set of matched elements. 153 // Multiple class names can be specified, separated by a space or via multiple arguments. 154 // If no class name is provided, all classes are removed. 155 func (s *Selection) RemoveClass(class ...string) *Selection { 156 var rclasses []string 157 158 classStr := strings.TrimSpace(strings.Join(class, " ")) 159 remove := classStr == "" 160 161 if !remove { 162 rclasses = getClassesSlice(classStr) 163 } 164 165 for _, n := range s.Nodes { 166 if remove { 167 removeAttr(n, "class") 168 } else { 169 classes, attr := getClassesAndAttr(n, true) 170 for _, rcl := range rclasses { 171 classes = strings.Replace(classes, " "+rcl+" ", " ", -1) 172 } 173 174 setClasses(n, attr, classes) 175 } 176 } 177 178 return s 179 } 180 181 // ToggleClass adds or removes the given class(es) for each element in the set of matched elements. 182 // Multiple class names can be specified, separated by a space or via multiple arguments. 183 func (s *Selection) ToggleClass(class ...string) *Selection { 184 classStr := strings.TrimSpace(strings.Join(class, " ")) 185 186 if classStr == "" { 187 return s 188 } 189 190 tcls := getClassesSlice(classStr) 191 192 for _, n := range s.Nodes { 193 classes, attr := getClassesAndAttr(n, true) 194 for _, tcl := range tcls { 195 if strings.Contains(classes, " "+tcl+" ") { 196 classes = strings.Replace(classes, " "+tcl+" ", " ", -1) 197 } else { 198 classes += tcl + " " 199 } 200 } 201 202 setClasses(n, attr, classes) 203 } 204 205 return s 206 } 207 208 func getAttributePtr(attrName string, n *html.Node) *html.Attribute { 209 if n == nil { 210 return nil 211 } 212 213 for i, a := range n.Attr { 214 if a.Key == attrName { 215 return &n.Attr[i] 216 } 217 } 218 return nil 219 } 220 221 // Private function to get the specified attribute's value from a node. 222 func getAttributeValue(attrName string, n *html.Node) (val string, exists bool) { 223 if a := getAttributePtr(attrName, n); a != nil { 224 val = a.Val 225 exists = true 226 } 227 return 228 } 229 230 // Get and normalize the "class" attribute from the node. 231 func getClassesAndAttr(n *html.Node, create bool) (classes string, attr *html.Attribute) { 232 // Applies only to element nodes 233 if n.Type == html.ElementNode { 234 attr = getAttributePtr("class", n) 235 if attr == nil && create { 236 n.Attr = append(n.Attr, html.Attribute{ 237 Key: "class", 238 Val: "", 239 }) 240 attr = &n.Attr[len(n.Attr)-1] 241 } 242 } 243 244 if attr == nil { 245 classes = " " 246 } else { 247 classes = rxClassTrim.ReplaceAllString(" "+attr.Val+" ", " ") 248 } 249 250 return 251 } 252 253 func getClassesSlice(classes string) []string { 254 return strings.Split(rxClassTrim.ReplaceAllString(" "+classes+" ", " "), " ") 255 } 256 257 func removeAttr(n *html.Node, attrName string) { 258 for i, a := range n.Attr { 259 if a.Key == attrName { 260 n.Attr[i], n.Attr[len(n.Attr)-1], n.Attr = 261 n.Attr[len(n.Attr)-1], html.Attribute{}, n.Attr[:len(n.Attr)-1] 262 return 263 } 264 } 265 } 266 267 func setClasses(n *html.Node, attr *html.Attribute, classes string) { 268 classes = strings.TrimSpace(classes) 269 if classes == "" { 270 removeAttr(n, "class") 271 return 272 } 273 274 attr.Val = classes 275 }