xml.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. CodeMirror.defineMode("xml", function(config, parserConfig) {
  2. var indentUnit = config.indentUnit;
  3. var Kludges = parserConfig.htmlMode ? {
  4. autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
  5. 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
  6. 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
  7. 'track': true, 'wbr': true},
  8. implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
  9. 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
  10. 'th': true, 'tr': true},
  11. contextGrabbers: {
  12. 'dd': {'dd': true, 'dt': true},
  13. 'dt': {'dd': true, 'dt': true},
  14. 'li': {'li': true},
  15. 'option': {'option': true, 'optgroup': true},
  16. 'optgroup': {'optgroup': true},
  17. 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
  18. 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
  19. 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
  20. 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
  21. 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
  22. 'rp': {'rp': true, 'rt': true},
  23. 'rt': {'rp': true, 'rt': true},
  24. 'tbody': {'tbody': true, 'tfoot': true},
  25. 'td': {'td': true, 'th': true},
  26. 'tfoot': {'tbody': true},
  27. 'th': {'td': true, 'th': true},
  28. 'thead': {'tbody': true, 'tfoot': true},
  29. 'tr': {'tr': true}
  30. },
  31. doNotIndent: {"pre": true},
  32. allowUnquoted: true,
  33. allowMissing: true
  34. } : {
  35. autoSelfClosers: {},
  36. implicitlyClosed: {},
  37. contextGrabbers: {},
  38. doNotIndent: {},
  39. allowUnquoted: false,
  40. allowMissing: false
  41. };
  42. var alignCDATA = parserConfig.alignCDATA;
  43. // Return variables for tokenizers
  44. var tagName, type;
  45. function inText(stream, state) {
  46. function chain(parser) {
  47. state.tokenize = parser;
  48. return parser(stream, state);
  49. }
  50. var ch = stream.next();
  51. if (ch == "<") {
  52. if (stream.eat("!")) {
  53. if (stream.eat("[")) {
  54. if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
  55. else return null;
  56. }
  57. else if (stream.match("--")) return chain(inBlock("comment", "-->"));
  58. else if (stream.match("DOCTYPE", true, true)) {
  59. stream.eatWhile(/[\w\._\-]/);
  60. return chain(doctype(1));
  61. }
  62. else return null;
  63. }
  64. else if (stream.eat("?")) {
  65. stream.eatWhile(/[\w\._\-]/);
  66. state.tokenize = inBlock("meta", "?>");
  67. return "meta";
  68. }
  69. else {
  70. var isClose = stream.eat("/");
  71. tagName = "";
  72. var c;
  73. while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c;
  74. if (!tagName) return "error";
  75. type = isClose ? "closeTag" : "openTag";
  76. state.tokenize = inTag;
  77. return "tag";
  78. }
  79. }
  80. else if (ch == "&") {
  81. var ok;
  82. if (stream.eat("#")) {
  83. if (stream.eat("x")) {
  84. ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
  85. } else {
  86. ok = stream.eatWhile(/[\d]/) && stream.eat(";");
  87. }
  88. } else {
  89. ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
  90. }
  91. return ok ? "atom" : "error";
  92. }
  93. else {
  94. stream.eatWhile(/[^&<]/);
  95. return null;
  96. }
  97. }
  98. function inTag(stream, state) {
  99. var ch = stream.next();
  100. if (ch == ">" || (ch == "/" && stream.eat(">"))) {
  101. state.tokenize = inText;
  102. type = ch == ">" ? "endTag" : "selfcloseTag";
  103. return "tag";
  104. }
  105. else if (ch == "=") {
  106. type = "equals";
  107. return null;
  108. }
  109. else if (/[\'\"]/.test(ch)) {
  110. state.tokenize = inAttribute(ch);
  111. return state.tokenize(stream, state);
  112. }
  113. else {
  114. stream.eatWhile(/[^\s\u00a0=<>\"\']/);
  115. return "word";
  116. }
  117. }
  118. function inAttribute(quote) {
  119. return function(stream, state) {
  120. while (!stream.eol()) {
  121. if (stream.next() == quote) {
  122. state.tokenize = inTag;
  123. break;
  124. }
  125. }
  126. return "string";
  127. };
  128. }
  129. function inBlock(style, terminator) {
  130. return function(stream, state) {
  131. while (!stream.eol()) {
  132. if (stream.match(terminator)) {
  133. state.tokenize = inText;
  134. break;
  135. }
  136. stream.next();
  137. }
  138. return style;
  139. };
  140. }
  141. function doctype(depth) {
  142. return function(stream, state) {
  143. var ch;
  144. while ((ch = stream.next()) != null) {
  145. if (ch == "<") {
  146. state.tokenize = doctype(depth + 1);
  147. return state.tokenize(stream, state);
  148. } else if (ch == ">") {
  149. if (depth == 1) {
  150. state.tokenize = inText;
  151. break;
  152. } else {
  153. state.tokenize = doctype(depth - 1);
  154. return state.tokenize(stream, state);
  155. }
  156. }
  157. }
  158. return "meta";
  159. };
  160. }
  161. var curState, setStyle;
  162. function pass() {
  163. for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]);
  164. }
  165. function cont() {
  166. pass.apply(null, arguments);
  167. return true;
  168. }
  169. function pushContext(tagName, startOfLine) {
  170. var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent);
  171. curState.context = {
  172. prev: curState.context,
  173. tagName: tagName,
  174. indent: curState.indented,
  175. startOfLine: startOfLine,
  176. noIndent: noIndent
  177. };
  178. }
  179. function popContext() {
  180. if (curState.context) curState.context = curState.context.prev;
  181. }
  182. function element(type) {
  183. if (type == "openTag") {
  184. curState.tagName = tagName;
  185. return cont(attributes, endtag(curState.startOfLine));
  186. } else if (type == "closeTag") {
  187. var err = false;
  188. if (curState.context) {
  189. if (curState.context.tagName != tagName) {
  190. if (Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) {
  191. popContext();
  192. }
  193. err = !curState.context || curState.context.tagName != tagName;
  194. }
  195. } else {
  196. err = true;
  197. }
  198. if (err) setStyle = "error";
  199. return cont(endclosetag(err));
  200. }
  201. return cont();
  202. }
  203. function endtag(startOfLine) {
  204. return function(type) {
  205. var tagName = curState.tagName;
  206. curState.tagName = null;
  207. if (type == "selfcloseTag" ||
  208. (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(tagName.toLowerCase()))) {
  209. maybePopContext(tagName.toLowerCase());
  210. return cont();
  211. }
  212. if (type == "endTag") {
  213. maybePopContext(tagName.toLowerCase());
  214. pushContext(tagName, startOfLine);
  215. return cont();
  216. }
  217. return cont();
  218. };
  219. }
  220. function endclosetag(err) {
  221. return function(type) {
  222. if (err) setStyle = "error";
  223. if (type == "endTag") { popContext(); return cont(); }
  224. setStyle = "error";
  225. return cont(arguments.callee);
  226. };
  227. }
  228. function maybePopContext(nextTagName) {
  229. var parentTagName;
  230. while (true) {
  231. if (!curState.context) {
  232. return;
  233. }
  234. parentTagName = curState.context.tagName.toLowerCase();
  235. if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) ||
  236. !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
  237. return;
  238. }
  239. popContext();
  240. }
  241. }
  242. function attributes(type) {
  243. if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);}
  244. if (type == "endTag" || type == "selfcloseTag") return pass();
  245. setStyle = "error";
  246. return cont(attributes);
  247. }
  248. function attribute(type) {
  249. if (type == "equals") return cont(attvalue, attributes);
  250. if (!Kludges.allowMissing) setStyle = "error";
  251. else if (type == "word") setStyle = "attribute";
  252. return (type == "endTag" || type == "selfcloseTag") ? pass() : cont();
  253. }
  254. function attvalue(type) {
  255. if (type == "string") return cont(attvaluemaybe);
  256. if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();}
  257. setStyle = "error";
  258. return (type == "endTag" || type == "selfCloseTag") ? pass() : cont();
  259. }
  260. function attvaluemaybe(type) {
  261. if (type == "string") return cont(attvaluemaybe);
  262. else return pass();
  263. }
  264. return {
  265. startState: function() {
  266. return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, context: null};
  267. },
  268. token: function(stream, state) {
  269. if (stream.sol()) {
  270. state.startOfLine = true;
  271. state.indented = stream.indentation();
  272. }
  273. if (stream.eatSpace()) return null;
  274. setStyle = type = tagName = null;
  275. var style = state.tokenize(stream, state);
  276. state.type = type;
  277. if ((style || type) && style != "comment") {
  278. curState = state;
  279. while (true) {
  280. var comb = state.cc.pop() || element;
  281. if (comb(type || style)) break;
  282. }
  283. }
  284. state.startOfLine = false;
  285. return setStyle || style;
  286. },
  287. indent: function(state, textAfter, fullLine) {
  288. var context = state.context;
  289. if ((state.tokenize != inTag && state.tokenize != inText) ||
  290. context && context.noIndent)
  291. return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
  292. if (alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
  293. if (context && /^<\//.test(textAfter))
  294. context = context.prev;
  295. while (context && !context.startOfLine)
  296. context = context.prev;
  297. if (context) return context.indent + indentUnit;
  298. else return 0;
  299. },
  300. electricChars: "/",
  301. configuration: parserConfig.htmlMode ? "html" : "xml"
  302. };
  303. });
  304. CodeMirror.defineMIME("text/xml", "xml");
  305. CodeMirror.defineMIME("application/xml", "xml");
  306. if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
  307. CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});