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