1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 // Distributed under an MIT license: http://codemirror.net/LICENSE
5 if (typeof exports
== "object" && typeof module
== "object") // CommonJS
6 mod(require("../../lib/codemirror"));
7 else if (typeof define
== "function" && define
.amd
) // AMD
8 define(["../../lib/codemirror"], mod
);
9 else // Plain browser env
11 })(function(CodeMirror
) {
14 CodeMirror
.defineMode("xml", function(config
, parserConfig
) {
15 var indentUnit
= config
.indentUnit
;
16 var multilineTagIndentFactor
= parserConfig
.multilineTagIndentFactor
|| 1;
17 var multilineTagIndentPastTag
= parserConfig
.multilineTagIndentPastTag
;
18 if (multilineTagIndentPastTag
== null) multilineTagIndentPastTag
= true;
20 var Kludges
= parserConfig
.htmlMode
? {
21 autoSelfClosers
: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
22 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
23 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
24 'track': true, 'wbr': true},
25 implicitlyClosed
: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
26 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
27 'th': true, 'tr': true},
29 'dd': {'dd': true, 'dt': true},
30 'dt': {'dd': true, 'dt': true},
32 'option': {'option': true, 'optgroup': true},
33 'optgroup': {'optgroup': true},
34 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
35 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
36 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
37 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
38 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
39 'rp': {'rp': true, 'rt': true},
40 'rt': {'rp': true, 'rt': true},
41 'tbody': {'tbody': true, 'tfoot': true},
42 'td': {'td': true, 'th': true},
43 'tfoot': {'tbody': true},
44 'th': {'td': true, 'th': true},
45 'thead': {'tbody': true, 'tfoot': true},
48 doNotIndent
: {"pre": true},
61 var alignCDATA
= parserConfig
.alignCDATA
;
63 // Return variables for tokenizers
66 function inText(stream
, state
) {
67 function chain(parser
) {
68 state
.tokenize
= parser
;
69 return parser(stream
, state
);
72 var ch
= stream
.next();
74 if (stream
.eat("!")) {
75 if (stream
.eat("[")) {
76 if (stream
.match("CDATA[")) return chain(inBlock("atom", "]]>"));
78 } else if (stream
.match("--")) {
79 return chain(inBlock("comment", "-->"));
80 } else if (stream
.match("DOCTYPE", true, true)) {
81 stream
.eatWhile(/[\w\._\-]/);
82 return chain(doctype(1));
86 } else if (stream
.eat("?")) {
87 stream
.eatWhile(/[\w\._\-]/);
88 state
.tokenize
= inBlock("meta", "?>");
91 type
= stream
.eat("/") ? "closeTag" : "openTag";
92 state
.tokenize
= inTag
;
95 } else if (ch
== "&") {
97 if (stream
.eat("#")) {
98 if (stream
.eat("x")) {
99 ok
= stream
.eatWhile(/[a-fA-F\d]/) && stream
.eat(";");
101 ok
= stream
.eatWhile(/[\d]/) && stream
.eat(";");
104 ok
= stream
.eatWhile(/[\w\.\-:]/) && stream
.eat(";");
106 return ok
? "atom" : "error";
108 stream
.eatWhile(/[^&<]/);
113 function inTag(stream
, state
) {
114 var ch
= stream
.next();
115 if (ch
== ">" || (ch
== "/" && stream
.eat(">"))) {
116 state
.tokenize
= inText
;
117 type
= ch
== ">" ? "endTag" : "selfcloseTag";
118 return "tag bracket";
119 } else if (ch
== "=") {
122 } else if (ch
== "<") {
123 state
.tokenize
= inText
;
124 state
.state
= baseState
;
125 state
.tagName
= state
.tagStart
= null;
126 var next
= state
.tokenize(stream
, state
);
127 return next
? next
+ " tag error" : "tag error";
128 } else if (/[\'\"]/.test(ch
)) {
129 state
.tokenize
= inAttribute(ch
);
130 state
.stringStartCol
= stream
.column();
131 return state
.tokenize(stream
, state
);
133 stream
.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/);
138 function inAttribute(quote
) {
139 var closure = function(stream
, state
) {
140 while (!stream
.eol()) {
141 if (stream
.next() == quote
) {
142 state
.tokenize
= inTag
;
148 closure
.isInAttribute
= true;
152 function inBlock(style
, terminator
) {
153 return function(stream
, state
) {
154 while (!stream
.eol()) {
155 if (stream
.match(terminator
)) {
156 state
.tokenize
= inText
;
164 function doctype(depth
) {
165 return function(stream
, state
) {
167 while ((ch
= stream
.next()) != null) {
169 state
.tokenize
= doctype(depth
+ 1);
170 return state
.tokenize(stream
, state
);
171 } else if (ch
== ">") {
173 state
.tokenize
= inText
;
176 state
.tokenize
= doctype(depth
- 1);
177 return state
.tokenize(stream
, state
);
185 function Context(state
, tagName
, startOfLine
) {
186 this.prev
= state
.context
;
187 this.tagName
= tagName
;
188 this.indent
= state
.indented
;
189 this.startOfLine
= startOfLine
;
190 if (Kludges
.doNotIndent
.hasOwnProperty(tagName
) || (state
.context
&& state
.context
.noIndent
))
191 this.noIndent
= true;
193 function popContext(state
) {
194 if (state
.context
) state
.context
= state
.context
.prev
;
196 function maybePopContext(state
, nextTagName
) {
199 if (!state
.context
) {
202 parentTagName
= state
.context
.tagName
;
203 if (!Kludges
.contextGrabbers
.hasOwnProperty(parentTagName
) ||
204 !Kludges
.contextGrabbers
[parentTagName
].hasOwnProperty(nextTagName
)) {
211 function baseState(type
, stream
, state
) {
212 if (type
== "openTag") {
213 state
.tagStart
= stream
.column();
215 } else if (type
== "closeTag") {
216 return closeTagNameState
;
221 function tagNameState(type
, stream
, state
) {
222 if (type
== "word") {
223 state
.tagName
= stream
.current();
231 function closeTagNameState(type
, stream
, state
) {
232 if (type
== "word") {
233 var tagName
= stream
.current();
234 if (state
.context
&& state
.context
.tagName
!= tagName
&&
235 Kludges
.implicitlyClosed
.hasOwnProperty(state
.context
.tagName
))
237 if (state
.context
&& state
.context
.tagName
== tagName
) {
241 setStyle
= "tag error";
242 return closeStateErr
;
246 return closeStateErr
;
250 function closeState(type
, _stream
, state
) {
251 if (type
!= "endTag") {
258 function closeStateErr(type
, stream
, state
) {
260 return closeState(type
, stream
, state
);
263 function attrState(type
, _stream
, state
) {
264 if (type
== "word") {
265 setStyle
= "attribute";
267 } else if (type
== "endTag" || type
== "selfcloseTag") {
268 var tagName
= state
.tagName
, tagStart
= state
.tagStart
;
269 state
.tagName
= state
.tagStart
= null;
270 if (type
== "selfcloseTag" ||
271 Kludges
.autoSelfClosers
.hasOwnProperty(tagName
)) {
272 maybePopContext(state
, tagName
);
274 maybePopContext(state
, tagName
);
275 state
.context
= new Context(state
, tagName
, tagStart
== state
.indented
);
282 function attrEqState(type
, stream
, state
) {
283 if (type
== "equals") return attrValueState
;
284 if (!Kludges
.allowMissing
) setStyle
= "error";
285 return attrState(type
, stream
, state
);
287 function attrValueState(type
, stream
, state
) {
288 if (type
== "string") return attrContinuedState
;
289 if (type
== "word" && Kludges
.allowUnquoted
) {setStyle
= "string"; return attrState
;}
291 return attrState(type
, stream
, state
);
293 function attrContinuedState(type
, stream
, state
) {
294 if (type
== "string") return attrContinuedState
;
295 return attrState(type
, stream
, state
);
299 startState: function() {
300 return {tokenize
: inText
,
303 tagName
: null, tagStart
: null,
307 token: function(stream
, state
) {
308 if (!state
.tagName
&& stream
.sol())
309 state
.indented
= stream
.indentation();
311 if (stream
.eatSpace()) return null;
313 var style
= state
.tokenize(stream
, state
);
314 if ((style
|| type
) && style
!= "comment") {
316 state
.state
= state
.state(type
|| style
, stream
, state
);
318 style
= setStyle
== "error" ? style
+ " error" : setStyle
;
323 indent: function(state
, textAfter
, fullLine
) {
324 var context
= state
.context
;
325 // Indent multi-line strings (e.g. css).
326 if (state
.tokenize
.isInAttribute
) {
327 if (state
.tagStart
== state
.indented
)
328 return state
.stringStartCol
+ 1;
330 return state
.indented
+ indentUnit
;
332 if (context
&& context
.noIndent
) return CodeMirror
.Pass
;
333 if (state
.tokenize
!= inTag
&& state
.tokenize
!= inText
)
334 return fullLine
? fullLine
.match(/^(\s*)/)[0].length
: 0;
335 // Indent the starts of attribute names.
337 if (multilineTagIndentPastTag
)
338 return state
.tagStart
+ state
.tagName
.length
+ 2;
340 return state
.tagStart
+ indentUnit
* multilineTagIndentFactor
;
342 if (alignCDATA
&& /<!\[CDATA\[/.test(textAfter
)) return 0;
343 var tagAfter
= textAfter
&& /^<(\/)?([\w_:\.-]*)/.exec(textAfter
);
344 if (tagAfter
&& tagAfter
[1]) { // Closing tag spotted
346 if (context
.tagName
== tagAfter
[2]) {
347 context
= context
.prev
;
349 } else if (Kludges
.implicitlyClosed
.hasOwnProperty(context
.tagName
)) {
350 context
= context
.prev
;
355 } else if (tagAfter
) { // Opening tag spotted
357 var grabbers
= Kludges
.contextGrabbers
[context
.tagName
];
358 if (grabbers
&& grabbers
.hasOwnProperty(tagAfter
[2]))
359 context
= context
.prev
;
364 while (context
&& !context
.startOfLine
)
365 context
= context
.prev
;
366 if (context
) return context
.indent
+ indentUnit
;
370 electricInput
: /<\/[\s\w:]+>$/,
371 blockCommentStart
: "<!--",
372 blockCommentEnd
: "-->",
374 configuration
: parserConfig
.htmlMode
? "html" : "xml",
375 helperType
: parserConfig
.htmlMode
? "html" : "xml"
379 CodeMirror
.defineMIME("text/xml", "xml");
380 CodeMirror
.defineMIME("application/xml", "xml");
381 if (!CodeMirror
.mimeModes
.hasOwnProperty("text/html"))
382 CodeMirror
.defineMIME("text/html", {name
: "xml", htmlMode
: true});