// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
|
(function(mod) {
|
if (typeof exports == "object" && typeof module == "object")
|
// CommonJS
|
mod(require("../../lib/codemirror"));
|
else if (typeof define == "function" && define.amd)
|
// AMD
|
define(["../../lib/codemirror"], mod);
|
// Plain browser env
|
else mod(CodeMirror);
|
})(function(CodeMirror) {
|
"use strict";
|
|
var htmlConfig = {
|
autoSelfClosers: {
|
area: true,
|
base: true,
|
br: true,
|
col: true,
|
command: true,
|
embed: true,
|
frame: true,
|
hr: true,
|
img: true,
|
input: true,
|
keygen: true,
|
link: true,
|
meta: true,
|
param: true,
|
source: true,
|
track: true,
|
wbr: true,
|
menuitem: true,
|
},
|
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,
|
},
|
contextGrabbers: {
|
dd: { dd: true, dt: true },
|
dt: { dd: true, dt: true },
|
li: { li: true },
|
option: { option: true, optgroup: true },
|
optgroup: { optgroup: true },
|
p: {
|
address: true,
|
article: true,
|
aside: true,
|
blockquote: true,
|
dir: true,
|
div: true,
|
dl: true,
|
fieldset: true,
|
footer: true,
|
form: true,
|
h1: true,
|
h2: true,
|
h3: true,
|
h4: true,
|
h5: true,
|
h6: true,
|
header: true,
|
hgroup: true,
|
hr: true,
|
menu: true,
|
nav: true,
|
ol: true,
|
p: true,
|
pre: true,
|
section: true,
|
table: true,
|
ul: true,
|
},
|
rp: { rp: true, rt: true },
|
rt: { rp: true, rt: true },
|
tbody: { tbody: true, tfoot: true },
|
td: { td: true, th: true },
|
tfoot: { tbody: true },
|
th: { td: true, th: true },
|
thead: { tbody: true, tfoot: true },
|
tr: { tr: true },
|
},
|
doNotIndent: { pre: true },
|
allowUnquoted: true,
|
allowMissing: true,
|
caseFold: true,
|
};
|
|
var xmlConfig = {
|
autoSelfClosers: {},
|
implicitlyClosed: {},
|
contextGrabbers: {},
|
doNotIndent: {},
|
allowUnquoted: false,
|
allowMissing: false,
|
allowMissingTagName: false,
|
caseFold: false,
|
};
|
|
CodeMirror.defineMode("xml", function(editorConf, config_) {
|
var indentUnit = editorConf.indentUnit;
|
var config = {};
|
var defaults = config_.htmlMode ? htmlConfig : xmlConfig;
|
for (var prop in defaults) config[prop] = defaults[prop];
|
for (var prop in config_) config[prop] = config_[prop];
|
|
// Return variables for tokenizers
|
var type, setStyle;
|
|
function inText(stream, state) {
|
function chain(parser) {
|
state.tokenize = parser;
|
return parser(stream, state);
|
}
|
|
var ch = stream.next();
|
if (ch == "<") {
|
if (stream.eat("!")) {
|
if (stream.eat("[")) {
|
if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
|
else return null;
|
} else if (stream.match("--")) {
|
return chain(inBlock("comment", "-->"));
|
} else if (stream.match("DOCTYPE", true, true)) {
|
stream.eatWhile(/[\w\._\-]/);
|
return chain(doctype(1));
|
} else {
|
return null;
|
}
|
} else if (stream.eat("?")) {
|
stream.eatWhile(/[\w\._\-]/);
|
state.tokenize = inBlock("meta", "?>");
|
return "meta";
|
} else {
|
type = stream.eat("/") ? "closeTag" : "openTag";
|
state.tokenize = inTag;
|
return "tag bracket";
|
}
|
} else if (ch == "&") {
|
var ok;
|
if (stream.eat("#")) {
|
if (stream.eat("x")) {
|
ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
|
} else {
|
ok = stream.eatWhile(/[\d]/) && stream.eat(";");
|
}
|
} else {
|
ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
|
}
|
return ok ? "atom" : "error";
|
} else {
|
stream.eatWhile(/[^&<]/);
|
return null;
|
}
|
}
|
inText.isInText = true;
|
|
function inTag(stream, state) {
|
var ch = stream.next();
|
if (ch == ">" || (ch == "/" && stream.eat(">"))) {
|
state.tokenize = inText;
|
type = ch == ">" ? "endTag" : "selfcloseTag";
|
return "tag bracket";
|
} else if (ch == "=") {
|
type = "equals";
|
return null;
|
} else if (ch == "<") {
|
state.tokenize = inText;
|
state.state = baseState;
|
state.tagName = state.tagStart = null;
|
var next = state.tokenize(stream, state);
|
return next ? next + " tag error" : "tag error";
|
} else if (/[\'\"]/.test(ch)) {
|
state.tokenize = inAttribute(ch);
|
state.stringStartCol = stream.column();
|
return state.tokenize(stream, state);
|
} else {
|
stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/);
|
return "word";
|
}
|
}
|
|
function inAttribute(quote) {
|
var closure = function(stream, state) {
|
while (!stream.eol()) {
|
if (stream.next() == quote) {
|
state.tokenize = inTag;
|
break;
|
}
|
}
|
return "string";
|
};
|
closure.isInAttribute = true;
|
return closure;
|
}
|
|
function inBlock(style, terminator) {
|
return function(stream, state) {
|
while (!stream.eol()) {
|
if (stream.match(terminator)) {
|
state.tokenize = inText;
|
break;
|
}
|
stream.next();
|
}
|
return style;
|
};
|
}
|
|
function doctype(depth) {
|
return function(stream, state) {
|
var ch;
|
while ((ch = stream.next()) != null) {
|
if (ch == "<") {
|
state.tokenize = doctype(depth + 1);
|
return state.tokenize(stream, state);
|
} else if (ch == ">") {
|
if (depth == 1) {
|
state.tokenize = inText;
|
break;
|
} else {
|
state.tokenize = doctype(depth - 1);
|
return state.tokenize(stream, state);
|
}
|
}
|
}
|
return "meta";
|
};
|
}
|
|
function Context(state, tagName, startOfLine) {
|
this.prev = state.context;
|
this.tagName = tagName;
|
this.indent = state.indented;
|
this.startOfLine = startOfLine;
|
if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent)) this.noIndent = true;
|
}
|
function popContext(state) {
|
if (state.context) state.context = state.context.prev;
|
}
|
function maybePopContext(state, nextTagName) {
|
var parentTagName;
|
while (true) {
|
if (!state.context) {
|
return;
|
}
|
parentTagName = state.context.tagName;
|
if (
|
!config.contextGrabbers.hasOwnProperty(parentTagName) ||
|
!config.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)
|
) {
|
return;
|
}
|
popContext(state);
|
}
|
}
|
|
function baseState(type, stream, state) {
|
if (type == "openTag") {
|
state.tagStart = stream.column();
|
return tagNameState;
|
} else if (type == "closeTag") {
|
return closeTagNameState;
|
} else {
|
return baseState;
|
}
|
}
|
function tagNameState(type, stream, state) {
|
if (type == "word") {
|
state.tagName = stream.current();
|
setStyle = "tag";
|
return attrState;
|
} else if (config.allowMissingTagName && type == "endTag") {
|
setStyle = "tag bracket";
|
return attrState(type, stream, state);
|
} else {
|
setStyle = "error";
|
return tagNameState;
|
}
|
}
|
function closeTagNameState(type, stream, state) {
|
if (type == "word") {
|
var tagName = stream.current();
|
if (
|
state.context &&
|
state.context.tagName != tagName &&
|
config.implicitlyClosed.hasOwnProperty(state.context.tagName)
|
)
|
popContext(state);
|
if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) {
|
setStyle = "tag";
|
return closeState;
|
} else {
|
setStyle = "tag error";
|
return closeStateErr;
|
}
|
} else if (config.allowMissingTagName && type == "endTag") {
|
setStyle = "tag bracket";
|
return closeState(type, stream, state);
|
} else {
|
setStyle = "error";
|
return closeStateErr;
|
}
|
}
|
|
function closeState(type, _stream, state) {
|
if (type != "endTag") {
|
setStyle = "error";
|
return closeState;
|
}
|
popContext(state);
|
return baseState;
|
}
|
function closeStateErr(type, stream, state) {
|
setStyle = "error";
|
return closeState(type, stream, state);
|
}
|
|
function attrState(type, _stream, state) {
|
if (type == "word") {
|
setStyle = "attribute";
|
return attrEqState;
|
} else if (type == "endTag" || type == "selfcloseTag") {
|
var tagName = state.tagName,
|
tagStart = state.tagStart;
|
state.tagName = state.tagStart = null;
|
if (type == "selfcloseTag" || config.autoSelfClosers.hasOwnProperty(tagName)) {
|
maybePopContext(state, tagName);
|
} else {
|
maybePopContext(state, tagName);
|
state.context = new Context(state, tagName, tagStart == state.indented);
|
}
|
return baseState;
|
}
|
setStyle = "error";
|
return attrState;
|
}
|
function attrEqState(type, stream, state) {
|
if (type == "equals") return attrValueState;
|
if (!config.allowMissing) setStyle = "error";
|
return attrState(type, stream, state);
|
}
|
function attrValueState(type, stream, state) {
|
if (type == "string") return attrContinuedState;
|
if (type == "word" && config.allowUnquoted) {
|
setStyle = "string";
|
return attrState;
|
}
|
setStyle = "error";
|
return attrState(type, stream, state);
|
}
|
function attrContinuedState(type, stream, state) {
|
if (type == "string") return attrContinuedState;
|
return attrState(type, stream, state);
|
}
|
|
return {
|
startState: function(baseIndent) {
|
var state = {
|
tokenize: inText,
|
state: baseState,
|
indented: baseIndent || 0,
|
tagName: null,
|
tagStart: null,
|
context: null,
|
};
|
if (baseIndent != null) state.baseIndent = baseIndent;
|
return state;
|
},
|
|
token: function(stream, state) {
|
if (!state.tagName && stream.sol()) state.indented = stream.indentation();
|
|
if (stream.eatSpace()) return null;
|
type = null;
|
var style = state.tokenize(stream, state);
|
if ((style || type) && style != "comment") {
|
setStyle = null;
|
state.state = state.state(type || style, stream, state);
|
if (setStyle) style = setStyle == "error" ? style + " error" : setStyle;
|
}
|
return style;
|
},
|
|
indent: function(state, textAfter, fullLine) {
|
var context = state.context;
|
// Indent multi-line strings (e.g. css).
|
if (state.tokenize.isInAttribute) {
|
if (state.tagStart == state.indented) return state.stringStartCol + 1;
|
else return state.indented + indentUnit;
|
}
|
if (context && context.noIndent) return CodeMirror.Pass;
|
if (state.tokenize != inTag && state.tokenize != inText)
|
return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
|
// Indent the starts of attribute names.
|
if (state.tagName) {
|
if (config.multilineTagIndentPastTag !== false) return state.tagStart + state.tagName.length + 2;
|
else return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1);
|
}
|
if (config.alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
|
var tagAfter = textAfter && /^<(\/)?([\w_:\.-]*)/.exec(textAfter);
|
if (tagAfter && tagAfter[1]) {
|
// Closing tag spotted
|
while (context) {
|
if (context.tagName == tagAfter[2]) {
|
context = context.prev;
|
break;
|
} else if (config.implicitlyClosed.hasOwnProperty(context.tagName)) {
|
context = context.prev;
|
} else {
|
break;
|
}
|
}
|
} else if (tagAfter) {
|
// Opening tag spotted
|
while (context) {
|
var grabbers = config.contextGrabbers[context.tagName];
|
if (grabbers && grabbers.hasOwnProperty(tagAfter[2])) context = context.prev;
|
else break;
|
}
|
}
|
while (context && context.prev && !context.startOfLine) context = context.prev;
|
if (context) return context.indent + indentUnit;
|
else return state.baseIndent || 0;
|
},
|
|
electricInput: /<\/[\s\w:]+>$/,
|
blockCommentStart: "<!--",
|
blockCommentEnd: "-->",
|
|
configuration: config.htmlMode ? "html" : "xml",
|
helperType: config.htmlMode ? "html" : "xml",
|
|
skipAttribute: function(state) {
|
if (state.state == attrValueState) state.state = attrState;
|
},
|
};
|
});
|
|
CodeMirror.defineMIME("text/xml", "xml");
|
CodeMirror.defineMIME("application/xml", "xml");
|
if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
|
CodeMirror.defineMIME("text/html", { name: "xml", htmlMode: true });
|
});
|