property.go 6.4 KB

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