You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

161 lines
4.6 KiB

  1. package goquery
  2. import (
  3. "bytes"
  4. "golang.org/x/net/html"
  5. )
  6. // used to determine if a set (map[*html.Node]bool) should be used
  7. // instead of iterating over a slice. The set uses more memory and
  8. // is slower than slice iteration for small N.
  9. const minNodesForSet = 1000
  10. var nodeNames = []string{
  11. html.ErrorNode: "#error",
  12. html.TextNode: "#text",
  13. html.DocumentNode: "#document",
  14. html.CommentNode: "#comment",
  15. }
  16. // NodeName returns the node name of the first element in the selection.
  17. // It tries to behave in a similar way as the DOM's nodeName property
  18. // (https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName).
  19. //
  20. // Go's net/html package defines the following node types, listed with
  21. // the corresponding returned value from this function:
  22. //
  23. // ErrorNode : #error
  24. // TextNode : #text
  25. // DocumentNode : #document
  26. // ElementNode : the element's tag name
  27. // CommentNode : #comment
  28. // DoctypeNode : the name of the document type
  29. //
  30. func NodeName(s *Selection) string {
  31. if s.Length() == 0 {
  32. return ""
  33. }
  34. switch n := s.Get(0); n.Type {
  35. case html.ElementNode, html.DoctypeNode:
  36. return n.Data
  37. default:
  38. if n.Type >= 0 && int(n.Type) < len(nodeNames) {
  39. return nodeNames[n.Type]
  40. }
  41. return ""
  42. }
  43. }
  44. // OuterHtml returns the outer HTML rendering of the first item in
  45. // the selection - that is, the HTML including the first element's
  46. // tag and attributes.
  47. //
  48. // Unlike InnerHtml, this is a function and not a method on the Selection,
  49. // because this is not a jQuery method (in javascript-land, this is
  50. // a property provided by the DOM).
  51. func OuterHtml(s *Selection) (string, error) {
  52. var buf bytes.Buffer
  53. if s.Length() == 0 {
  54. return "", nil
  55. }
  56. n := s.Get(0)
  57. if err := html.Render(&buf, n); err != nil {
  58. return "", err
  59. }
  60. return buf.String(), nil
  61. }
  62. // Loop through all container nodes to search for the target node.
  63. func sliceContains(container []*html.Node, contained *html.Node) bool {
  64. for _, n := range container {
  65. if nodeContains(n, contained) {
  66. return true
  67. }
  68. }
  69. return false
  70. }
  71. // Checks if the contained node is within the container node.
  72. func nodeContains(container *html.Node, contained *html.Node) bool {
  73. // Check if the parent of the contained node is the container node, traversing
  74. // upward until the top is reached, or the container is found.
  75. for contained = contained.Parent; contained != nil; contained = contained.Parent {
  76. if container == contained {
  77. return true
  78. }
  79. }
  80. return false
  81. }
  82. // Checks if the target node is in the slice of nodes.
  83. func isInSlice(slice []*html.Node, node *html.Node) bool {
  84. return indexInSlice(slice, node) > -1
  85. }
  86. // Returns the index of the target node in the slice, or -1.
  87. func indexInSlice(slice []*html.Node, node *html.Node) int {
  88. if node != nil {
  89. for i, n := range slice {
  90. if n == node {
  91. return i
  92. }
  93. }
  94. }
  95. return -1
  96. }
  97. // Appends the new nodes to the target slice, making sure no duplicate is added.
  98. // There is no check to the original state of the target slice, so it may still
  99. // contain duplicates. The target slice is returned because append() may create
  100. // a new underlying array. If targetSet is nil, a local set is created with the
  101. // target if len(target) + len(nodes) is greater than minNodesForSet.
  102. func appendWithoutDuplicates(target []*html.Node, nodes []*html.Node, targetSet map[*html.Node]bool) []*html.Node {
  103. // if there are not that many nodes, don't use the map, faster to just use nested loops
  104. // (unless a non-nil targetSet is passed, in which case the caller knows better).
  105. if targetSet == nil && len(target)+len(nodes) < minNodesForSet {
  106. for _, n := range nodes {
  107. if !isInSlice(target, n) {
  108. target = append(target, n)
  109. }
  110. }
  111. return target
  112. }
  113. // if a targetSet is passed, then assume it is reliable, otherwise create one
  114. // and initialize it with the current target contents.
  115. if targetSet == nil {
  116. targetSet = make(map[*html.Node]bool, len(target))
  117. for _, n := range target {
  118. targetSet[n] = true
  119. }
  120. }
  121. for _, n := range nodes {
  122. if !targetSet[n] {
  123. target = append(target, n)
  124. targetSet[n] = true
  125. }
  126. }
  127. return target
  128. }
  129. // Loop through a selection, returning only those nodes that pass the predicate
  130. // function.
  131. func grep(sel *Selection, predicate func(i int, s *Selection) bool) (result []*html.Node) {
  132. for i, n := range sel.Nodes {
  133. if predicate(i, newSingleSelection(n, sel.document)) {
  134. result = append(result, n)
  135. }
  136. }
  137. return result
  138. }
  139. // Creates a new Selection object based on the specified nodes, and keeps the
  140. // source Selection object on the stack (linked list).
  141. func pushStack(fromSel *Selection, nodes []*html.Node) *Selection {
  142. result := &Selection{nodes, fromSel.document, fromSel}
  143. return result
  144. }