Commit | Line | Data |
---|---|---|
837afb80 TD |
1 | (function(mod) { |
2 | if (typeof exports == "object" && typeof module == "object") // CommonJS | |
3 | mod(require("../../lib/codemirror")); | |
4 | else if (typeof define == "function" && define.amd) // AMD | |
5 | define(["../../lib/codemirror"], mod); | |
6 | else // Plain browser env | |
7 | mod(CodeMirror); | |
8 | })(function(CodeMirror) { | |
9 | "use strict"; | |
10 | ||
77b7b761 TD |
11 | CodeMirror.defineMode("xml", function(config, parserConfig) { |
12 | var indentUnit = config.indentUnit; | |
13 | var multilineTagIndentFactor = parserConfig.multilineTagIndentFactor || 1; | |
837afb80 TD |
14 | var multilineTagIndentPastTag = parserConfig.multilineTagIndentPastTag; |
15 | if (multilineTagIndentPastTag == null) multilineTagIndentPastTag = true; | |
77b7b761 TD |
16 | |
17 | var Kludges = parserConfig.htmlMode ? { | |
18 | autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true, | |
19 | 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true, | |
20 | 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true, | |
21 | 'track': true, 'wbr': true}, | |
22 | implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true, | |
23 | 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true, | |
24 | 'th': true, 'tr': true}, | |
25 | contextGrabbers: { | |
26 | 'dd': {'dd': true, 'dt': true}, | |
27 | 'dt': {'dd': true, 'dt': true}, | |
28 | 'li': {'li': true}, | |
29 | 'option': {'option': true, 'optgroup': true}, | |
30 | 'optgroup': {'optgroup': true}, | |
31 | 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true, | |
32 | 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true, | |
33 | 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true, | |
34 | 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true, | |
35 | 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true}, | |
36 | 'rp': {'rp': true, 'rt': true}, | |
37 | 'rt': {'rp': true, 'rt': true}, | |
38 | 'tbody': {'tbody': true, 'tfoot': true}, | |
39 | 'td': {'td': true, 'th': true}, | |
40 | 'tfoot': {'tbody': true}, | |
41 | 'th': {'td': true, 'th': true}, | |
42 | 'thead': {'tbody': true, 'tfoot': true}, | |
43 | 'tr': {'tr': true} | |
44 | }, | |
45 | doNotIndent: {"pre": true}, | |
46 | allowUnquoted: true, | |
837afb80 TD |
47 | allowMissing: true, |
48 | caseFold: true | |
77b7b761 TD |
49 | } : { |
50 | autoSelfClosers: {}, | |
51 | implicitlyClosed: {}, | |
52 | contextGrabbers: {}, | |
53 | doNotIndent: {}, | |
54 | allowUnquoted: false, | |
837afb80 TD |
55 | allowMissing: false, |
56 | caseFold: false | |
77b7b761 TD |
57 | }; |
58 | var alignCDATA = parserConfig.alignCDATA; | |
59 | ||
60 | // Return variables for tokenizers | |
837afb80 | 61 | var tagName, type, setStyle; |
77b7b761 TD |
62 | |
63 | function inText(stream, state) { | |
64 | function chain(parser) { | |
65 | state.tokenize = parser; | |
66 | return parser(stream, state); | |
67 | } | |
68 | ||
69 | var ch = stream.next(); | |
70 | if (ch == "<") { | |
71 | if (stream.eat("!")) { | |
72 | if (stream.eat("[")) { | |
73 | if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>")); | |
74 | else return null; | |
837afb80 TD |
75 | } else if (stream.match("--")) { |
76 | return chain(inBlock("comment", "-->")); | |
77 | } else if (stream.match("DOCTYPE", true, true)) { | |
77b7b761 TD |
78 | stream.eatWhile(/[\w\._\-]/); |
79 | return chain(doctype(1)); | |
837afb80 TD |
80 | } else { |
81 | return null; | |
77b7b761 | 82 | } |
837afb80 | 83 | } else if (stream.eat("?")) { |
77b7b761 TD |
84 | stream.eatWhile(/[\w\._\-]/); |
85 | state.tokenize = inBlock("meta", "?>"); | |
86 | return "meta"; | |
837afb80 | 87 | } else { |
77b7b761 TD |
88 | var isClose = stream.eat("/"); |
89 | tagName = ""; | |
90 | var c; | |
91 | while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c; | |
837afb80 TD |
92 | if (Kludges.caseFold) tagName = tagName.toLowerCase(); |
93 | if (!tagName) return "tag error"; | |
77b7b761 TD |
94 | type = isClose ? "closeTag" : "openTag"; |
95 | state.tokenize = inTag; | |
96 | return "tag"; | |
97 | } | |
837afb80 | 98 | } else if (ch == "&") { |
77b7b761 TD |
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"; | |
837afb80 | 110 | } else { |
77b7b761 TD |
111 | stream.eatWhile(/[^&<]/); |
112 | return null; | |
113 | } | |
114 | } | |
115 | ||
116 | function inTag(stream, state) { | |
117 | var ch = stream.next(); | |
118 | if (ch == ">" || (ch == "/" && stream.eat(">"))) { | |
119 | state.tokenize = inText; | |
120 | type = ch == ">" ? "endTag" : "selfcloseTag"; | |
121 | return "tag"; | |
837afb80 | 122 | } else if (ch == "=") { |
77b7b761 TD |
123 | type = "equals"; |
124 | return null; | |
837afb80 TD |
125 | } else if (ch == "<") { |
126 | state.tokenize = inText; | |
127 | state.state = baseState; | |
128 | state.tagName = state.tagStart = null; | |
129 | var next = state.tokenize(stream, state); | |
130 | return next ? next + " error" : "error"; | |
131 | } else if (/[\'\"]/.test(ch)) { | |
77b7b761 | 132 | state.tokenize = inAttribute(ch); |
837afb80 | 133 | state.stringStartCol = stream.column(); |
77b7b761 | 134 | return state.tokenize(stream, state); |
837afb80 | 135 | } else { |
77b7b761 TD |
136 | stream.eatWhile(/[^\s\u00a0=<>\"\']/); |
137 | return "word"; | |
138 | } | |
139 | } | |
140 | ||
141 | function inAttribute(quote) { | |
837afb80 | 142 | var closure = function(stream, state) { |
77b7b761 TD |
143 | while (!stream.eol()) { |
144 | if (stream.next() == quote) { | |
145 | state.tokenize = inTag; | |
146 | break; | |
147 | } | |
148 | } | |
149 | return "string"; | |
150 | }; | |
837afb80 TD |
151 | closure.isInAttribute = true; |
152 | return closure; | |
77b7b761 TD |
153 | } |
154 | ||
155 | function inBlock(style, terminator) { | |
156 | return function(stream, state) { | |
157 | while (!stream.eol()) { | |
158 | if (stream.match(terminator)) { | |
159 | state.tokenize = inText; | |
160 | break; | |
161 | } | |
162 | stream.next(); | |
163 | } | |
164 | return style; | |
165 | }; | |
166 | } | |
167 | function doctype(depth) { | |
168 | return function(stream, state) { | |
169 | var ch; | |
170 | while ((ch = stream.next()) != null) { | |
171 | if (ch == "<") { | |
172 | state.tokenize = doctype(depth + 1); | |
173 | return state.tokenize(stream, state); | |
174 | } else if (ch == ">") { | |
175 | if (depth == 1) { | |
176 | state.tokenize = inText; | |
177 | break; | |
178 | } else { | |
179 | state.tokenize = doctype(depth - 1); | |
180 | return state.tokenize(stream, state); | |
181 | } | |
182 | } | |
183 | } | |
184 | return "meta"; | |
185 | }; | |
186 | } | |
187 | ||
837afb80 TD |
188 | function Context(state, tagName, startOfLine) { |
189 | this.prev = state.context; | |
190 | this.tagName = tagName; | |
191 | this.indent = state.indented; | |
192 | this.startOfLine = startOfLine; | |
193 | if (Kludges.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent)) | |
194 | this.noIndent = true; | |
77b7b761 | 195 | } |
837afb80 TD |
196 | function popContext(state) { |
197 | if (state.context) state.context = state.context.prev; | |
77b7b761 | 198 | } |
837afb80 TD |
199 | function maybePopContext(state, nextTagName) { |
200 | var parentTagName; | |
201 | while (true) { | |
202 | if (!state.context) { | |
203 | return; | |
204 | } | |
205 | parentTagName = state.context.tagName; | |
206 | if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) || | |
207 | !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) { | |
208 | return; | |
209 | } | |
210 | popContext(state); | |
211 | } | |
77b7b761 TD |
212 | } |
213 | ||
837afb80 | 214 | function baseState(type, stream, state) { |
77b7b761 | 215 | if (type == "openTag") { |
837afb80 TD |
216 | state.tagName = tagName; |
217 | state.tagStart = stream.column(); | |
218 | return attrState; | |
77b7b761 TD |
219 | } else if (type == "closeTag") { |
220 | var err = false; | |
837afb80 TD |
221 | if (state.context) { |
222 | if (state.context.tagName != tagName) { | |
223 | if (Kludges.implicitlyClosed.hasOwnProperty(state.context.tagName)) | |
224 | popContext(state); | |
225 | err = !state.context || state.context.tagName != tagName; | |
77b7b761 TD |
226 | } |
227 | } else { | |
228 | err = true; | |
229 | } | |
230 | if (err) setStyle = "error"; | |
837afb80 TD |
231 | return err ? closeStateErr : closeState; |
232 | } else { | |
233 | return baseState; | |
77b7b761 | 234 | } |
77b7b761 | 235 | } |
837afb80 TD |
236 | |
237 | function closeState(type, _stream, state) { | |
238 | if (type != "endTag") { | |
77b7b761 | 239 | setStyle = "error"; |
837afb80 | 240 | return closeState; |
77b7b761 | 241 | } |
837afb80 TD |
242 | popContext(state); |
243 | return baseState; | |
244 | } | |
245 | function closeStateErr(type, stream, state) { | |
246 | setStyle = "error"; | |
247 | return closeState(type, stream, state); | |
77b7b761 TD |
248 | } |
249 | ||
837afb80 TD |
250 | function attrState(type, _stream, state) { |
251 | if (type == "word") { | |
252 | setStyle = "attribute"; | |
253 | return attrEqState; | |
254 | } else if (type == "endTag" || type == "selfcloseTag") { | |
255 | var tagName = state.tagName, tagStart = state.tagStart; | |
256 | state.tagName = state.tagStart = null; | |
257 | if (type == "selfcloseTag" || | |
258 | Kludges.autoSelfClosers.hasOwnProperty(tagName)) { | |
259 | maybePopContext(state, tagName); | |
260 | } else { | |
261 | maybePopContext(state, tagName); | |
262 | state.context = new Context(state, tagName, tagStart == state.indented); | |
263 | } | |
264 | return baseState; | |
265 | } | |
77b7b761 | 266 | setStyle = "error"; |
837afb80 | 267 | return attrState; |
77b7b761 | 268 | } |
837afb80 TD |
269 | function attrEqState(type, stream, state) { |
270 | if (type == "equals") return attrValueState; | |
77b7b761 | 271 | if (!Kludges.allowMissing) setStyle = "error"; |
837afb80 | 272 | return attrState(type, stream, state); |
77b7b761 | 273 | } |
837afb80 TD |
274 | function attrValueState(type, stream, state) { |
275 | if (type == "string") return attrContinuedState; | |
276 | if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return attrState;} | |
77b7b761 | 277 | setStyle = "error"; |
837afb80 | 278 | return attrState(type, stream, state); |
77b7b761 | 279 | } |
837afb80 TD |
280 | function attrContinuedState(type, stream, state) { |
281 | if (type == "string") return attrContinuedState; | |
282 | return attrState(type, stream, state); | |
77b7b761 TD |
283 | } |
284 | ||
285 | return { | |
286 | startState: function() { | |
837afb80 TD |
287 | return {tokenize: inText, |
288 | state: baseState, | |
289 | indented: 0, | |
290 | tagName: null, tagStart: null, | |
291 | context: null}; | |
77b7b761 TD |
292 | }, |
293 | ||
294 | token: function(stream, state) { | |
837afb80 | 295 | if (!state.tagName && stream.sol()) |
77b7b761 | 296 | state.indented = stream.indentation(); |
77b7b761 | 297 | |
837afb80 TD |
298 | if (stream.eatSpace()) return null; |
299 | tagName = type = null; | |
77b7b761 | 300 | var style = state.tokenize(stream, state); |
77b7b761 | 301 | if ((style || type) && style != "comment") { |
837afb80 TD |
302 | setStyle = null; |
303 | state.state = state.state(type || style, stream, state); | |
304 | if (setStyle) | |
305 | style = setStyle == "error" ? style + " error" : setStyle; | |
77b7b761 | 306 | } |
837afb80 | 307 | return style; |
77b7b761 TD |
308 | }, |
309 | ||
310 | indent: function(state, textAfter, fullLine) { | |
311 | var context = state.context; | |
837afb80 TD |
312 | // Indent multi-line strings (e.g. css). |
313 | if (state.tokenize.isInAttribute) { | |
314 | return state.stringStartCol + 1; | |
315 | } | |
316 | if (context && context.noIndent) return CodeMirror.Pass; | |
317 | if (state.tokenize != inTag && state.tokenize != inText) | |
77b7b761 | 318 | return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0; |
837afb80 TD |
319 | // Indent the starts of attribute names. |
320 | if (state.tagName) { | |
321 | if (multilineTagIndentPastTag) | |
322 | return state.tagStart + state.tagName.length + 2; | |
323 | else | |
324 | return state.tagStart + indentUnit * multilineTagIndentFactor; | |
325 | } | |
77b7b761 TD |
326 | if (alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0; |
327 | if (context && /^<\//.test(textAfter)) | |
328 | context = context.prev; | |
329 | while (context && !context.startOfLine) | |
330 | context = context.prev; | |
331 | if (context) return context.indent + indentUnit; | |
332 | else return 0; | |
333 | }, | |
334 | ||
335 | electricChars: "/", | |
336 | blockCommentStart: "<!--", | |
337 | blockCommentEnd: "-->", | |
338 | ||
837afb80 TD |
339 | configuration: parserConfig.htmlMode ? "html" : "xml", |
340 | helperType: parserConfig.htmlMode ? "html" : "xml" | |
77b7b761 TD |
341 | }; |
342 | }); | |
343 | ||
344 | CodeMirror.defineMIME("text/xml", "xml"); | |
345 | CodeMirror.defineMIME("application/xml", "xml"); | |
346 | if (!CodeMirror.mimeModes.hasOwnProperty("text/html")) | |
347 | CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true}); | |
837afb80 TD |
348 | |
349 | }); |