#994: highlighting strange characters
[redakcja.git] / redakcja / static / js / lib / codemirror-0.8 / parsexml.js
1 /* This file defines an XML parser, with a few kludges to make it
2  * useable for HTML. autoSelfClosers defines a set of tag names that
3  * are expected to not have a closing tag, and doNotIndent specifies
4  * the tags inside of which no indentation should happen (see Config
5  * object). These can be disabled by passing the editor an object like
6  * {useHTMLKludges: false} as parserConfig option.
7  */
8
9 var XMLParser = Editor.Parser = (function() {
10   var Kludges = {
11     autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true,
12                       "meta": true, "col": true, "frame": true, "base": true, "area": true},
13     doNotIndent: {"pre": true, "!cdata": true}
14   };
15   var NoKludges = {autoSelfClosers: {}, doNotIndent: {"!cdata": true}};
16   var UseKludges = Kludges;
17   var alignCDATA = false;
18
19   // Simple stateful tokenizer for XML documents. Returns a
20   // MochiKit-style iterator, with a state property that contains a
21   // function encapsulating the current state. See tokenize.js.
22   var tokenizeXML = (function() {
23     function inText(source, setState) {
24       var ch = source.next();
25       if (ch == "<") {
26         if (source.equals("!")) {
27           source.next();
28           if (source.equals("[")) {
29             if (source.lookAhead("[CDATA[", true)) {
30               setState(inBlock("xml-cdata", "]]>"));
31               return null;
32             }
33             else {
34               return "xml-text";
35             }
36           }
37           else if (source.lookAhead("--", true)) {
38             setState(inBlock("xml-comment", "-->"));
39             return null;
40           }
41           else {
42             return "xml-text";
43           }
44         }
45         else if (source.equals("?")) {
46           source.next();
47           source.nextWhileMatches(/[\w\._\-]/);
48           setState(inBlock("xml-processing", "?>"));
49           return "xml-processing";
50         }
51         else {
52           if (source.equals("/")) source.next();
53           setState(inTag);
54           return "xml-punctuation";
55         }
56       }
57       else if (ch == "&") {
58         while (!source.endOfLine()) {
59           if (source.next() == ";")
60             break;
61         }
62         return "xml-entity";
63       }
64       else {
65         source.nextWhileMatches(/[^&<\n]/);
66         return "xml-text";
67       }
68     }
69
70     function inTag(source, setState) {
71       var ch = source.next();
72       if (ch == ">") {
73         setState(inText);
74         return "xml-punctuation";
75       }
76       else if (/[?\/]/.test(ch) && source.equals(">")) {
77         source.next();
78         setState(inText);
79         return "xml-punctuation";
80       }
81       else if (ch == "=") {
82         return "xml-punctuation";
83       }
84       else if (/[\'\"]/.test(ch)) {
85         setState(inAttribute(ch));
86         return null;
87       }
88       else {
89         source.nextWhileMatches(/[^\s\u00a0=<>\"\'\/?]/);
90         return "xml-name";
91       }
92     }
93
94     function inAttribute(quote) {
95       return function(source, setState) {
96         while (!source.endOfLine()) {
97           if (source.next() == quote) {
98             setState(inTag);
99             break;
100           }
101         }
102         return "xml-attribute";
103       };
104     }
105
106     function inBlock(style, terminator) {
107       return function(source, setState) {
108         while (!source.endOfLine()) {
109           if (source.lookAhead(terminator, true)) {
110             setState(inText);
111             break;
112           }
113           source.next();
114         }
115         return style;
116       };
117     }
118
119     return function(source, startState) {
120       return tokenizer(source, startState || inText);
121     };
122   })();
123
124   // The parser. The structure of this function largely follows that of
125   // parseJavaScript in parsejavascript.js (there is actually a bit more
126   // shared code than I'd like), but it is quite a bit simpler.
127   function parseXML(source) {
128     var tokens = tokenizeXML(source), token;
129     var cc = [base];
130     var tokenNr = 0, indented = 0;
131     var currentTag = null, context = null;
132     var consume;
133     
134     function push(fs) {
135       for (var i = fs.length - 1; i >= 0; i--)
136         cc.push(fs[i]);
137     }
138     function cont() {
139       push(arguments);
140       consume = true;
141     }
142     function pass() {
143       push(arguments);
144       consume = false;
145     }
146
147     function markErr() {
148       token.style += " xml-error";
149     }
150     function expect(text) {
151       return function(style, content) {
152         if (content == text) cont();
153         else {markErr(); cont(arguments.callee);}
154       };
155     }
156
157     function pushContext(tagname, startOfLine) {
158       var noIndent = UseKludges.doNotIndent.hasOwnProperty(tagname) || (context && context.noIndent);
159       context = {prev: context, name: tagname, indent: indented, startOfLine: startOfLine, noIndent: noIndent};
160     }
161     function popContext() {
162       context = context.prev;
163     }
164     function computeIndentation(baseContext) {
165       return function(nextChars, current) {
166         var context = baseContext;
167         if (context && context.noIndent)
168           return current;
169         if (alignCDATA && /<!\[CDATA\[/.test(nextChars))
170           return 0;
171         if (context && /^<\//.test(nextChars))
172           context = context.prev;
173         while (context && !context.startOfLine)
174           context = context.prev;
175         if (context)
176           return context.indent + indentUnit;
177         else
178           return 0;
179       };
180     }
181
182     function base() {
183       return pass(element, base);
184     }
185     var harmlessTokens = {"xml-text": true, "xml-entity": true, "xml-comment": true, "xml-processing": true};
186     function element(style, content) {
187       if (content == "<") cont(tagname, attributes, endtag(tokenNr == 1));
188       else if (content == "</") cont(closetagname, expect(">"));
189       else if (style == "xml-cdata") {
190         if (!context || context.name != "!cdata") pushContext("!cdata");
191         if (/\]\]>$/.test(content)) popContext();
192         cont();
193       }
194       else if (harmlessTokens.hasOwnProperty(style)) cont();
195       else {markErr(); cont();}
196     }
197     function tagname(style, content) {
198       if (style == "xml-name") {
199         currentTag = content.toLowerCase();
200         token.style = "xml-tagname";
201         cont();
202       }
203       else {
204         currentTag = null;
205         pass();
206       }
207     }
208     function closetagname(style, content) {
209       if (style == "xml-name") {
210         token.style = "xml-tagname";
211         if (context && content.toLowerCase() == context.name) popContext();
212         else markErr();
213       }
214       cont();
215     }
216     function endtag(startOfLine) {
217       return function(style, content) {
218         if (content == "/>" || (content == ">" && UseKludges.autoSelfClosers.hasOwnProperty(currentTag))) cont();
219         else if (content == ">") {pushContext(currentTag, startOfLine); cont();}
220         else {markErr(); cont(arguments.callee);}
221       };
222     }
223     function attributes(style) {
224       if (style == "xml-name") {token.style = "xml-attname"; cont(attribute, attributes);}
225       else pass();
226     }
227     function attribute(style, content) {
228       if (content == "=") cont(value);
229       else if (content == ">" || content == "/>") pass(endtag);
230       else pass();
231     }
232     function value(style) {
233       if (style == "xml-attribute") cont(value);
234       else pass();
235     }
236
237     return {
238       indentation: function() {return indented;},
239
240       next: function(){
241         token = tokens.next();
242         if (token.style == "whitespace" && tokenNr == 0)
243           indented = token.value.length;
244         else
245           tokenNr++;
246         if (token.content == "\n") {
247           indented = tokenNr = 0;
248           token.indentation = computeIndentation(context);
249         }
250
251         if (token.style == "whitespace" || token.type == "xml-comment")
252           return token;
253
254         while(true){
255           consume = false;
256           cc.pop()(token.style, token.content);
257           if (consume) return token;
258         }
259       },
260
261       copy: function(){
262         var _cc = cc.concat([]), _tokenState = tokens.state, _context = context;
263         var parser = this;
264         
265         return function(input){
266           cc = _cc.concat([]);
267           tokenNr = indented = 0;
268           context = _context;
269           tokens = tokenizeXML(input, _tokenState);
270           return parser;
271         };
272       }
273     };
274   }
275
276   return {
277     make: parseXML,
278     electricChars: "/",
279     configure: function(config) {
280       if (config.useHTMLKludges != null)
281         UseKludges = config.useHTMLKludges ? Kludges : NoKludges;
282       if (config.alignCDATA)
283         alignCDATA = config.alignCDATA;
284     }
285   };
286 })();