Mercurial
comparison .cms/lib/codemirror/mode/xml/xml.js @ 0:78edf6b517a0 draft
24.10
author | Coffee CMS <info@coffee-cms.ru> |
---|---|
date | Fri, 11 Oct 2024 22:40:23 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:78edf6b517a0 |
---|---|
1 // CodeMirror, copyright (c) by Marijn Haverbeke and others | |
2 // Distributed under an MIT license: https://codemirror.net/5/LICENSE | |
3 | |
4 (function(mod) { | |
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 | |
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 lower(tagName) { | |
191 return tagName && tagName.toLowerCase(); | |
192 } | |
193 | |
194 function Context(state, tagName, startOfLine) { | |
195 this.prev = state.context; | |
196 this.tagName = tagName || ""; | |
197 this.indent = state.indented; | |
198 this.startOfLine = startOfLine; | |
199 if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent)) | |
200 this.noIndent = true; | |
201 } | |
202 function popContext(state) { | |
203 if (state.context) state.context = state.context.prev; | |
204 } | |
205 function maybePopContext(state, nextTagName) { | |
206 var parentTagName; | |
207 while (true) { | |
208 if (!state.context) { | |
209 return; | |
210 } | |
211 parentTagName = state.context.tagName; | |
212 if (!config.contextGrabbers.hasOwnProperty(lower(parentTagName)) || | |
213 !config.contextGrabbers[lower(parentTagName)].hasOwnProperty(lower(nextTagName))) { | |
214 return; | |
215 } | |
216 popContext(state); | |
217 } | |
218 } | |
219 | |
220 function baseState(type, stream, state) { | |
221 if (type == "openTag") { | |
222 state.tagStart = stream.column(); | |
223 return tagNameState; | |
224 } else if (type == "closeTag") { | |
225 return closeTagNameState; | |
226 } else { | |
227 return baseState; | |
228 } | |
229 } | |
230 function tagNameState(type, stream, state) { | |
231 if (type == "word") { | |
232 state.tagName = stream.current(); | |
233 setStyle = "tag"; | |
234 return attrState; | |
235 } else if (config.allowMissingTagName && type == "endTag") { | |
236 setStyle = "tag bracket"; | |
237 return attrState(type, stream, state); | |
238 } else { | |
239 setStyle = "error"; | |
240 return tagNameState; | |
241 } | |
242 } | |
243 function closeTagNameState(type, stream, state) { | |
244 if (type == "word") { | |
245 var tagName = stream.current(); | |
246 if (state.context && state.context.tagName != tagName && | |
247 config.implicitlyClosed.hasOwnProperty(lower(state.context.tagName))) | |
248 popContext(state); | |
249 if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) { | |
250 setStyle = "tag"; | |
251 return closeState; | |
252 } else { | |
253 setStyle = "tag error"; | |
254 return closeStateErr; | |
255 } | |
256 } else if (config.allowMissingTagName && type == "endTag") { | |
257 setStyle = "tag bracket"; | |
258 return closeState(type, stream, state); | |
259 } else { | |
260 setStyle = "error"; | |
261 return closeStateErr; | |
262 } | |
263 } | |
264 | |
265 function closeState(type, _stream, state) { | |
266 if (type != "endTag") { | |
267 setStyle = "error"; | |
268 return closeState; | |
269 } | |
270 popContext(state); | |
271 return baseState; | |
272 } | |
273 function closeStateErr(type, stream, state) { | |
274 setStyle = "error"; | |
275 return closeState(type, stream, state); | |
276 } | |
277 | |
278 function attrState(type, _stream, state) { | |
279 if (type == "word") { | |
280 setStyle = "attribute"; | |
281 return attrEqState; | |
282 } else if (type == "endTag" || type == "selfcloseTag") { | |
283 var tagName = state.tagName, tagStart = state.tagStart; | |
284 state.tagName = state.tagStart = null; | |
285 if (type == "selfcloseTag" || | |
286 config.autoSelfClosers.hasOwnProperty(lower(tagName))) { | |
287 maybePopContext(state, tagName); | |
288 } else { | |
289 maybePopContext(state, tagName); | |
290 state.context = new Context(state, tagName, tagStart == state.indented); | |
291 } | |
292 return baseState; | |
293 } | |
294 setStyle = "error"; | |
295 return attrState; | |
296 } | |
297 function attrEqState(type, stream, state) { | |
298 if (type == "equals") return attrValueState; | |
299 if (!config.allowMissing) setStyle = "error"; | |
300 return attrState(type, stream, state); | |
301 } | |
302 function attrValueState(type, stream, state) { | |
303 if (type == "string") return attrContinuedState; | |
304 if (type == "word" && config.allowUnquoted) {setStyle = "string"; return attrState;} | |
305 setStyle = "error"; | |
306 return attrState(type, stream, state); | |
307 } | |
308 function attrContinuedState(type, stream, state) { | |
309 if (type == "string") return attrContinuedState; | |
310 return attrState(type, stream, state); | |
311 } | |
312 | |
313 return { | |
314 startState: function(baseIndent) { | |
315 var state = {tokenize: inText, | |
316 state: baseState, | |
317 indented: baseIndent || 0, | |
318 tagName: null, tagStart: null, | |
319 context: null} | |
320 if (baseIndent != null) state.baseIndent = baseIndent | |
321 return state | |
322 }, | |
323 | |
324 token: function(stream, state) { | |
325 if (!state.tagName && stream.sol()) | |
326 state.indented = stream.indentation(); | |
327 | |
328 if (stream.eatSpace()) return null; | |
329 type = null; | |
330 var style = state.tokenize(stream, state); | |
331 if ((style || type) && style != "comment") { | |
332 setStyle = null; | |
333 state.state = state.state(type || style, stream, state); | |
334 if (setStyle) | |
335 style = setStyle == "error" ? style + " error" : setStyle; | |
336 } | |
337 return style; | |
338 }, | |
339 | |
340 indent: function(state, textAfter, fullLine) { | |
341 var context = state.context; | |
342 // Indent multi-line strings (e.g. css). | |
343 if (state.tokenize.isInAttribute) { | |
344 if (state.tagStart == state.indented) | |
345 return state.stringStartCol + 1; | |
346 else | |
347 return state.indented + indentUnit; | |
348 } | |
349 if (context && context.noIndent) return CodeMirror.Pass; | |
350 if (state.tokenize != inTag && state.tokenize != inText) | |
351 return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0; | |
352 // Indent the starts of attribute names. | |
353 if (state.tagName) { | |
354 if (config.multilineTagIndentPastTag !== false) | |
355 return state.tagStart + state.tagName.length + 2; | |
356 else | |
357 return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1); | |
358 } | |
359 if (config.alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0; | |
360 var tagAfter = textAfter && /^<(\/)?([\w_:\.-]*)/.exec(textAfter); | |
361 if (tagAfter && tagAfter[1]) { // Closing tag spotted | |
362 while (context) { | |
363 if (context.tagName == tagAfter[2]) { | |
364 context = context.prev; | |
365 break; | |
366 } else if (config.implicitlyClosed.hasOwnProperty(lower(context.tagName))) { | |
367 context = context.prev; | |
368 } else { | |
369 break; | |
370 } | |
371 } | |
372 } else if (tagAfter) { // Opening tag spotted | |
373 while (context) { | |
374 var grabbers = config.contextGrabbers[lower(context.tagName)]; | |
375 if (grabbers && grabbers.hasOwnProperty(lower(tagAfter[2]))) | |
376 context = context.prev; | |
377 else | |
378 break; | |
379 } | |
380 } | |
381 while (context && context.prev && !context.startOfLine) | |
382 context = context.prev; | |
383 if (context) return context.indent + indentUnit; | |
384 else return state.baseIndent || 0; | |
385 }, | |
386 | |
387 electricInput: /<\/[\s\w:]+>$/, | |
388 blockCommentStart: "<!--", | |
389 blockCommentEnd: "-->", | |
390 | |
391 configuration: config.htmlMode ? "html" : "xml", | |
392 helperType: config.htmlMode ? "html" : "xml", | |
393 | |
394 skipAttribute: function(state) { | |
395 if (state.state == attrValueState) | |
396 state.state = attrState | |
397 }, | |
398 | |
399 xmlCurrentTag: function(state) { | |
400 return state.tagName ? {name: state.tagName, close: state.type == "closeTag"} : null | |
401 }, | |
402 | |
403 xmlCurrentContext: function(state) { | |
404 var context = [] | |
405 for (var cx = state.context; cx; cx = cx.prev) | |
406 context.push(cx.tagName) | |
407 return context.reverse() | |
408 } | |
409 }; | |
410 }); | |
411 | |
412 CodeMirror.defineMIME("text/xml", "xml"); | |
413 CodeMirror.defineMIME("application/xml", "xml"); | |
414 if (!CodeMirror.mimeModes.hasOwnProperty("text/html")) | |
415 CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true}); | |
416 | |
417 }); |