property.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  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. for _, n := range s.Nodes {
  54. buf.WriteString(getNodeText(n))
  55. }
  56. return buf.String()
  57. }
  58. // Size is an alias for Length.
  59. func (s *Selection) Size() int {
  60. return s.Length()
  61. }
  62. // Length returns the number of elements in the Selection object.
  63. func (s *Selection) Length() int {
  64. return len(s.Nodes)
  65. }
  66. // Html gets the HTML contents of the first element in the set of matched
  67. // elements. It includes text and comment nodes.
  68. func (s *Selection) Html() (ret string, e error) {
  69. // Since there is no .innerHtml, the HTML content must be re-created from
  70. // the nodes using html.Render.
  71. var buf bytes.Buffer
  72. if len(s.Nodes) > 0 {
  73. for c := s.Nodes[0].FirstChild; c != nil; c = c.NextSibling {
  74. e = html.Render(&buf, c)
  75. if e != nil {
  76. return
  77. }
  78. }
  79. ret = buf.String()
  80. }
  81. return
  82. }
  83. func (s *Selection) OuterHtml() (ret string, e error) {
  84. // Since there is no .innerHtml, the HTML content must be re-created from
  85. // the nodes using html.Render.
  86. var buf bytes.Buffer
  87. if len(s.Nodes) > 0 {
  88. e = html.Render(&buf, s.Nodes[0])
  89. if e != nil {
  90. return
  91. }
  92. ret = buf.String()
  93. }
  94. return
  95. }
  96. // AddClass adds the given class(es) to each element in the set of matched elements.
  97. // Multiple class names can be specified, separated by a space or via multiple arguments.
  98. func (s *Selection) AddClass(class ...string) *Selection {
  99. classStr := strings.TrimSpace(strings.Join(class, " "))
  100. if classStr == "" {
  101. return s
  102. }
  103. tcls := getClassesSlice(classStr)
  104. for _, n := range s.Nodes {
  105. curClasses, attr := getClassesAndAttr(n, true)
  106. for _, newClass := range tcls {
  107. if strings.Index(curClasses, " "+newClass+" ") == -1 {
  108. curClasses += newClass + " "
  109. }
  110. }
  111. setClasses(n, attr, curClasses)
  112. }
  113. return s
  114. }
  115. // HasClass determines whether any of the matched elements are assigned the
  116. // given class.
  117. func (s *Selection) HasClass(class string) bool {
  118. class = " " + class + " "
  119. for _, n := range s.Nodes {
  120. classes, _ := getClassesAndAttr(n, false)
  121. if strings.Index(classes, class) > -1 {
  122. return true
  123. }
  124. }
  125. return false
  126. }
  127. // RemoveClass removes the given class(es) from each element in the set of matched elements.
  128. // Multiple class names can be specified, separated by a space or via multiple arguments.
  129. // If no class name is provided, all classes are removed.
  130. func (s *Selection) RemoveClass(class ...string) *Selection {
  131. var rclasses []string
  132. classStr := strings.TrimSpace(strings.Join(class, " "))
  133. remove := classStr == ""
  134. if !remove {
  135. rclasses = getClassesSlice(classStr)
  136. }
  137. for _, n := range s.Nodes {
  138. if remove {
  139. removeAttr(n, "class")
  140. } else {
  141. classes, attr := getClassesAndAttr(n, true)
  142. for _, rcl := range rclasses {
  143. classes = strings.Replace(classes, " "+rcl+" ", " ", -1)
  144. }
  145. setClasses(n, attr, classes)
  146. }
  147. }
  148. return s
  149. }
  150. // ToggleClass adds or removes the given class(es) for each element in the set of matched elements.
  151. // Multiple class names can be specified, separated by a space or via multiple arguments.
  152. func (s *Selection) ToggleClass(class ...string) *Selection {
  153. classStr := strings.TrimSpace(strings.Join(class, " "))
  154. if classStr == "" {
  155. return s
  156. }
  157. tcls := getClassesSlice(classStr)
  158. for _, n := range s.Nodes {
  159. classes, attr := getClassesAndAttr(n, true)
  160. for _, tcl := range tcls {
  161. if strings.Index(classes, " "+tcl+" ") != -1 {
  162. classes = strings.Replace(classes, " "+tcl+" ", " ", -1)
  163. } else {
  164. classes += tcl + " "
  165. }
  166. }
  167. setClasses(n, attr, classes)
  168. }
  169. return s
  170. }
  171. // Get the specified node's text content.
  172. func getNodeText(node *html.Node) string {
  173. if node.Type == html.TextNode {
  174. // Keep newlines and spaces, like jQuery
  175. return node.Data
  176. } else if node.FirstChild != nil {
  177. var buf bytes.Buffer
  178. for c := node.FirstChild; c != nil; c = c.NextSibling {
  179. buf.WriteString(getNodeText(c))
  180. }
  181. return buf.String()
  182. }
  183. return ""
  184. }
  185. func getAttributePtr(attrName string, n *html.Node) *html.Attribute {
  186. if n == nil {
  187. return nil
  188. }
  189. for i, a := range n.Attr {
  190. if a.Key == attrName {
  191. return &n.Attr[i]
  192. }
  193. }
  194. return nil
  195. }
  196. // Private function to get the specified attribute's value from a node.
  197. func getAttributeValue(attrName string, n *html.Node) (val string, exists bool) {
  198. if a := getAttributePtr(attrName, n); a != nil {
  199. val = a.Val
  200. exists = true
  201. }
  202. return
  203. }
  204. // Get and normalize the "class" attribute from the node.
  205. func getClassesAndAttr(n *html.Node, create bool) (classes string, attr *html.Attribute) {
  206. // Applies only to element nodes
  207. if n.Type == html.ElementNode {
  208. attr = getAttributePtr("class", n)
  209. if attr == nil && create {
  210. n.Attr = append(n.Attr, html.Attribute{
  211. Key: "class",
  212. Val: "",
  213. })
  214. attr = &n.Attr[len(n.Attr)-1]
  215. }
  216. }
  217. if attr == nil {
  218. classes = " "
  219. } else {
  220. classes = rxClassTrim.ReplaceAllString(" "+attr.Val+" ", " ")
  221. }
  222. return
  223. }
  224. func getClassesSlice(classes string) []string {
  225. return strings.Split(rxClassTrim.ReplaceAllString(" "+classes+" ", " "), " ")
  226. }
  227. func removeAttr(n *html.Node, attrName string) {
  228. for i, a := range n.Attr {
  229. if a.Key == attrName {
  230. n.Attr[i], n.Attr[len(n.Attr)-1], n.Attr =
  231. n.Attr[len(n.Attr)-1], html.Attribute{}, n.Attr[:len(n.Attr)-1]
  232. return
  233. }
  234. }
  235. }
  236. func setClasses(n *html.Node, attr *html.Attribute, classes string) {
  237. classes = strings.TrimSpace(classes)
  238. if classes == "" {
  239. removeAttr(n, "class")
  240. return
  241. }
  242. attr.Val = classes
  243. }