xml.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  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 htmlConfig = {
  15. autoSelfClosers: {
  16. area: true,
  17. base: true,
  18. br: true,
  19. col: true,
  20. command: true,
  21. embed: true,
  22. frame: true,
  23. hr: true,
  24. img: true,
  25. input: true,
  26. keygen: true,
  27. link: true,
  28. meta: true,
  29. param: true,
  30. source: true,
  31. track: true,
  32. wbr: true,
  33. menuitem: true,
  34. },
  35. implicitlyClosed: { dd: true, li: true, optgroup: true, option: true, p: true, rp: true, rt: true, tbody: true, td: true, tfoot: true, th: true, tr: true },
  36. contextGrabbers: {
  37. dd: { dd: true, dt: true },
  38. dt: { dd: true, dt: true },
  39. li: { li: true },
  40. option: { option: true, optgroup: true },
  41. optgroup: { optgroup: true },
  42. p: {
  43. address: true,
  44. article: true,
  45. aside: true,
  46. blockquote: true,
  47. dir: true,
  48. div: true,
  49. dl: true,
  50. fieldset: true,
  51. footer: true,
  52. form: true,
  53. h1: true,
  54. h2: true,
  55. h3: true,
  56. h4: true,
  57. h5: true,
  58. h6: true,
  59. header: true,
  60. hgroup: true,
  61. hr: true,
  62. menu: true,
  63. nav: true,
  64. ol: true,
  65. p: true,
  66. pre: true,
  67. section: true,
  68. table: true,
  69. ul: true,
  70. },
  71. rp: { rp: true, rt: true },
  72. rt: { rp: true, rt: true },
  73. tbody: { tbody: true, tfoot: true },
  74. td: { td: true, th: true },
  75. tfoot: { tbody: true },
  76. th: { td: true, th: true },
  77. thead: { tbody: true, tfoot: true },
  78. tr: { tr: true },
  79. },
  80. doNotIndent: { pre: true },
  81. allowUnquoted: true,
  82. allowMissing: true,
  83. caseFold: true,
  84. }
  85. var xmlConfig = {
  86. autoSelfClosers: {},
  87. implicitlyClosed: {},
  88. contextGrabbers: {},
  89. doNotIndent: {},
  90. allowUnquoted: false,
  91. allowMissing: false,
  92. allowMissingTagName: false,
  93. caseFold: false,
  94. }
  95. CodeMirror.defineMode('xml', function (editorConf, config_) {
  96. var indentUnit = editorConf.indentUnit
  97. var config = {}
  98. var defaults = config_.htmlMode ? htmlConfig : xmlConfig
  99. for (var prop in defaults) config[prop] = defaults[prop]
  100. for (var prop in config_) config[prop] = config_[prop]
  101. // Return variables for tokenizers
  102. var type, setStyle
  103. function inText(stream, state) {
  104. function chain(parser) {
  105. state.tokenize = parser
  106. return parser(stream, state)
  107. }
  108. var ch = stream.next()
  109. if (ch == '<') {
  110. if (stream.eat('!')) {
  111. if (stream.eat('[')) {
  112. if (stream.match('CDATA[')) return chain(inBlock('atom', ']]>'))
  113. else return null
  114. } else if (stream.match('--')) {
  115. return chain(inBlock('comment', '-->'))
  116. } else if (stream.match('DOCTYPE', true, true)) {
  117. stream.eatWhile(/[\w\._\-]/)
  118. return chain(doctype(1))
  119. } else {
  120. return null
  121. }
  122. } else if (stream.eat('?')) {
  123. stream.eatWhile(/[\w\._\-]/)
  124. state.tokenize = inBlock('meta', '?>')
  125. return 'meta'
  126. } else {
  127. type = stream.eat('/') ? 'closeTag' : 'openTag'
  128. state.tokenize = inTag
  129. return 'tag bracket'
  130. }
  131. } else if (ch == '&') {
  132. var ok
  133. if (stream.eat('#')) {
  134. if (stream.eat('x')) {
  135. ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(';')
  136. } else {
  137. ok = stream.eatWhile(/[\d]/) && stream.eat(';')
  138. }
  139. } else {
  140. ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(';')
  141. }
  142. return ok ? 'atom' : 'error'
  143. } else {
  144. stream.eatWhile(/[^&<]/)
  145. return null
  146. }
  147. }
  148. inText.isInText = true
  149. function inTag(stream, state) {
  150. var ch = stream.next()
  151. if (ch == '>' || (ch == '/' && stream.eat('>'))) {
  152. state.tokenize = inText
  153. type = ch == '>' ? 'endTag' : 'selfcloseTag'
  154. return 'tag bracket'
  155. } else if (ch == '=') {
  156. type = 'equals'
  157. return null
  158. } else if (ch == '<') {
  159. state.tokenize = inText
  160. state.state = baseState
  161. state.tagName = state.tagStart = null
  162. var next = state.tokenize(stream, state)
  163. return next ? next + ' tag error' : 'tag error'
  164. } else if (/[\'\"]/.test(ch)) {
  165. state.tokenize = inAttribute(ch)
  166. state.stringStartCol = stream.column()
  167. return state.tokenize(stream, state)
  168. } else {
  169. stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/)
  170. return 'word'
  171. }
  172. }
  173. function inAttribute(quote) {
  174. var closure = function (stream, state) {
  175. while (!stream.eol()) {
  176. if (stream.next() == quote) {
  177. state.tokenize = inTag
  178. break
  179. }
  180. }
  181. return 'string'
  182. }
  183. closure.isInAttribute = true
  184. return closure
  185. }
  186. function inBlock(style, terminator) {
  187. return function (stream, state) {
  188. while (!stream.eol()) {
  189. if (stream.match(terminator)) {
  190. state.tokenize = inText
  191. break
  192. }
  193. stream.next()
  194. }
  195. return style
  196. }
  197. }
  198. function doctype(depth) {
  199. return function (stream, state) {
  200. var ch
  201. while ((ch = stream.next()) != null) {
  202. if (ch == '<') {
  203. state.tokenize = doctype(depth + 1)
  204. return state.tokenize(stream, state)
  205. } else if (ch == '>') {
  206. if (depth == 1) {
  207. state.tokenize = inText
  208. break
  209. } else {
  210. state.tokenize = doctype(depth - 1)
  211. return state.tokenize(stream, state)
  212. }
  213. }
  214. }
  215. return 'meta'
  216. }
  217. }
  218. function lower(tagName) {
  219. return tagName && tagName.toLowerCase()
  220. }
  221. function Context(state, tagName, startOfLine) {
  222. this.prev = state.context
  223. this.tagName = tagName || ''
  224. this.indent = state.indented
  225. this.startOfLine = startOfLine
  226. if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent)) this.noIndent = true
  227. }
  228. function popContext(state) {
  229. if (state.context) state.context = state.context.prev
  230. }
  231. function maybePopContext(state, nextTagName) {
  232. var parentTagName
  233. while (true) {
  234. if (!state.context) {
  235. return
  236. }
  237. parentTagName = state.context.tagName
  238. if (!config.contextGrabbers.hasOwnProperty(lower(parentTagName)) || !config.contextGrabbers[lower(parentTagName)].hasOwnProperty(lower(nextTagName))) {
  239. return
  240. }
  241. popContext(state)
  242. }
  243. }
  244. function baseState(type, stream, state) {
  245. if (type == 'openTag') {
  246. state.tagStart = stream.column()
  247. return tagNameState
  248. } else if (type == 'closeTag') {
  249. return closeTagNameState
  250. } else {
  251. return baseState
  252. }
  253. }
  254. function tagNameState(type, stream, state) {
  255. if (type == 'word') {
  256. state.tagName = stream.current()
  257. setStyle = 'tag'
  258. return attrState
  259. } else if (config.allowMissingTagName && type == 'endTag') {
  260. setStyle = 'tag bracket'
  261. return attrState(type, stream, state)
  262. } else {
  263. setStyle = 'error'
  264. return tagNameState
  265. }
  266. }
  267. function closeTagNameState(type, stream, state) {
  268. if (type == 'word') {
  269. var tagName = stream.current()
  270. if (state.context && state.context.tagName != tagName && config.implicitlyClosed.hasOwnProperty(lower(state.context.tagName))) popContext(state)
  271. if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) {
  272. setStyle = 'tag'
  273. return closeState
  274. } else {
  275. setStyle = 'tag error'
  276. return closeStateErr
  277. }
  278. } else if (config.allowMissingTagName && type == 'endTag') {
  279. setStyle = 'tag bracket'
  280. return closeState(type, stream, state)
  281. } else {
  282. setStyle = 'error'
  283. return closeStateErr
  284. }
  285. }
  286. function closeState(type, _stream, state) {
  287. if (type != 'endTag') {
  288. setStyle = 'error'
  289. return closeState
  290. }
  291. popContext(state)
  292. return baseState
  293. }
  294. function closeStateErr(type, stream, state) {
  295. setStyle = 'error'
  296. return closeState(type, stream, state)
  297. }
  298. function attrState(type, _stream, state) {
  299. if (type == 'word') {
  300. setStyle = 'attribute'
  301. return attrEqState
  302. } else if (type == 'endTag' || type == 'selfcloseTag') {
  303. var tagName = state.tagName,
  304. tagStart = state.tagStart
  305. state.tagName = state.tagStart = null
  306. if (type == 'selfcloseTag' || config.autoSelfClosers.hasOwnProperty(lower(tagName))) {
  307. maybePopContext(state, tagName)
  308. } else {
  309. maybePopContext(state, tagName)
  310. state.context = new Context(state, tagName, tagStart == state.indented)
  311. }
  312. return baseState
  313. }
  314. setStyle = 'error'
  315. return attrState
  316. }
  317. function attrEqState(type, stream, state) {
  318. if (type == 'equals') return attrValueState
  319. if (!config.allowMissing) setStyle = 'error'
  320. return attrState(type, stream, state)
  321. }
  322. function attrValueState(type, stream, state) {
  323. if (type == 'string') return attrContinuedState
  324. if (type == 'word' && config.allowUnquoted) {
  325. setStyle = 'string'
  326. return attrState
  327. }
  328. setStyle = 'error'
  329. return attrState(type, stream, state)
  330. }
  331. function attrContinuedState(type, stream, state) {
  332. if (type == 'string') return attrContinuedState
  333. return attrState(type, stream, state)
  334. }
  335. return {
  336. startState: function (baseIndent) {
  337. var state = { tokenize: inText, state: baseState, indented: baseIndent || 0, tagName: null, tagStart: null, context: null }
  338. if (baseIndent != null) state.baseIndent = baseIndent
  339. return state
  340. },
  341. token: function (stream, state) {
  342. if (!state.tagName && stream.sol()) state.indented = stream.indentation()
  343. if (stream.eatSpace()) return null
  344. type = null
  345. var style = state.tokenize(stream, state)
  346. if ((style || type) && style != 'comment') {
  347. setStyle = null
  348. state.state = state.state(type || style, stream, state)
  349. if (setStyle) style = setStyle == 'error' ? style + ' error' : setStyle
  350. }
  351. return style
  352. },
  353. indent: function (state, textAfter, fullLine) {
  354. var context = state.context
  355. // Indent multi-line strings (e.g. css).
  356. if (state.tokenize.isInAttribute) {
  357. if (state.tagStart == state.indented) return state.stringStartCol + 1
  358. else return state.indented + indentUnit
  359. }
  360. if (context && context.noIndent) return CodeMirror.Pass
  361. if (state.tokenize != inTag && state.tokenize != inText) return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0
  362. // Indent the starts of attribute names.
  363. if (state.tagName) {
  364. if (config.multilineTagIndentPastTag !== false) return state.tagStart + state.tagName.length + 2
  365. else return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1)
  366. }
  367. if (config.alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0
  368. var tagAfter = textAfter && /^<(\/)?([\w_:\.-]*)/.exec(textAfter)
  369. if (tagAfter && tagAfter[1]) {
  370. // Closing tag spotted
  371. while (context) {
  372. if (context.tagName == tagAfter[2]) {
  373. context = context.prev
  374. break
  375. } else if (config.implicitlyClosed.hasOwnProperty(lower(context.tagName))) {
  376. context = context.prev
  377. } else {
  378. break
  379. }
  380. }
  381. } else if (tagAfter) {
  382. // Opening tag spotted
  383. while (context) {
  384. var grabbers = config.contextGrabbers[lower(context.tagName)]
  385. if (grabbers && grabbers.hasOwnProperty(lower(tagAfter[2]))) context = context.prev
  386. else break
  387. }
  388. }
  389. while (context && context.prev && !context.startOfLine) context = context.prev
  390. if (context) return context.indent + indentUnit
  391. else return state.baseIndent || 0
  392. },
  393. electricInput: /<\/[\s\w:]+>$/,
  394. blockCommentStart: '<!--',
  395. blockCommentEnd: '-->',
  396. configuration: config.htmlMode ? 'html' : 'xml',
  397. helperType: config.htmlMode ? 'html' : 'xml',
  398. skipAttribute: function (state) {
  399. if (state.state == attrValueState) state.state = attrState
  400. },
  401. xmlCurrentTag: function (state) {
  402. return state.tagName ? { name: state.tagName, close: state.type == 'closeTag' } : null
  403. },
  404. xmlCurrentContext: function (state) {
  405. var context = []
  406. for (var cx = state.context; cx; cx = cx.prev) context.push(cx.tagName)
  407. return context.reverse()
  408. },
  409. }
  410. })
  411. CodeMirror.defineMIME('text/xml', 'xml')
  412. CodeMirror.defineMIME('application/xml', 'xml')
  413. if (!CodeMirror.mimeModes.hasOwnProperty('text/html')) CodeMirror.defineMIME('text/html', { name: 'xml', htmlMode: true })
  414. })