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.

394 lines
12 KiB

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