The reconcile package is used for DOM reconcilation in Isomorphic Go web applications.

parsetree.go 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. // The UXToolkit Project
  2. // Copyright (c) Wirecog, LLC. All rights reserved.
  3. // Use of this source code is governed by a BSD-style
  4. // license, which can be found in the LICENSE file.
  5. package reconcile
  6. import (
  7. "bytes"
  8. "errors"
  9. "io"
  10. "golang.org/x/net/html"
  11. )
  12. // Declare considered empty HTML tags
  13. // Reference: https://developer.mozilla.org/en-US/docs/Glossary/Empty_element
  14. var emptyTags = map[string]int{"input": 1, "area": 1, "base": 1, "br": 1, "col": 1, "embed": 1, "hr": 1, "img": 1, "keygen": 1, "link": 1, "meta": 1, "param": 1, "source": 1, "track": 1, "wbr": 1}
  15. func IsEmptyElement(tag string) bool {
  16. if _, ok := emptyTags[tag]; ok == true {
  17. return true
  18. } else {
  19. return false
  20. }
  21. }
  22. type ParseTree struct {
  23. reader io.Reader
  24. src []byte
  25. ChildNodes []*DOMNode
  26. }
  27. func NewParseTree(src []byte) (*ParseTree, error) {
  28. r := bytes.NewReader(src)
  29. z := html.NewTokenizer(r)
  30. tree := &ParseTree{src: src, reader: r}
  31. var node *DOMNode = nil
  32. for tokenType := z.Next(); tokenType != html.ErrorToken; tokenType = z.Next() {
  33. token := z.Token()
  34. if next, err := tree.parse(token, node, z); err != nil {
  35. return nil, err
  36. } else {
  37. node = next
  38. }
  39. }
  40. return tree, nil
  41. }
  42. func (pt *ParseTree) parse(token html.Token, current *DOMNode, tokenizer *html.Tokenizer) (next *DOMNode, err error) {
  43. emptyElement := IsEmptyElement(token.Data)
  44. offset := tokenizer.CurrentOffset()
  45. var result *DOMNode
  46. if token.Type == html.StartTagToken || token.Type == html.SelfClosingTagToken {
  47. node := &DOMNode{}
  48. node.NodeType = ElementNodeType
  49. node.Name = token.Data
  50. node.tree = pt
  51. for _, attribute := range token.Attr {
  52. node.Attributes = append(node.Attributes, Attribute{
  53. Name: attribute.Key,
  54. Value: attribute.Val,
  55. })
  56. }
  57. if current != nil {
  58. node.Position = make([]int, len(current.Position)+1)
  59. copy(node.Position, current.Position)
  60. node.Position[len(current.Position)] = len(current.ChildNodes)
  61. node.ParentNode = current
  62. current.ChildNodes = append(current.ChildNodes, node)
  63. } else {
  64. node.Position = []int{len(pt.ChildNodes)}
  65. }
  66. node.startPosition, err = pt.ReverseFind(0, offset[0]-1, '<')
  67. if err != nil {
  68. return nil, err
  69. }
  70. node.innerStartPosition = offset[0]
  71. next = node
  72. result = node
  73. if token.Type == html.SelfClosingTagToken || emptyElement == true {
  74. current = node
  75. }
  76. }
  77. if token.Type == html.EndTagToken || token.Type == html.SelfClosingTagToken || emptyElement == true {
  78. offset := tokenizer.CurrentOffset()
  79. if token.Type == html.SelfClosingTagToken || emptyElement == true {
  80. current.IsSelfClosing = true
  81. if current.ParentNode != nil {
  82. next = current.ParentNode
  83. } else {
  84. next = nil
  85. }
  86. } else {
  87. current.endPosition = offset[0]
  88. closingTagLength := len(current.Name) + 3
  89. current.innerEndPosition = offset[0] - closingTagLength
  90. if current.ParentNode != nil {
  91. next = current.ParentNode
  92. } else {
  93. next = nil
  94. }
  95. }
  96. }
  97. if token.Type == html.TextToken {
  98. charData := token.Data
  99. text := &DOMNode{}
  100. text.NodeType = TextNodeType
  101. text.Value = []byte(charData)
  102. if current != nil {
  103. text.Position = make([]int, len(current.Position)+1)
  104. copy(text.Position, current.Position)
  105. text.Position[len(current.Position)] = len(current.ChildNodes)
  106. text.ParentNode = current
  107. current.ChildNodes = append(current.ChildNodes, text)
  108. } else {
  109. text.Position = []int{len(pt.ChildNodes)}
  110. }
  111. result = text
  112. next = current
  113. } else if token.Type == html.CommentToken {
  114. htmlComment := token.Data
  115. comment := &DOMNode{}
  116. comment.NodeType = CommentNodeType
  117. comment.Value = []byte(htmlComment)
  118. if current != nil {
  119. comment.Position = make([]int, len(current.Position)+1)
  120. copy(comment.Position, current.Position)
  121. comment.Position[len(current.Position)] = len(current.ChildNodes)
  122. comment.ParentNode = current
  123. current.ChildNodes = append(current.ChildNodes, comment)
  124. } else {
  125. comment.Position = []int{len(pt.ChildNodes)}
  126. }
  127. result = comment
  128. next = current
  129. }
  130. if result != nil && current == nil {
  131. pt.ChildNodes = append(pt.ChildNodes, result)
  132. }
  133. return next, nil
  134. }
  135. func (t *ParseTree) Compare(other *ParseTree) (Changes, error) {
  136. changes := []Reconciler{}
  137. if err := compareNodes(&changes, t.ChildNodes, other.ChildNodes); err != nil {
  138. return nil, err
  139. }
  140. return changes, nil
  141. }
  142. func compareAttributes(changes *[]Reconciler, a, b *DOMNode) {
  143. bAttributes := b.AttributesMap()
  144. aAttributes := a.AttributesMap()
  145. for attributeName := range aAttributes {
  146. if _, found := bAttributes[attributeName]; !found {
  147. *changes = append(*changes, Reconciler{ActionType: RemoveNodeAttributeAction, ExistingNode: a, AttributeName: attributeName})
  148. }
  149. }
  150. for name, bValue := range bAttributes {
  151. value, found := aAttributes[name]
  152. if !found {
  153. *changes = append(*changes, Reconciler{ActionType: SetNodeAttributeAction, ExistingNode: a, AttributeName: name, AttributeValue: bValue})
  154. } else if value != bValue {
  155. *changes = append(*changes, Reconciler{ActionType: SetNodeAttributeAction, ExistingNode: a, AttributeName: name, AttributeValue: bValue})
  156. }
  157. }
  158. }
  159. func compareNodes(changes *[]Reconciler, a, b []*DOMNode) error {
  160. bCount := len(b)
  161. aCount := len(a)
  162. minCount := bCount
  163. if bCount > aCount {
  164. for _, bNode := range b[aCount:] {
  165. *changes = append(*changes, Reconciler{ActionType: AppendChildNodeAction, ParentNode: bNode.ParentNode, ChildNode: bNode})
  166. }
  167. minCount = aCount
  168. } else if aCount > bCount {
  169. for _, node := range a[bCount:] {
  170. *changes = append(*changes, Reconciler{ActionType: RemoveNodeAction, ExistingNode: node})
  171. }
  172. minCount = bCount
  173. }
  174. for i := 0; i < minCount; i++ {
  175. bNode := b[i]
  176. n := a[i]
  177. if n.IsEqual(*bNode) == false {
  178. *changes = append(*changes, Reconciler{ActionType: ReplaceNodeAction, ExistingNode: n, NewNode: bNode})
  179. continue
  180. }
  181. if bNode.NodeType == ElementNodeType {
  182. node := n
  183. compareAttributes(changes, node, bNode)
  184. compareNodes(changes, n.ChildNodes, bNode.ChildNodes)
  185. }
  186. }
  187. return nil
  188. }
  189. func (pt *ParseTree) ReverseFind(m int, n int, b byte) (int, error) {
  190. if n >= len(pt.src) || n < m {
  191. return -1, errors.New("invalid value for n")
  192. }
  193. if m >= len(pt.src) || m < 0 {
  194. return -1, errors.New("invalid value for m")
  195. }
  196. for i := n; i >= m; i-- {
  197. if pt.src[i] == b {
  198. return i, nil
  199. }
  200. }
  201. return -1, nil
  202. }