xml-fold.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: https://codemirror.net/LICENSE
  3. ;(function (mod) {
  4. if (typeof exports == 'object' && typeof module == 'object')
  5. // CommonJS
  6. mod(require('../../lib/codemirror'))
  7. else if (typeof define == 'function' && define.amd)
  8. // AMD
  9. define(['../../lib/codemirror'], mod)
  10. // Plain browser env
  11. else mod(CodeMirror)
  12. })(function (CodeMirror) {
  13. 'use strict'
  14. var Pos = CodeMirror.Pos
  15. function cmp(a, b) {
  16. return a.line - b.line || a.ch - b.ch
  17. }
  18. var nameStartChar = 'A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD'
  19. var nameChar = nameStartChar + '-:.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040'
  20. var xmlTagStart = new RegExp('<(/?)([' + nameStartChar + '][' + nameChar + ']*)', 'g')
  21. function Iter(cm, line, ch, range) {
  22. this.line = line
  23. this.ch = ch
  24. this.cm = cm
  25. this.text = cm.getLine(line)
  26. this.min = range ? Math.max(range.from, cm.firstLine()) : cm.firstLine()
  27. this.max = range ? Math.min(range.to - 1, cm.lastLine()) : cm.lastLine()
  28. }
  29. function tagAt(iter, ch) {
  30. var type = iter.cm.getTokenTypeAt(Pos(iter.line, ch))
  31. return type && /\btag\b/.test(type)
  32. }
  33. function nextLine(iter) {
  34. if (iter.line >= iter.max) return
  35. iter.ch = 0
  36. iter.text = iter.cm.getLine(++iter.line)
  37. return true
  38. }
  39. function prevLine(iter) {
  40. if (iter.line <= iter.min) return
  41. iter.text = iter.cm.getLine(--iter.line)
  42. iter.ch = iter.text.length
  43. return true
  44. }
  45. function toTagEnd(iter) {
  46. for (;;) {
  47. var gt = iter.text.indexOf('>', iter.ch)
  48. if (gt == -1) {
  49. if (nextLine(iter)) continue
  50. else return
  51. }
  52. if (!tagAt(iter, gt + 1)) {
  53. iter.ch = gt + 1
  54. continue
  55. }
  56. var lastSlash = iter.text.lastIndexOf('/', gt)
  57. var selfClose = lastSlash > -1 && !/\S/.test(iter.text.slice(lastSlash + 1, gt))
  58. iter.ch = gt + 1
  59. return selfClose ? 'selfClose' : 'regular'
  60. }
  61. }
  62. function toTagStart(iter) {
  63. for (;;) {
  64. var lt = iter.ch ? iter.text.lastIndexOf('<', iter.ch - 1) : -1
  65. if (lt == -1) {
  66. if (prevLine(iter)) continue
  67. else return
  68. }
  69. if (!tagAt(iter, lt + 1)) {
  70. iter.ch = lt
  71. continue
  72. }
  73. xmlTagStart.lastIndex = lt
  74. iter.ch = lt
  75. var match = xmlTagStart.exec(iter.text)
  76. if (match && match.index == lt) return match
  77. }
  78. }
  79. function toNextTag(iter) {
  80. for (;;) {
  81. xmlTagStart.lastIndex = iter.ch
  82. var found = xmlTagStart.exec(iter.text)
  83. if (!found) {
  84. if (nextLine(iter)) continue
  85. else return
  86. }
  87. if (!tagAt(iter, found.index + 1)) {
  88. iter.ch = found.index + 1
  89. continue
  90. }
  91. iter.ch = found.index + found[0].length
  92. return found
  93. }
  94. }
  95. function toPrevTag(iter) {
  96. for (;;) {
  97. var gt = iter.ch ? iter.text.lastIndexOf('>', iter.ch - 1) : -1
  98. if (gt == -1) {
  99. if (prevLine(iter)) continue
  100. else return
  101. }
  102. if (!tagAt(iter, gt + 1)) {
  103. iter.ch = gt
  104. continue
  105. }
  106. var lastSlash = iter.text.lastIndexOf('/', gt)
  107. var selfClose = lastSlash > -1 && !/\S/.test(iter.text.slice(lastSlash + 1, gt))
  108. iter.ch = gt + 1
  109. return selfClose ? 'selfClose' : 'regular'
  110. }
  111. }
  112. function findMatchingClose(iter, tag) {
  113. var stack = []
  114. for (;;) {
  115. var next = toNextTag(iter),
  116. end,
  117. startLine = iter.line,
  118. startCh = iter.ch - (next ? next[0].length : 0)
  119. if (!next || !(end = toTagEnd(iter))) return
  120. if (end == 'selfClose') continue
  121. if (next[1]) {
  122. // closing tag
  123. for (var i = stack.length - 1; i >= 0; --i)
  124. if (stack[i] == next[2]) {
  125. stack.length = i
  126. break
  127. }
  128. if (i < 0 && (!tag || tag == next[2]))
  129. return {
  130. tag: next[2],
  131. from: Pos(startLine, startCh),
  132. to: Pos(iter.line, iter.ch),
  133. }
  134. } else {
  135. // opening tag
  136. stack.push(next[2])
  137. }
  138. }
  139. }
  140. function findMatchingOpen(iter, tag) {
  141. var stack = []
  142. for (;;) {
  143. var prev = toPrevTag(iter)
  144. if (!prev) return
  145. if (prev == 'selfClose') {
  146. toTagStart(iter)
  147. continue
  148. }
  149. var endLine = iter.line,
  150. endCh = iter.ch
  151. var start = toTagStart(iter)
  152. if (!start) return
  153. if (start[1]) {
  154. // closing tag
  155. stack.push(start[2])
  156. } else {
  157. // opening tag
  158. for (var i = stack.length - 1; i >= 0; --i)
  159. if (stack[i] == start[2]) {
  160. stack.length = i
  161. break
  162. }
  163. if (i < 0 && (!tag || tag == start[2]))
  164. return {
  165. tag: start[2],
  166. from: Pos(iter.line, iter.ch),
  167. to: Pos(endLine, endCh),
  168. }
  169. }
  170. }
  171. }
  172. CodeMirror.registerHelper('fold', 'xml', function (cm, start) {
  173. var iter = new Iter(cm, start.line, 0)
  174. for (;;) {
  175. var openTag = toNextTag(iter)
  176. if (!openTag || iter.line != start.line) return
  177. var end = toTagEnd(iter)
  178. if (!end) return
  179. if (!openTag[1] && end != 'selfClose') {
  180. var startPos = Pos(iter.line, iter.ch)
  181. var endPos = findMatchingClose(iter, openTag[2])
  182. return endPos && cmp(endPos.from, startPos) > 0 ? { from: startPos, to: endPos.from } : null
  183. }
  184. }
  185. })
  186. CodeMirror.findMatchingTag = function (cm, pos, range) {
  187. var iter = new Iter(cm, pos.line, pos.ch, range)
  188. if (iter.text.indexOf('>') == -1 && iter.text.indexOf('<') == -1) return
  189. var end = toTagEnd(iter),
  190. to = end && Pos(iter.line, iter.ch)
  191. var start = end && toTagStart(iter)
  192. if (!end || !start || cmp(iter, pos) > 0) return
  193. var here = { from: Pos(iter.line, iter.ch), to: to, tag: start[2] }
  194. if (end == 'selfClose') return { open: here, close: null, at: 'open' }
  195. if (start[1]) {
  196. // closing tag
  197. return { open: findMatchingOpen(iter, start[2]), close: here, at: 'close' }
  198. } else {
  199. // opening tag
  200. iter = new Iter(cm, to.line, to.ch, range)
  201. return { open: here, close: findMatchingClose(iter, start[2]), at: 'open' }
  202. }
  203. }
  204. CodeMirror.findEnclosingTag = function (cm, pos, range, tag) {
  205. var iter = new Iter(cm, pos.line, pos.ch, range)
  206. for (;;) {
  207. var open = findMatchingOpen(iter, tag)
  208. if (!open) break
  209. var forward = new Iter(cm, pos.line, pos.ch, range)
  210. var close = findMatchingClose(forward, open.tag)
  211. if (close) return { open: open, close: close }
  212. }
  213. }
  214. // Used by addon/edit/closetag.js
  215. CodeMirror.scanForClosingTag = function (cm, pos, name, end) {
  216. var iter = new Iter(cm, pos.line, pos.ch, end ? { from: 0, to: end } : null)
  217. return findMatchingClose(iter, name)
  218. }
  219. })