all repos — NoPaste @ de062991ccdf763b2091f26873047685f9c0c977

Resurrected - The PussTheCat.org fork of NoPaste

scripts/CodeMirror/mode/xml/xml.js (view raw)

  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("../../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
 14var 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
 48var xmlConfig = {
 49  autoSelfClosers: {},
 50  implicitlyClosed: {},
 51  contextGrabbers: {},
 52  doNotIndent: {},
 53  allowUnquoted: false,
 54  allowMissing: false,
 55  allowMissingTagName: false,
 56  caseFold: false
 57}
 58
 59CodeMirror.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
408CodeMirror.defineMIME("text/xml", "xml");
409CodeMirror.defineMIME("application/xml", "xml");
410if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
411  CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
412
413});