xml.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. CodeMirror.defineMode("xml", function(config, parserConfig) {
  2. var indentUnit = config.indentUnit;
  3. var Kludges = parserConfig.htmlMode ? {
  4. autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true,
  5. "meta": true, "col": true, "frame": true, "base": true, "area": true},
  6. doNotIndent: {"pre": true, "!cdata": true},
  7. allowUnquoted: true
  8. } : {autoSelfClosers: {}, doNotIndent: {"!cdata": true}, allowUnquoted: false};
  9. var alignCDATA = parserConfig.alignCDATA;
  10. // Return variables for tokenizers
  11. var tagName, type;
  12. function inText(stream, state) {
  13. function chain(parser) {
  14. state.tokenize = parser;
  15. return parser(stream, state);
  16. }
  17. var ch = stream.next();
  18. if (ch == "<") {
  19. if (stream.eat("!")) {
  20. if (stream.eat("[")) {
  21. if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
  22. else return null;
  23. }
  24. else if (stream.match("--")) return chain(inBlock("comment", "-->"));
  25. else if (stream.match("DOCTYPE", true, true)) {
  26. stream.eatWhile(/[\w\._\-]/);
  27. return chain(inBlock("meta", ">"));
  28. }
  29. else return null;
  30. }
  31. else if (stream.eat("?")) {
  32. stream.eatWhile(/[\w\._\-]/);
  33. state.tokenize = inBlock("meta", "?>");
  34. return "meta";
  35. }
  36. else {
  37. type = stream.eat("/") ? "closeTag" : "openTag";
  38. stream.eatSpace();
  39. tagName = "";
  40. var c;
  41. while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c;
  42. state.tokenize = inTag;
  43. return "tag";
  44. }
  45. }
  46. else if (ch == "&") {
  47. stream.eatWhile(/[^;]/);
  48. stream.eat(";");
  49. return "atom";
  50. }
  51. else {
  52. stream.eatWhile(/[^&<]/);
  53. return null;
  54. }
  55. }
  56. function inTag(stream, state) {
  57. var ch = stream.next();
  58. if (ch == ">" || (ch == "/" && stream.eat(">"))) {
  59. state.tokenize = inText;
  60. type = ch == ">" ? "endTag" : "selfcloseTag";
  61. return "tag";
  62. }
  63. else if (ch == "=") {
  64. type = "equals";
  65. return null;
  66. }
  67. else if (/[\'\"]/.test(ch)) {
  68. state.tokenize = inAttribute(ch);
  69. return state.tokenize(stream, state);
  70. }
  71. else {
  72. stream.eatWhile(/[^\s\u00a0=<>\"\'\/?]/);
  73. return "word";
  74. }
  75. }
  76. function inAttribute(quote) {
  77. return function(stream, state) {
  78. while (!stream.eol()) {
  79. if (stream.next() == quote) {
  80. state.tokenize = inTag;
  81. break;
  82. }
  83. }
  84. return "string";
  85. };
  86. }
  87. function inBlock(style, terminator) {
  88. return function(stream, state) {
  89. while (!stream.eol()) {
  90. if (stream.match(terminator)) {
  91. state.tokenize = inText;
  92. break;
  93. }
  94. stream.next();
  95. }
  96. return style;
  97. };
  98. }
  99. var curState, setStyle;
  100. function pass() {
  101. for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]);
  102. }
  103. function cont() {
  104. pass.apply(null, arguments);
  105. return true;
  106. }
  107. function pushContext(tagName, startOfLine) {
  108. var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent);
  109. curState.context = {
  110. prev: curState.context,
  111. tagName: tagName,
  112. indent: curState.indented,
  113. startOfLine: startOfLine,
  114. noIndent: noIndent
  115. };
  116. }
  117. function popContext() {
  118. if (curState.context) curState.context = curState.context.prev;
  119. }
  120. function element(type) {
  121. if (type == "openTag") {curState.tagName = tagName; return cont(attributes, endtag(curState.startOfLine));}
  122. else if (type == "closeTag") {
  123. var err = false;
  124. if (curState.context) {
  125. err = curState.context.tagName != tagName;
  126. popContext();
  127. } else {
  128. err = true;
  129. }
  130. if (err) setStyle = "error";
  131. return cont(endclosetag(err));
  132. }
  133. else if (type == "string") {
  134. if (!curState.context || curState.context.name != "!cdata") pushContext("!cdata");
  135. if (curState.tokenize == inText) popContext();
  136. return cont();
  137. }
  138. else return cont();
  139. }
  140. function endtag(startOfLine) {
  141. return function(type) {
  142. if (type == "selfcloseTag" ||
  143. (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase())))
  144. return cont();
  145. if (type == "endTag") {pushContext(curState.tagName, startOfLine); return cont();}
  146. return cont();
  147. };
  148. }
  149. function endclosetag(err) {
  150. return function(type) {
  151. if (err) setStyle = "error";
  152. if (type == "endTag") return cont();
  153. return pass();
  154. }
  155. }
  156. function attributes(type) {
  157. if (type == "word") {setStyle = "attribute"; return cont(attributes);}
  158. if (type == "equals") return cont(attvalue, attributes);
  159. return pass();
  160. }
  161. function attvalue(type) {
  162. if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();}
  163. if (type == "string") return cont(attvaluemaybe);
  164. return pass();
  165. }
  166. function attvaluemaybe(type) {
  167. if (type == "string") return cont(attvaluemaybe);
  168. else return pass();
  169. }
  170. return {
  171. startState: function() {
  172. return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, context: null};
  173. },
  174. token: function(stream, state) {
  175. if (stream.sol()) {
  176. state.startOfLine = true;
  177. state.indented = stream.indentation();
  178. }
  179. if (stream.eatSpace()) return null;
  180. setStyle = type = tagName = null;
  181. var style = state.tokenize(stream, state);
  182. if ((style || type) && style != "comment") {
  183. curState = state;
  184. while (true) {
  185. var comb = state.cc.pop() || element;
  186. if (comb(type || style)) break;
  187. }
  188. }
  189. state.startOfLine = false;
  190. return setStyle || style;
  191. },
  192. indent: function(state, textAfter) {
  193. var context = state.context;
  194. if (context && context.noIndent) return 0;
  195. if (alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
  196. if (context && /^<\//.test(textAfter))
  197. context = context.prev;
  198. while (context && !context.startOfLine)
  199. context = context.prev;
  200. if (context) return context.indent + indentUnit;
  201. else return 0;
  202. },
  203. compareStates: function(a, b) {
  204. if (a.indented != b.indented || a.tagName != b.tagName) return false;
  205. for (var ca = a.context, cb = b.context; ; ca = ca.prev, cb = cb.prev) {
  206. if (!ca || !cb) return ca == cb;
  207. if (ca.tagName != cb.tagName) return false;
  208. }
  209. },
  210. electricChars: "/"
  211. };
  212. });
  213. CodeMirror.defineMIME("application/xml", "xml");
  214. CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});