source: Dev/branches/rest-dojo-ui/client/dojox/highlight/_base.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 12.4 KB
Line 
1define(["dojo", "dojox/main"], function(dojo, dojox){
2
3        /*=====
4                dojox.highlight = {
5                        //      summary:
6                        //              Syntax highlighting with language auto-detection package
7                        //
8                        //      description:
9                        //
10                        //              Syntax highlighting with language auto-detection package.
11                        //              Released under CLA by the Dojo Toolkit, original BSD release
12                        //              available from: http://softwaremaniacs.org/soft/highlight/
13                        //
14                        //
15                };
16        =====*/
17        var dh = dojo.getObject("dojox.highlight", true),
18                C_NUMBER_RE = '\\b(0x[A-Za-z0-9]+|\\d+(\\.\\d+)?)'
19        ;
20        dh.languages = dh.languages || {};
21        // constants
22
23        dh.constants = {
24                IDENT_RE: '[a-zA-Z][a-zA-Z0-9_]*',
25                UNDERSCORE_IDENT_RE: '[a-zA-Z_][a-zA-Z0-9_]*',
26                NUMBER_RE: '\\b\\d+(\\.\\d+)?',
27                C_NUMBER_RE: C_NUMBER_RE,
28                // Common modes
29                APOS_STRING_MODE: {
30                        className: 'string',
31                        begin: '\'', end: '\'',
32                        illegal: '\\n',
33                        contains: ['escape'],
34                        relevance: 0
35                },
36                QUOTE_STRING_MODE: {
37                        className: 'string',
38                        begin: '"',
39                        end: '"',
40                        illegal: '\\n',
41                        contains: ['escape'],
42                        relevance: 0
43                },
44                BACKSLASH_ESCAPE: {
45                        className: 'escape',
46                        begin: '\\\\.', end: '^',
47                        relevance: 0
48                },
49                C_LINE_COMMENT_MODE: {
50                        className: 'comment',
51                        begin: '//', end: '$',
52                        relevance: 0
53                },
54                C_BLOCK_COMMENT_MODE: {
55                        className: 'comment',
56                        begin: '/\\*', end: '\\*/'
57                },
58                HASH_COMMENT_MODE: {
59                        className: 'comment',
60                        begin: '#', end: '$'
61                },
62                C_NUMBER_MODE: {
63                        className: 'number',
64                        begin: C_NUMBER_RE, end: '^',
65                        relevance: 0
66                }
67        };
68
69        // utilities
70       
71        function esc(value){
72                return value.replace(/&/gm, '&amp;').replace(/</gm, '&lt;').replace(/>/gm, '&gt;');
73        }
74       
75        function verifyText(block){
76                return dojo.every(block.childNodes, function(node){
77                        return node.nodeType == 3 || String(node.nodeName).toLowerCase() == 'br';
78                });
79        }
80
81        function blockText(block){
82                var result = [];
83                dojo.forEach(block.childNodes, function(node){
84                        if(node.nodeType == 3){
85                                result.push(node.nodeValue);
86                        }else if(String(node.nodeName).toLowerCase() == 'br'){
87                                result.push("\n");
88                        }else{
89                                throw 'Complex markup';
90                        }
91                });
92                return result.join("");
93        }
94
95        function buildKeywordGroups(mode){
96                if(!mode.keywordGroups){
97                        for(var key in mode.keywords){
98                                var kw = mode.keywords[key];
99                        if(kw instanceof Object){  // dojo.isObject?
100                                        mode.keywordGroups = mode.keywords;
101                                }else{
102                                        mode.keywordGroups = {keyword: mode.keywords};
103                                }
104                                break;
105                        }
106                }
107        }
108       
109        function buildKeywords(lang){
110                if(lang.defaultMode && lang.modes){
111                        buildKeywordGroups(lang.defaultMode);
112                        dojo.forEach(lang.modes, buildKeywordGroups);
113                }
114        }
115       
116        // main object
117
118        var Highlighter = function(langName, textBlock){
119                // initialize the state
120                this.langName = langName;
121                this.lang = dh.languages[langName];
122                this.modes = [this.lang.defaultMode];
123                this.relevance = 0;
124                this.keywordCount = 0;
125                this.result = [];
126               
127                // build resources lazily
128                if(!this.lang.defaultMode.illegalRe){
129                        this.buildRes();
130                        buildKeywords(this.lang);
131                }
132               
133                // run the algorithm
134                try{
135                        this.highlight(textBlock);
136                        this.result = this.result.join("");
137                }catch(e){
138                        if(e == 'Illegal'){
139                                this.relevance = 0;
140                                this.keywordCount = 0;
141                                this.partialResult = this.result.join("");
142                                this.result = esc(textBlock);
143                        }else{
144                                throw e;
145                        }
146                }
147        };
148
149        dojo.extend(Highlighter, {
150                buildRes: function(){
151                        dojo.forEach(this.lang.modes, function(mode){
152                                if(mode.begin){
153                                        mode.beginRe = this.langRe('^' + mode.begin);
154                                }
155                                if(mode.end){
156                                        mode.endRe = this.langRe('^' + mode.end);
157                                }
158                                if(mode.illegal){
159                                        mode.illegalRe = this.langRe('^(?:' + mode.illegal + ')');
160                                }
161                        }, this);
162                        this.lang.defaultMode.illegalRe = this.langRe('^(?:' + this.lang.defaultMode.illegal + ')');
163                },
164               
165                subMode: function(lexeme){
166                        var classes = this.modes[this.modes.length - 1].contains;
167                        if(classes){
168                                var modes = this.lang.modes;
169                                for(var i = 0; i < classes.length; ++i){
170                                        var className = classes[i];
171                                        for(var j = 0; j < modes.length; ++j){
172                                                var mode = modes[j];
173                                                if(mode.className == className && mode.beginRe.test(lexeme)){ return mode; }
174                                        }
175                                }
176                        }
177                        return null;
178                },
179
180                endOfMode: function(lexeme){
181                        for(var i = this.modes.length - 1; i >= 0; --i){
182                                var mode = this.modes[i];
183                                if(mode.end && mode.endRe.test(lexeme)){ return this.modes.length - i; }
184                                if(!mode.endsWithParent){ break; }
185                        }
186                        return 0;
187                },
188
189                isIllegal: function(lexeme){
190                        var illegalRe = this.modes[this.modes.length - 1].illegalRe;
191                        return illegalRe && illegalRe.test(lexeme);
192                },
193
194
195                langRe: function(value, global){
196                        var mode =  'm' + (this.lang.case_insensitive ? 'i' : '') + (global ? 'g' : '');
197                        return new RegExp(value, mode);
198                },
199       
200                buildTerminators: function(){
201                        var mode = this.modes[this.modes.length - 1],
202                                terminators = {};
203                        if(mode.contains){
204                                dojo.forEach(this.lang.modes, function(lmode){
205                                        if(dojo.indexOf(mode.contains, lmode.className) >= 0){
206                                                terminators[lmode.begin] = 1;
207                                        }
208                                });
209                        }
210                        for(var i = this.modes.length - 1; i >= 0; --i){
211                                var m = this.modes[i];
212                                if(m.end){ terminators[m.end] = 1; }
213                                if(!m.endsWithParent){ break; }
214                        }
215                        if(mode.illegal){ terminators[mode.illegal] = 1; }
216                        var t = [];
217                        for(i in terminators){ t.push(i); }
218                        mode.terminatorsRe = this.langRe("(" + t.join("|") + ")");
219                },
220
221                eatModeChunk: function(value, index){
222                        var mode = this.modes[this.modes.length - 1];
223                       
224                        // create terminators lazily
225                        if(!mode.terminatorsRe){
226                                this.buildTerminators();
227                        }
228       
229                        value = value.substr(index);
230                        var match = mode.terminatorsRe.exec(value);
231                        if(!match){
232                                return {
233                                        buffer: value,
234                                        lexeme: "",
235                                        end:    true
236                                };
237                        }
238                        return {
239                                buffer: match.index ? value.substr(0, match.index) : "",
240                                lexeme: match[0],
241                                end:    false
242                        };
243                },
244       
245                keywordMatch: function(mode, match){
246                        var matchStr = match[0];
247                        if(this.lang.case_insensitive){ matchStr = matchStr.toLowerCase(); }
248                        for(var className in mode.keywordGroups){
249                                if(matchStr in mode.keywordGroups[className]){ return className; }
250                        }
251                        return "";
252                },
253               
254                buildLexemes: function(mode){
255                        var lexemes = {};
256                        dojo.forEach(mode.lexems, function(lexeme){
257                                lexemes[lexeme] = 1;
258                        });
259                        var t = [];
260                        for(var i in lexemes){ t.push(i); }
261                        mode.lexemsRe = this.langRe("(" + t.join("|") + ")", true);
262                },
263       
264                processKeywords: function(buffer){
265                        var mode = this.modes[this.modes.length - 1];
266                        if(!mode.keywords || !mode.lexems){
267                                return esc(buffer);
268                        }
269                       
270                        // create lexemes lazily
271                        if(!mode.lexemsRe){
272                                this.buildLexemes(mode);
273                        }
274                       
275                        mode.lexemsRe.lastIndex = 0;
276                        var result = [], lastIndex = 0,
277                                match = mode.lexemsRe.exec(buffer);
278                        while(match){
279                                result.push(esc(buffer.substr(lastIndex, match.index - lastIndex)));
280                                var keywordM = this.keywordMatch(mode, match);
281                                if(keywordM){
282                                        ++this.keywordCount;
283                                        result.push('<span class="'+ keywordM +'">' + esc(match[0]) + '</span>');
284                                }else{
285                                        result.push(esc(match[0]));
286                                }
287                                lastIndex = mode.lexemsRe.lastIndex;
288                                match = mode.lexemsRe.exec(buffer);
289                        }
290                        result.push(esc(buffer.substr(lastIndex, buffer.length - lastIndex)));
291                        return result.join("");
292                },
293       
294                processModeInfo: function(buffer, lexeme, end) {
295                        var mode = this.modes[this.modes.length - 1];
296                        if(end){
297                                this.result.push(this.processKeywords(mode.buffer + buffer));
298                                return;
299                        }
300                        if(this.isIllegal(lexeme)){ throw 'Illegal'; }
301                        var newMode = this.subMode(lexeme);
302                        if(newMode){
303                                mode.buffer += buffer;
304                                this.result.push(this.processKeywords(mode.buffer));
305                                if(newMode.excludeBegin){
306                                        this.result.push(lexeme + '<span class="' + newMode.className + '">');
307                                        newMode.buffer = '';
308                                }else{
309                                        this.result.push('<span class="' + newMode.className + '">');
310                                        newMode.buffer = lexeme;
311                                }
312                                this.modes.push(newMode);
313                                this.relevance += typeof newMode.relevance == "number" ? newMode.relevance : 1;
314                                return;
315                        }
316                        var endLevel = this.endOfMode(lexeme);
317                        if(endLevel){
318                                mode.buffer += buffer;
319                                if(mode.excludeEnd){
320                                        this.result.push(this.processKeywords(mode.buffer) + '</span>' + lexeme);
321                                }else{
322                                        this.result.push(this.processKeywords(mode.buffer + lexeme) + '</span>');
323                                }
324                                while(endLevel > 1){
325                                        this.result.push('</span>');
326                                        --endLevel;
327                                        this.modes.pop();
328                                }
329                                this.modes.pop();
330                                this.modes[this.modes.length - 1].buffer = '';
331                                return;
332                        }
333                },
334       
335                highlight: function(value){
336                        var index = 0;
337                        this.lang.defaultMode.buffer = '';
338                        do{
339                                var modeInfo = this.eatModeChunk(value, index);
340                                this.processModeInfo(modeInfo.buffer, modeInfo.lexeme, modeInfo.end);
341                                index += modeInfo.buffer.length + modeInfo.lexeme.length;
342                        }while(!modeInfo.end);
343                        if(this.modes.length > 1){
344                                throw 'Illegal';
345                        }
346                }
347        });
348       
349        // more utilities
350       
351        function replaceText(node, className, text){
352                if(String(node.tagName).toLowerCase() == "code" && String(node.parentNode.tagName).toLowerCase() == "pre"){
353                        // See these 4 lines? This is IE's notion of "node.innerHTML = text". Love this browser :-/
354                        var container = document.createElement('div'),
355                                environment = node.parentNode.parentNode;
356                        container.innerHTML = '<pre><code class="' + className + '">' + text + '</code></pre>';
357                        environment.replaceChild(container.firstChild, node.parentNode);
358                }else{
359                        node.className = className;
360                        node.innerHTML = text;
361                }
362        }
363        function highlightStringLanguage(lang, str){
364                var highlight = new Highlighter(lang, str);
365                return {result:highlight.result, langName:lang, partialResult:highlight.partialResult};
366        }
367
368        function highlightLanguage(block, lang){
369                var result = highlightStringLanguage(lang, blockText(block));
370                replaceText(block, block.className, result.result);
371        }
372
373        function highlightStringAuto(str){
374                var result = "", langName = "", bestRelevance = 2,
375                        textBlock = str;
376                for(var key in dh.languages){
377                        if(!dh.languages[key].defaultMode){ continue; } // skip internal members
378                        var highlight = new Highlighter(key, textBlock),
379                                relevance = highlight.keywordCount + highlight.relevance, relevanceMax = 0;
380                        if(!result || relevance > relevanceMax){
381                                relevanceMax = relevance;
382                                result = highlight.result;
383                                langName = highlight.langName;
384                        }
385                }
386                return {result:result, langName:langName};
387        }
388       
389        function highlightAuto(block){
390                var result = highlightStringAuto(blockText(block));
391                if(result.result){
392                        replaceText(block, result.langName, result.result);
393                }
394        }
395       
396        // the public API
397
398        dojox.highlight.processString = function(/* String */ str, /* String? */lang){
399                // summary: highlight a string of text
400                // returns: Object containing:
401                //         result - string of html with spans to apply formatting
402                //         partialResult - if the formating failed: string of html
403                //                 up to the point of the failure, otherwise: undefined
404                //         langName - the language used to do the formatting
405                return lang ? highlightStringLanguage(lang, str) : highlightStringAuto(str);
406        };
407
408        dojox.highlight.init = function(/* String|DomNode */ node){
409                //      summary: Highlight a passed node
410                //
411                //      description:
412                //
413                //              Syntax highlight a passed DomNode or String ID of a DomNode
414                //
415                //
416                //      example:
417                //      |       dojox.highlight.init("someId");
418                //
419                node = dojo.byId(node);
420                if(dojo.hasClass(node, "no-highlight")){ return; }
421                if(!verifyText(node)){ return; }
422       
423                var classes = node.className.split(/\s+/),
424                        flag = dojo.some(classes, function(className){
425                                if(className.charAt(0) != "_" && dh.languages[className]){
426                                        highlightLanguage(node, className);
427                                        return true;    // stop iterations
428                                }
429                                return false;   // continue iterations
430                        });
431                if(!flag){
432                        highlightAuto(node);
433                }
434        };
435
436/*=====
437        dojox.highlight.Code = function(props, node){
438                //      summary: A Class object to allow for dojoType usage with the highlight engine. This is
439                //              NOT a Widget in the conventional sense, and does not have any member functions for
440                //              the instance. This is provided as a convenience. You likely should be calling
441                //              `dojox.highlight.init` directly.
442                //
443                //      props: Object?
444                //              Unused. Pass 'null' or {}. Positional usage to allow `dojo.parser` to instantiate
445                //              this class as other Widgets would be.
446                //
447                //      node: String|DomNode
448                //              A String ID or DomNode reference to use as the root node of this instance.
449                //
450                //      example:
451                //      |       <pre><code dojoType="dojox.highlight.Code">for(var i in obj){ ... }</code></pre>
452                //
453                //      example:
454                //      |       var inst = new dojox.highlight.Code({}, "someId");
455                //
456                this.node = dojo.byId(node);
457        };
458=====*/
459
460        dh.Code = function(props, node){ dh.init(node); };
461
462        return dh;
463
464});
Note: See TracBrowser for help on using the repository browser.