Dotclear

source: plugins/themeEditor/codemirror/xml.js @ 948:206fd3d17d64

Revision 948:206fd3d17d64, 9.7 KB checked in by franck <carnet.franck.paul@…>, 13 years ago (diff)

Add syntax color option to theme editor plugin, using CodeMirror? ( http://codemirror.net/), fixes #628

Line 
1CodeMirror.defineMode("xml", function(config, parserConfig) {
2  var indentUnit = config.indentUnit;
3  var Kludges = parserConfig.htmlMode ? {
4    autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
5                      'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
6                      'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
7                      'track': true, 'wbr': true},
8    implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
9                       'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
10                       'th': true, 'tr': true},
11    contextGrabbers: {
12      'dd': {'dd': true, 'dt': true},
13      'dt': {'dd': true, 'dt': true},
14      'li': {'li': true},
15      'option': {'option': true, 'optgroup': true},
16      'optgroup': {'optgroup': true},
17      'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
18            'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
19            'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
20            'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
21            'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
22      'rp': {'rp': true, 'rt': true},
23      'rt': {'rp': true, 'rt': true},
24      'tbody': {'tbody': true, 'tfoot': true},
25      'td': {'td': true, 'th': true},
26      'tfoot': {'tbody': true},
27      'th': {'td': true, 'th': true},
28      'thead': {'tbody': true, 'tfoot': true},
29      'tr': {'tr': true}
30    },
31    doNotIndent: {"pre": true},
32    allowUnquoted: true,
33    allowMissing: true
34  } : {
35    autoSelfClosers: {},
36    implicitlyClosed: {},
37    contextGrabbers: {},
38    doNotIndent: {},
39    allowUnquoted: false,
40    allowMissing: false
41  };
42  var alignCDATA = parserConfig.alignCDATA;
43
44  // Return variables for tokenizers
45  var tagName, type;
46
47  function inText(stream, state) {
48    function chain(parser) {
49      state.tokenize = parser;
50      return parser(stream, state);
51    }
52
53    var ch = stream.next();
54    if (ch == "<") {
55      if (stream.eat("!")) {
56        if (stream.eat("[")) {
57          if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
58          else return null;
59        }
60        else if (stream.match("--")) return chain(inBlock("comment", "-->"));
61        else if (stream.match("DOCTYPE", true, true)) {
62          stream.eatWhile(/[\w\._\-]/);
63          return chain(doctype(1));
64        }
65        else return null;
66      }
67      else if (stream.eat("?")) {
68        stream.eatWhile(/[\w\._\-]/);
69        state.tokenize = inBlock("meta", "?>");
70        return "meta";
71      }
72      else {
73        type = stream.eat("/") ? "closeTag" : "openTag";
74        stream.eatSpace();
75        tagName = "";
76        var c;
77        while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c;
78        state.tokenize = inTag;
79        return "tag";
80      }
81    }
82    else if (ch == "&") {
83      var ok;
84      if (stream.eat("#")) {
85        if (stream.eat("x")) {
86          ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");         
87        } else {
88          ok = stream.eatWhile(/[\d]/) && stream.eat(";");
89        }
90      } else {
91        ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
92      }
93      return ok ? "atom" : "error";
94    }
95    else {
96      stream.eatWhile(/[^&<]/);
97      return null;
98    }
99  }
100
101  function inTag(stream, state) {
102    var ch = stream.next();
103    if (ch == ">" || (ch == "/" && stream.eat(">"))) {
104      state.tokenize = inText;
105      type = ch == ">" ? "endTag" : "selfcloseTag";
106      return "tag";
107    }
108    else if (ch == "=") {
109      type = "equals";
110      return null;
111    }
112    else if (/[\'\"]/.test(ch)) {
113      state.tokenize = inAttribute(ch);
114      return state.tokenize(stream, state);
115    }
116    else {
117      stream.eatWhile(/[^\s\u00a0=<>\"\'\/?]/);
118      return "word";
119    }
120  }
121
122  function inAttribute(quote) {
123    return function(stream, state) {
124      while (!stream.eol()) {
125        if (stream.next() == quote) {
126          state.tokenize = inTag;
127          break;
128        }
129      }
130      return "string";
131    };
132  }
133
134  function inBlock(style, terminator) {
135    return function(stream, state) {
136      while (!stream.eol()) {
137        if (stream.match(terminator)) {
138          state.tokenize = inText;
139          break;
140        }
141        stream.next();
142      }
143      return style;
144    };
145  }
146  function doctype(depth) {
147    return function(stream, state) {
148      var ch;
149      while ((ch = stream.next()) != null) {
150        if (ch == "<") {
151          state.tokenize = doctype(depth + 1);
152          return state.tokenize(stream, state);
153        } else if (ch == ">") {
154          if (depth == 1) {
155            state.tokenize = inText;
156            break;
157          } else {
158            state.tokenize = doctype(depth - 1);
159            return state.tokenize(stream, state);
160          }
161        }
162      }
163      return "meta";
164    };
165  }
166
167  var curState, setStyle;
168  function pass() {
169    for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]);
170  }
171  function cont() {
172    pass.apply(null, arguments);
173    return true;
174  }
175
176  function pushContext(tagName, startOfLine) {
177    var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent);
178    curState.context = {
179      prev: curState.context,
180      tagName: tagName,
181      indent: curState.indented,
182      startOfLine: startOfLine,
183      noIndent: noIndent
184    };
185  }
186  function popContext() {
187    if (curState.context) curState.context = curState.context.prev;
188  }
189
190  function element(type) {
191    if (type == "openTag") {
192      curState.tagName = tagName;
193      return cont(attributes, endtag(curState.startOfLine));
194    } else if (type == "closeTag") {
195      var err = false;
196      if (curState.context) {
197        if (curState.context.tagName != tagName) {
198          if (Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) {
199            popContext();
200          }
201          err = !curState.context || curState.context.tagName != tagName;
202        }
203      } else {
204        err = true;
205      }
206      if (err) setStyle = "error";
207      return cont(endclosetag(err));
208    }
209    return cont();
210  }
211  function endtag(startOfLine) {
212    return function(type) {
213      if (type == "selfcloseTag" ||
214          (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase()))) {
215        maybePopContext(curState.tagName.toLowerCase());
216        return cont();
217      }
218      if (type == "endTag") {
219        maybePopContext(curState.tagName.toLowerCase());
220        pushContext(curState.tagName, startOfLine);
221        return cont();
222      }
223      return cont();
224    };
225  }
226  function endclosetag(err) {
227    return function(type) {
228      if (err) setStyle = "error";
229      if (type == "endTag") { popContext(); return cont(); }
230      setStyle = "error";
231      return cont(arguments.callee);
232    };
233  }
234  function maybePopContext(nextTagName) {
235    var parentTagName;
236    while (true) {
237      if (!curState.context) {
238        return;
239      }
240      parentTagName = curState.context.tagName.toLowerCase();
241      if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) ||
242          !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
243        return;
244      }
245      popContext();
246    }
247  }
248
249  function attributes(type) {
250    if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);}
251    if (type == "endTag" || type == "selfcloseTag") return pass();
252    setStyle = "error";
253    return cont(attributes);
254  }
255  function attribute(type) {
256    if (type == "equals") return cont(attvalue, attributes);
257    if (!Kludges.allowMissing) setStyle = "error";
258    return (type == "endTag" || type == "selfcloseTag") ? pass() : cont();
259  }
260  function attvalue(type) {
261    if (type == "string") return cont(attvaluemaybe);
262    if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();}
263    setStyle = "error";
264    return (type == "endTag" || type == "selfCloseTag") ? pass() : cont();
265  }
266  function attvaluemaybe(type) {
267    if (type == "string") return cont(attvaluemaybe);
268    else return pass();
269  }
270
271  return {
272    startState: function() {
273      return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, context: null};
274    },
275
276    token: function(stream, state) {
277      if (stream.sol()) {
278        state.startOfLine = true;
279        state.indented = stream.indentation();
280      }
281      if (stream.eatSpace()) return null;
282
283      setStyle = type = tagName = null;
284      var style = state.tokenize(stream, state);
285      state.type = type;
286      if ((style || type) && style != "comment") {
287        curState = state;
288        while (true) {
289          var comb = state.cc.pop() || element;
290          if (comb(type || style)) break;
291        }
292      }
293      state.startOfLine = false;
294      return setStyle || style;
295    },
296
297    indent: function(state, textAfter, fullLine) {
298      var context = state.context;
299      if ((state.tokenize != inTag && state.tokenize != inText) ||
300          context && context.noIndent)
301        return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
302      if (alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
303      if (context && /^<\//.test(textAfter))
304        context = context.prev;
305      while (context && !context.startOfLine)
306        context = context.prev;
307      if (context) return context.indent + indentUnit;
308      else return 0;
309    },
310
311    electricChars: "/"
312  };
313});
314
315CodeMirror.defineMIME("text/xml", "xml");
316CodeMirror.defineMIME("application/xml", "xml");
317if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
318  CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
Note: See TracBrowser for help on using the repository browser.

Sites map