OSDN Git Service

import source-tree based svn r84.
[bluegriffon/BlueGriffon.git] / extensions / diavolo / content / grammar.js
1 /* ***** BEGIN LICENSE BLOCK *****
2  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3  *
4  * The contents of this file are subject to the Mozilla Public License Version
5  * 1.1 (the "License"); you may not use this file except in compliance with
6  * the License. You may obtain a copy of the License at
7  * http://www.mozilla.org/MPL/
8  *
9  * Software distributed under the License is distributed on an "AS IS" basis,
10  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11  * for the specific language governing rights and limitations under the
12  * License.
13  *
14  * The Original Code is Diavolo.
15  *
16  * The Initial Developer of the Original Code is
17  * Disruptive Innovations SARL.
18  * Portions created by the Initial Developer are Copyright (C) 2006-2008
19  * the Initial Developer. All Rights Reserved.
20  *
21  * Contributor(s):
22  *   Daniel Glazman <daniel.glazman@disruptive-innovations.com>, Original author
23  *
24  * Alternatively, the contents of this file may be used under the terms of
25  * either the GNU General Public License Version 2 or later (the "GPL"), or
26  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27  * in which case the provisions of the GPL or the LGPL are applicable instead
28  * of those above. If you wish to allow use of your version of this file only
29  * under the terms of either the GPL or the LGPL, and not to allow others to
30  * use your version of this file under the terms of the MPL, indicate your
31  * decision by deleting the provisions above and replace them with the notice
32  * and other provisions required by the GPL or the LGPL. If you do not delete
33  * the provisions above, a recipient may use your version of this file under
34  * the terms of any one of the MPL, the GPL or the LGPL.
35  *
36  * ***** END LICENSE BLOCK ***** */
37
38 function DiavoloGrammar(aChromeURL)
39 {
40   this.init(aChromeURL);
41 }
42
43 DiavoloGrammar.prototype = {
44   
45   kIOServiceCID      : "@mozilla.org/network/io-service;1",
46   kFileInputStreamCID: "@mozilla.org/network/file-input-stream;1",
47   kScriptableInputCID: "@mozilla.org/scriptableinputstream;1",
48   kUnicodeConverterCID: "@mozilla.org/intl/scriptableunicodeconverter",
49
50   nsIIOService            : Components.interfaces.nsIIOService,
51   nsIFileInputStream      : Components.interfaces.nsIFileInputStream,
52   nsIScriptableInputStream: Components.interfaces.nsIScriptableInputStream,
53   nsIScriptableUnicodeConverter: Components.interfaces.nsIScriptableUnicodeConverter,
54
55   kSTRING_TOKEN: 0,
56   kREGEXP_TOKEN: 1,
57
58   mChromeURL: "",
59   mName: "",
60
61   mGrammarDocument: null,
62
63   mContexts: {},
64   mRegExps: {},
65   mDeftokens: {},
66   mRoles: {},
67   mStyles: [],
68   mErrorStyle: "",
69   mSkipuntilStyle: "",
70
71   mFirstContextId: null,
72
73   // options
74   mCaseInsensitive: false,
75
76   mResolver: new RegExp("\{[a-zA-Z][a-zA-Z0-9]*\}", "g"),
77
78   getGrammarName: function()
79   {
80     return this.mName;
81   },
82
83   getDeftokens: function()
84   {
85     return this.mDeftokens;
86   },
87
88   isCaseInsensitive: function()
89   {
90     return this.mCaseInsensitive;
91   },
92
93   convertToUnicode: function(aCharset, aSrc )
94   {
95     // http://lxr.mozilla.org/mozilla/source/intl/uconv/idl/nsIScriptableUConv.idl
96     var unicodeConverter = Components.classes[this.kUnicodeConverterCID]
97                              .createInstance(this.nsIScriptableUnicodeConverter);
98     unicodeConverter.charset = aCharset;
99     return unicodeConverter.ConvertToUnicode( aSrc );
100   },
101
102   init: function(aChromeURL)
103   {
104     this.mChromeURL = aChromeURL;
105     
106     // Get the grammar file
107     // var urlspec="chrome://diavolo/content/css.xml";
108     var ioService = Components.classes[this.kIOServiceCID]
109                       .getService(this.nsIIOService);
110
111     // Get the baseURI
112     var url = ioService.newURI(aChromeURL, null, null);
113
114     var chann = ioService.newChannelFromURI(url);
115     var inputStream = Components.classes[this.kFileInputStreamCID]
116                         .createInstance(this.nsIFileInputStream);
117     var sis = Components.classes[this.kScriptableInputCID]
118                 .createInstance(this.nsIScriptableInputStream);
119
120     sis.init(chann.open());
121     var str = sis.read(sis.available());
122     sis.close();
123     str = this.convertToUnicode("UTF-8",str);
124     var parser = new DOMParser();
125     this.mGrammarDocument = parser.parseFromString(str, "text/xml");
126
127     this.mName = this.mGrammarDocument.documentElement.getAttribute("name");
128
129     this.initRegExps();
130     this.getContexts();
131     this.initStyles();
132
133     // cleanup
134     this.mRegExps = null;
135     this.mGrammarDocument = null;
136   },
137
138   initRegExps: function()
139   {
140     var regexps = this.mGrammarDocument.getElementsByTagName("regexp");
141     var length = regexps.length;
142     for (var i = 0; i < length; i++)
143     {
144       var r     = regexps.item(i);
145       var name  = r.getAttribute("name");
146       var value = r.getAttribute("value");
147
148       this.mRegExps[name] = {value: value, resolved: false};
149     }
150
151     this.resolveAllRegexps();
152     this.getOptions();
153     this.resolveDeftokens();
154   },
155
156   resolveAllRegexps: function()
157   {
158     for (var i in this.mRegExps)
159       this.resolveRegexp(i);
160   },
161
162   resolveRegexp: function(aName)
163   {
164       if (!this.mRegExps[aName].resolved)
165       {
166         var value = this.mRegExps[aName].value;
167         var match = value.match(this.mResolver);
168         if (match)
169         {
170           for (var j = 0; j < match.length; j++)
171           {
172             var m = match[j];
173             var subRegexp = m.substring(1, m.length - 1);
174             this.resolveRegexp(subRegexp);
175
176             var r = new RegExp( "\{" + subRegexp + "\}", "g" )
177             value = value.replace(r, "(" + this.mRegExps[subRegexp].value + ")");
178           }
179           this.mRegExps[aName].value = value;
180         }
181         this.mRegExps[aName].resolved = true;
182       }
183   },
184
185   getOptions: function()
186   {
187     var options = this.mGrammarDocument.getElementsByTagName("option");
188     var length = options.length;
189     for (var i = 0; i < length; i++)
190     {
191       var option = options.item(i);
192       var type = option.getAttribute("type");
193       switch (type)
194       {
195         case "case-insensitive":
196           this.mCaseInsensitive = true;
197           break;
198         default:
199           break;
200       }
201     }
202   },
203
204   resolveDeftokens: function()
205   {
206     var deftokens = this.mGrammarDocument.getElementsByTagName("deftoken");
207     var length = deftokens.length;
208     for (var i = 0; i < length; i++)
209     {
210       var t = deftokens.item(i);
211       var name = t.getAttribute("name");
212       var type, value;
213       if (t.hasAttribute("string"))
214       {
215         type = this.kSTRING_TOKEN;
216         value = t.getAttribute("string");        
217       }
218       else
219       {
220         type = this.kREGEXP_TOKEN;
221         value = new RegExp(this.mRegExps[t.getAttribute("regexp")].value,
222                            this.mCaseInsensitive ? "iy" : "y");
223       }
224       this.mDeftokens[name] = { type: type, value: value};
225     }
226   },
227
228   getNextToken: function getNextToken(aSourceString, aLastIndex)
229   {
230     if (aLastIndex >= aSourceString.length)
231       return null;
232     var tokens = this.mDeftokens;
233     var tokenFound = false;
234     var tokenName = "";
235     var tokenString = "";
236     for (var i in tokens)
237     {
238       var token = tokens[i];
239       switch (token.type)
240       {
241         case this.kSTRING_TOKEN:
242           {
243             var s = token.value;
244             var l = s.length;
245             if (aSourceString.substr(aLastIndex, l) == s)
246             {
247               tokenName = i;
248               tokenString = s;
249               tokenFound = true;
250             }
251           }
252           break;
253         case this.kREGEXP_TOKEN:
254           {
255             var r = token.value;
256             r.lastIndex = aLastIndex;
257             var result = r.exec(aSourceString); 
258             if (result)
259             {
260               tokenName = i;
261               tokenString = result[0];
262               tokenFound = true;
263             }
264           }
265           break;
266         default: // we should never reach this
267           break;
268       }
269       if (tokenFound)
270         break;
271     }
272     if (tokenFound)
273       return {name: tokenName, string: tokenString, error: false};
274     return {name: "", string: aSourceString[aLastIndex], error: true};
275   },
276
277   getContexts: function getContexts()
278   {
279     var contexts = this.mGrammarDocument.getElementsByTagName("context");
280
281     if (contexts.length)
282       this.mFirstContextId = contexts.item(0).getAttribute("id");
283
284     for (var k = 0; k < contexts.length; k++) 
285     {
286       var c = contexts.item(k);
287       var contextName = c.getAttribute("id");
288       this.mContexts[contextName] = {};
289       // get all error handlers
290       var skipuntils = {};
291       var errorsElts = c.getElementsByTagName("error");
292       for (var i = 0; i < errorsElts.length; i++) 
293       {
294         var e = errorsElts.item(i);
295         skipuntils[e.getAttribute("skipuntil")] = e.getAttribute("expecting");
296       }
297       this.mContexts[contextName].skipuntils = skipuntils;
298
299       // now find all tokens to ignore in this context
300       var ignores = {};
301       var ignoreElts = c.getElementsByTagName("ignore");
302       
303       if (ignoreElts && ignoreElts.length) 
304       {
305         
306         if (ignoreElts.length > 1) 
307           dump("more than one <ignore> element found in context " + contextName + "\n")
308         var ignoreElt = ignoreElts.item(0);
309         var typeElts = ignoreElt.getElementsByTagName("type");
310         for (var i = 0; i < typeElts.length; i++) 
311         {
312           var t = typeElts.item(i);
313           ignores[t.getAttribute("name")] = true;
314         }
315       }
316       this.mContexts[contextName].ignores = ignores;
317
318       // and now finally tokens
319       var tokens = [];
320       var tokenElts = c.getElementsByTagName("token");
321       for (var i = 0; i < tokenElts.length; i++) 
322       {
323         t = tokenElts.item(i);
324         var ttype = t.getAttribute("type");
325         var tlookahead = t.hasAttribute("lookahead") ? t.getAttribute("lookahead") : null;
326         var trole = t.getAttribute("role");
327         var texpecting = t.getAttribute("expecting");
328         tokens.push({
329           type: ttype,
330           lookahead: tlookahead,
331           role: trole,
332           expecting: texpecting
333         });
334         // we need to keep track of all roles for color management
335         if (!(trole in this.mRoles))
336           this.mRoles[trole] = true;
337       }
338       this.mContexts[contextName].tokens = tokens;
339     }
340   },
341
342   initStyles: function initStyles()
343   {
344     var stylesets = this.mGrammarDocument.getElementsByTagName("styleset");
345     if (!stylesets || !stylesets.length)
346       return;
347     var styles = stylesets[0].getElementsByTagName("style");
348     for (var i = 0; i < styles.length; i++)
349     {
350       var s = styles.item(i);
351       var role = s.getAttribute("role");
352       var forToken = s.hasAttribute("forToken")
353                      ? s.getAttribute("forToken")
354                      : "";
355       var value = s.getAttribute("value");
356
357       this.mStyles.push( { role:     role,
358                            forToken: forToken,
359                            value:    value }); 
360     }
361
362     var errorstyles = stylesets[0].getElementsByTagName("errorstyle");
363     if (errorstyles && errorstyles.length)
364     {
365       this.mErrorStyle = errorstyles[0].getAttribute("value"); 
366     }
367
368     var skipuntilstyles = stylesets[0].getElementsByTagName("skipuntilstyle");
369     if (skipuntilstyles && skipuntilstyles.length)
370     {
371       this.mSkipuntilStyle = skipuntilstyles[0].getAttribute("value"); 
372     }
373   },
374
375   buildStylesheet: function buildStylesheet()
376   {
377     var cssText = "";
378
379     cssText += "*[error='true'] {" + this.mErrorStyle + "}\n";
380     cssText += ".SKIPUNTIL {" + this.mSkipuntilStyle + "}\n";
381
382     for (var i = 0; i < this.mStyles.length; i++)
383     {
384       var s = this.mStyles[i];
385       cssText += "." + s.role
386                      + (s.forToken ? "[token='" + s.forToken + "']"
387                                    : "")
388                      + " {"
389                      + s.value
390                      + "}\n"; 
391     }
392
393     return cssText;
394   },
395
396   getRoles: function getRoles()
397   {
398     var res = [];
399     for (var i in this.mRoles)
400       res.push(i);
401     return res;
402   },
403
404   getTokens: function getTokens()
405     {
406     var res = [];
407     for (var i in this.mDeftokens)
408       res.push(i);
409     return res;
410   },
411
412   getStyles: function getStyles(aRole, aForToken)
413   {
414     return this.mStyles;
415   },
416
417   setStyles: function setStyles(aStyles)
418   {
419     this.mStyles = aStyles;
420   },
421
422   getErrorStyle: function getErrorStyle()
423   {
424     return this.mErrorStyle;
425   },
426   
427   setErrorStyle: function setErrorStyle(aStyle)
428   {
429     this.mErrorStyle = aStyle;
430   },
431   
432   getSkipuntilStyle: function getSkipuntilStyle()
433   {
434     return this.mSkipuntilStyle;
435   },
436   
437   setSkipuntilStyle: function setSkipuntilStyle(aStyle)
438   {
439     this.mSkipuntilStyle = aStyle;
440   }
441   
442 };
443