~ubuntu-branches/ubuntu/trusty/yelp-xsl/trusty

« back to all changes in this revision

Viewing changes to js/jquery.syntax.core.js

  • Committer: Package Import Robot
  • Author(s): Michael Biebl
  • Date: 2012-03-19 17:46:00 UTC
  • mto: (11.1.2 experimental) (1.3.1)
  • mto: This revision was merged to the branch mainline in revision 15.
  • Revision ID: package-import@ubuntu.com-20120319174600-pzeaayow54b559hi
Tags: upstream-3.3.92
ImportĀ upstreamĀ versionĀ 3.3.92

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
        };
21
21
}
22
22
 
23
 
// The jQuery version of container.text() is broken on IE6.
24
 
// This version fixes it... for pre elements only. Other elements
25
 
// in IE will have the whitespace manipulated.
26
 
Syntax.getCDATA = function (elems) {
27
 
        var cdata = "", elem;
28
 
        
29
 
        (function (elems) {
30
 
                for (var i = 0; elems[i]; i++) {
31
 
                        elem = elems[i];
32
 
 
33
 
                        // Get the text from text nodes and CDATA nodes
34
 
                        if (elem.nodeType === 3 || elem.nodeType === 4) {
35
 
                                cdata += elem.nodeValue;
36
 
                
37
 
                        // Use textContent || innerText for elements
38
 
                        } else if (elem.nodeType === 1) {
39
 
                                if (typeof(elem.textContent) === 'string')
40
 
                                        cdata += elem.textContent;
41
 
                                else if (typeof(elem.innerText) === 'string')
42
 
                                        cdata += elem.innerText;
43
 
                                else
44
 
                                        arguments.callee(elem.childNodes);
45
 
                        
46
 
                        // Traverse everything else, except comment nodes
47
 
                        } else if (elem.nodeType !== 8) {
48
 
                                arguments.callee(elem.childNodes);
49
 
                        }
50
 
                }
51
 
        })(elems);
52
 
        
53
 
        return cdata.replace(/\r\n?/g, "\n");
 
23
// Return the inner text of an element - must preserve whitespace.
 
24
// Avoid returning \r characters.
 
25
Syntax.innerText = function(element) {
 
26
        var text;
 
27
        
 
28
        if (!element) {
 
29
                return "";
 
30
        }
 
31
        
 
32
        if (element.nodeName == 'BR') {
 
33
                return '\n';
 
34
        } else if (element.textContent) {
 
35
                // W3C: FF, Safari, Chrome, etc.
 
36
                text = element.textContent;
 
37
        } else if (document.body.innerText) {
 
38
                // IE, other older browsers.
 
39
                text = element.innerText;
 
40
        }
 
41
        
 
42
        return text.replace(/\r\n?/g, '\n');
54
43
}
55
44
 
56
45
// Convert to stack based implementation
67
56
                                offset += elem.nodeValue.length;
68
57
                        
69
58
                        } else if (elem.nodeType === 1) {
70
 
                                var text = Syntax.getCDATA(elem.childNodes);
71
 
                                var expr = {klass: elem.className, force: true, element: elem};
 
59
                                var text = Syntax.innerText(elem);
72
60
                                
73
 
                                matches.push(new Syntax.Match(offset, text.length, expr, text));
 
61
                                matches.push(new Syntax.Match(offset, text.length, {
 
62
                                        klass: elem.className,
 
63
                                        force: true,
 
64
                                        element: elem,
 
65
                                        allow: '*'
 
66
                                }, text));
74
67
                        }
75
68
                        
76
69
                        // Traverse everything, except comment nodes
77
 
                        if (elem.nodeType !== 8) {
 
70
                        if (elem.nodeType !== 8 && elem.children) {
78
71
                                arguments.callee(elem.childNodes, offset);
79
72
                        }
80
73
                }
87
80
        return matches;
88
81
}
89
82
 
 
83
// Basic layout doesn't do anything e.g. identity layout.
90
84
Syntax.layouts.preformatted = function (options, html, container) {
91
85
        return html;
92
86
};
95
89
        'tab-width': function(name, value, options) { options.tabWidth = parseInt(value, 10); }
96
90
};
97
91
 
 
92
// Should be obvious right?
98
93
Syntax.convertTabsToSpaces = function (text, tabSize) {
99
94
        var space = [], pattern = /\r|\n|\t/g, tabOffset = 0, offsets = [], totalOffset = 0;
100
95
        tabSize = tabSize || 4
123
118
        return {text: text, offsets: offsets};
124
119
};
125
120
 
 
121
// This function converts from a compressed set of offsets of the form:
 
122
//      [
 
123
//              [offset, width, totalOffset],
 
124
//              ...
 
125
//      ]
 
126
// This means that at a $offset, a tab (single character) was expanded to $width
 
127
// single space characters.
 
128
// This function produces a lookup table of offsets, where a given character offset
 
129
// is mapped to how far the character has been offset.
126
130
Syntax.convertToLinearOffsets = function (offsets, length) {
127
131
        var current = 0, changes = [];
128
132
        
130
134
        // has been shifted right by offset[current][2]
131
135
        for (var i = 0; i < length; i++) {
132
136
                if (offsets[current] && i > offsets[current][0]) {
133
 
                        if (offsets[current+1] && i <= offsets[current+1][0]) {
 
137
                        // Is there a next offset?
 
138
                        if (offsets[current+1]) {
 
139
                                // Is the index less than the start of the next offset?
 
140
                                if (i <= offsets[current+1][0]) {
 
141
                                        changes.push(offsets[current][2]);
 
142
                                } else {
 
143
                                        // If so, move to the next offset.
 
144
                                        current += 1;
 
145
                                        i -= 1;
 
146
                                }
 
147
                        } else {
 
148
                                // If there is no next offset we assume this one to the end.
134
149
                                changes.push(offsets[current][2]);
135
 
                        } else {
136
 
                                current += 1;
137
 
                                i -= 1;
138
150
                        }
139
151
                } else {
140
152
                        changes.push(changes[changes.length-1] || 0);
144
156
        return changes;
145
157
}
146
158
 
 
159
// Used for tab expansion process, by shifting matches when tab charaters were converted to
 
160
// spaces.
147
161
Syntax.updateMatchesWithOffsets = function (matches, linearOffsets, text) {
148
162
        (function (matches) {
149
163
                for (var i = 0; i < matches.length; i++) {
165
179
        return matches;
166
180
};
167
181
 
 
182
// A helper function which automatically matches expressions with capture groups from the regular expression match.
 
183
// Each argument position corresponds to the same index regular expression group.
 
184
// Or, override by providing rule.index
168
185
Syntax.extractMatches = function() {
169
186
        var rules = arguments;
170
187
        
188
205
                        
189
206
                        if (match[index].length > 0) {
190
207
                                if (rule.brush) {
191
 
                                        matches.push(Syntax.brushes[rule.brush].buildTree(match[index], RegExp.indexOf(match, index)));
 
208
                                        matches.push(Syntax.Brush.buildTree(rule, match[index], RegExp.indexOf(match, index)));
192
209
                                } else {
193
210
                                        var expression = jQuery.extend({owner: expr.owner}, rule);
194
211
                                        
201
218
        };
202
219
};
203
220
 
 
221
// Used to create processing functions that automatically link to remote documentation.
204
222
Syntax.lib.webLinkProcess = function (queryURI, lucky) {
205
223
        if (lucky) {
206
224
                queryURI = "http://www.google.com/search?btnI=I&q=" + encodeURIComponent(queryURI + " ");
207
225
        }
208
226
        
209
 
        return function (element, match) {
210
 
                return jQuery('<a>').
211
 
                        attr('href', queryURI + encodeURIComponent(element.text())).
212
 
                        attr('class', element.attr('class')).
213
 
                        append(element.contents());
 
227
        return function (element, match, options) {
 
228
                // Per-code block linkification control.
 
229
                if (options.linkify === false)
 
230
                        return element;
 
231
                
 
232
                var a = document.createElement('a');
 
233
                a.href = queryURI + encodeURIComponent(Syntax.innerText(element));
 
234
                a.className = element.className;
 
235
                
 
236
                // Move children from <element> to <a>
 
237
                while (element.childNodes.length > 0)
 
238
                        a.appendChild(element.childNodes[0]);
 
239
                
 
240
                return a;
214
241
        };
215
242
};
216
243
 
 
244
// Global brush registration function.
217
245
Syntax.register = function (name, callback) {
218
246
        var brush = Syntax.brushes[name] = new Syntax.Brush();
219
247
        brush.klass = name;
221
249
        callback(brush);
222
250
};
223
251
 
 
252
// Library of helper patterns
224
253
Syntax.lib.cStyleComment = {pattern: /\/\*[\s\S]*?\*\//gm, klass: 'comment', allow: ['href']};
225
254
Syntax.lib.cppStyleComment = {pattern: /\/\/.*$/gm, klass: 'comment', allow: ['href']};
226
255
Syntax.lib.perlStyleComment = {pattern: /#.*$/gm, klass: 'comment', allow: ['href']};
227
256
 
228
 
Syntax.lib.perlStyleRegularExpressions = {pattern: /\B\/([^\/]|\\\/)*?\/[a-z]*(?=\s*[^\w\s'";\/])/g, klass: 'constant'};
 
257
Syntax.lib.perlStyleRegularExpression = {pattern: /\B\/([^\/]|\\\/)*?\/[a-z]*(?=\s*($|[^\w\s'"\(]))/gm, klass: 'constant', incremental: true};
229
258
 
230
259
Syntax.lib.cStyleFunction = {pattern: /([a-z_][a-z0-9_]*)\s*\(/gi, matches: Syntax.extractMatches({klass: 'function'})};
231
260
Syntax.lib.camelCaseType = {pattern: /\b_*[A-Z][\w]*\b/g, klass: 'type'};
 
261
Syntax.lib.cStyleType = {pattern: /\b[_a-z][_\w]*_t\b/gi, klass: 'type'};
232
262
 
233
263
Syntax.lib.xmlComment = {pattern: /(&lt;|<)!--[\s\S]*?--(&gt;|>)/gm, klass: 'comment'};
234
264
Syntax.lib.webLink = {pattern: /\w+:\/\/[\w\-.\/?%&=@:;#]*/g, klass: 'href'};
242
272
Syntax.lib.multiLineSingleQuotedString = {pattern: /'([^\\']|\\.)*'/g, klass: 'string'};
243
273
Syntax.lib.stringEscape = {pattern: /\\./g, klass: 'escape', only: ['string']};
244
274
 
 
275
// Main match constructor. Make sure value is the correct size.
245
276
Syntax.Match = function (offset, length, expression, value) {
246
277
        this.offset = offset;
247
278
        this.endOffset = offset + length;
279
310
        }
280
311
};
281
312
 
 
313
// Sort helper for sorting matches in forward order (e.g. same as the text that they were extracted from)
282
314
Syntax.Match.sort = function (a,b) {
283
315
        return (a.offset - b.offset) || (b.length - a.length);
284
316
};
285
317
 
 
318
// Is the given match contained in the range of the parent match?
286
319
Syntax.Match.prototype.contains = function (match) {
287
320
        return (match.offset >= this.offset) && (match.endOffset <= this.endOffset);
288
321
};
289
322
 
 
323
// Reduce a givent tree node into an html node.
290
324
Syntax.Match.defaultReduceCallback = function (node, container) {
291
325
        // We avoid using jQuery in this function since it is incredibly performance sensitive.
292
326
        // Using jQuery jQuery.fn.append() can reduce performance by as much as 1/3rd.
293
327
        if (typeof(node) === 'string') {
294
328
                node = document.createTextNode(node);
295
 
        } else {
296
 
                node = node[0];
297
329
        }
298
330
        
299
 
        container[0].appendChild(node);
 
331
        container.appendChild(node);
300
332
};
301
333
 
 
334
// Convert a tree of matches into some flat form (typically HTML nodes).
302
335
Syntax.Match.prototype.reduce = function (append, process) {
303
336
        var start = this.offset;
304
 
        var container = jQuery('<span></span>');
 
337
        var container = document.createElement('span');
305
338
        
306
339
        append = append || Syntax.Match.defaultReduceCallback;
307
340
        
308
341
        if (this.expression && this.expression.klass) {
309
 
                container.addClass(this.expression.klass);
 
342
                if (container.className.length > 0)
 
343
                        container.className += ' ';
 
344
                
 
345
                container.className += this.expression.klass;
310
346
        }
311
347
        
312
348
        for (var i = 0; i < this.children.length; i += 1) {
339
375
        return container;
340
376
};
341
377
 
 
378
// Main nesting check - can a match contain the given match?
342
379
Syntax.Match.prototype.canContain = function (match) {
343
380
        // This is a special conditional for explicitly added ranges by the user.
344
381
        // Since user added it, we honour it no matter what.
379
416
        return false;
380
417
};
381
418
 
 
419
// Return true if the given match can be spliced in as a child.
 
420
// Checked automatically when calling _splice.
382
421
Syntax.Match.prototype.canHaveChild = function(match) {
383
422
        var only = match.expression.only;
384
423
        
405
444
        return true;
406
445
};
407
446
 
 
447
// Add a child into the list of children for a given match, if it is acceptable to do so.
 
448
// Updates the owner of the match.
 
449
// Returns null if splice failed.
408
450
Syntax.Match.prototype._splice = function(i, match) {
409
451
        if (this.canHaveChild(match)) {
410
452
                this.children.splice(i, 0, match);
424
466
// This function implements a full insertion procedure, and will break up the match to fit.
425
467
// This operation is potentially very expensive, but is used to insert custom ranges into
426
468
// the tree, if they are specified by the user. A custom <span> may cover multiple leafs in
427
 
// the tree, thus naturally it needs to be broken up.
 
469
// the tree, thus some parts of the tree may need to be split. This behavior is controlled
 
470
// by whole - if true, the tree is split, if false, the match is split.
428
471
// You should avoid using this function except in very specific cases.
429
 
Syntax.Match.prototype.insert = function(match) {
 
472
Syntax.Match.prototype.insert = function(match, whole) {
430
473
        if (!this.contains(match))
431
474
                return null;
432
475
        
433
 
        return this._insert(match);
 
476
        if (whole) {
 
477
                var top = this, i = 0;
 
478
                while (i < top.children.length) {
 
479
                        if (top.children[i].contains(match)) {
 
480
                                top = top.children[i];
 
481
                                i = 0;
 
482
                        } else {
 
483
                                i += 1;
 
484
                        }
 
485
                }
 
486
                
 
487
                return top._insertWhole(match);
 
488
        } else {
 
489
                return this._insert(match);
 
490
        }
 
491
}
 
492
 
 
493
Syntax.Match.prototype._insertWhole = function(match) {
 
494
        var parts = this.bisectAtOffsets([match.offset, match.endOffset])
 
495
        this.children = [];
 
496
        
 
497
        if (parts[0]) {
 
498
                this.children = this.children.concat(parts[0].children);
 
499
        }
 
500
        
 
501
        if (parts[1]) {
 
502
                match.children = [];
 
503
                
 
504
                // Update the match's expression based on the current position in the tree:
 
505
                if (this.expression && this.expression.owner) {
 
506
                        match.expression = this.expression.owner.getRuleForKlass(match.expression.klass) || match.expression;
 
507
                }
 
508
                
 
509
                // This probably isn't ideal, it would be better to convert all children and children-of-children
 
510
                // into a linear array and reinsert - it would be slightly more accurate in many cases.
 
511
                for (var i = 0; i < parts[1].children.length; i += 1) {
 
512
                        var child = parts[1].children[i];
 
513
                        
 
514
                        if (match.canContain(child)) {
 
515
                                match.children.push(child);
 
516
                        }
 
517
                }
 
518
                
 
519
                this.children.push(match);
 
520
        }
 
521
        
 
522
        if (parts[2]) {
 
523
                this.children = this.children.concat(parts[2].children);
 
524
        }
 
525
        
 
526
        return this;
434
527
}
435
528
 
436
529
// This is not a general tree insertion function. It is optimised to run in almost constant
437
530
// time, but data must be inserted in sorted order, otherwise you will have problems.
438
531
// This function also ensures that matches won't be broken up unless absolutely necessary.
439
 
Syntax.Match.prototype.insertAtEnd = function (match) {
 
532
Syntax.Match.prototype.insertAtEnd = function(match) {
440
533
        if (!this.contains(match)) {
441
534
                Syntax.log("Syntax Error: Child is not contained in parent node!");
442
535
                return null;
493
586
};
494
587
 
495
588
// This insertion function is relatively complex because it is required to split the match over
496
 
// several children.
 
589
// several children. This function is used infrequently and is mostly for completeness. However,
 
590
// it might be possible to remove it to reduce code.
497
591
Syntax.Match.prototype._insert = function(match) {
498
592
        if (this.children.length == 0)
499
593
                return this._splice(0, match);
522
616
                        return child._insert(match);
523
617
                }
524
618
                
525
 
                console.log("Bisect at offsets", match, child.offset, child.endOffset);
 
619
                // console.log("Bisect at offsets", match, child.offset, child.endOffset);
526
620
                var parts = match.bisectAtOffsets([child.offset, child.endOffset]);
527
 
                console.log("parts =", parts);
 
621
                // console.log("parts =", parts);
528
622
                // We now have at most three parts
529
623
                //           {------child------}   {---possibly some other child---}
530
624
                //   |--[0]--|-------[1]-------|--[2]--|
695
789
        return parts;
696
790
};
697
791
 
 
792
// Split a match at points in the tree that match a specific regular expression pattern.
 
793
// Uses the fast tree bisection algorithm, performance should be bounded O(S log N) where N is
 
794
// the total number of matches and S is the number of splits (?).
698
795
Syntax.Match.prototype.split = function(pattern) {
699
796
        var splits = [], match;
700
797
        
724
821
        this.processes = {};
725
822
};
726
823
 
727
 
Syntax.Brush.convertStringToTokenPattern = function (pattern, escape) {
728
 
        var prefix = "\\b", postfix = "\\b";
729
 
        
730
 
        if (!pattern.match(/^\w/)) {
731
 
                if (!pattern.match(/\w$/)) {
732
 
                        prefix = postfix = "";
733
 
                } else {
734
 
                        prefix = "\\B";
735
 
                }
736
 
        } else {
737
 
                if (!pattern.match(/\w$/)) {
738
 
                        postfix = "\\B";
739
 
                }
740
 
        }
741
 
        
742
 
        if (escape)
743
 
                pattern = RegExp.escape(pattern)
744
 
        
745
 
        return prefix + pattern + postfix;
746
 
}
747
 
 
748
824
// Add a parent to the brush. This brush should be loaded as a dependency.
749
825
Syntax.Brush.prototype.derives = function (name) {
750
826
        this.parents.push(name);
751
827
        this.rules.push({
752
 
                apply: function(text, expr, offset) {
753
 
                        return Syntax.brushes[name].getMatches(text, offset);
 
828
                apply: function(text, expr) {
 
829
                        return Syntax.brushes[name].getMatches(text);
754
830
                }
755
831
        });
756
832
}
767
843
        return klasses;
768
844
}
769
845
 
 
846
Syntax.Brush.convertStringToTokenPattern = function (pattern, escape) {
 
847
        var prefix = "\\b", postfix = "\\b";
 
848
        
 
849
        if (!pattern.match(/^\w/)) {
 
850
                if (!pattern.match(/\w$/)) {
 
851
                        prefix = postfix = "";
 
852
                } else {
 
853
                        prefix = "\\B";
 
854
                }
 
855
        } else {
 
856
                if (!pattern.match(/\w$/)) {
 
857
                        postfix = "\\B";
 
858
                }
 
859
        }
 
860
        
 
861
        if (escape)
 
862
                pattern = RegExp.escape(pattern)
 
863
        
 
864
        return prefix + pattern + postfix;
 
865
}
 
866
 
 
867
Syntax.Brush.MatchPattern = function (text, rule) {
 
868
        if (!rule.pattern)
 
869
                return [];
 
870
        
 
871
        // Duplicate the pattern so that the function is reentrant.
 
872
        var matches = [], pattern = new RegExp;
 
873
        pattern.compile(rule.pattern);
 
874
        
 
875
        while((match = pattern.exec(text)) !== null) {
 
876
                if (rule.matches) {
 
877
                        matches = matches.concat(rule.matches(match, rule));
 
878
                } else if (rule.brush) {
 
879
                        matches.push(Syntax.Brush.buildTree(rule, match[0], match.index));
 
880
                } else {
 
881
                        matches.push(new Syntax.Match(match.index, match[0].length, rule, match[0]));
 
882
                }
 
883
                
 
884
                if (rule.incremental) {
 
885
                        // Don't start scanning from the end of the match..
 
886
                        pattern.lastIndex = match.index + 1;
 
887
                }
 
888
        }
 
889
        
 
890
        return matches;
 
891
}
 
892
 
770
893
Syntax.Brush.prototype.push = function () {
771
894
        if (jQuery.isArray(arguments[0])) {
772
895
                var patterns = arguments[0], rule = arguments[1];
773
896
                
 
897
                var all = "(";
 
898
                
774
899
                for (var i = 0; i < patterns.length; i += 1) {
775
 
                        this.push(jQuery.extend({pattern: patterns[i]}, rule));
 
900
                        if (i > 0) all += "|";
 
901
                        
 
902
                        var p = patterns[i];
 
903
                        
 
904
                        if (p instanceof RegExp) {
 
905
                                all += p.source;
 
906
                        } else {
 
907
                                all += Syntax.Brush.convertStringToTokenPattern(p, true);
 
908
                        }
776
909
                }
 
910
                
 
911
                all += ")";
 
912
                
 
913
                this.push(jQuery.extend({
 
914
                        pattern: new RegExp(all, rule.options || 'g')
 
915
                }, rule));
777
916
        } else {
778
917
                var rule = arguments[0];
779
918
                
785
924
                if (typeof(XRegExp) !== 'undefined') {
786
925
                        rule.pattern = new XRegExp(rule.pattern);
787
926
                }
 
927
                
 
928
                // Default pattern extraction algorithm
 
929
                rule.apply = rule.apply || Syntax.Brush.MatchPattern;
788
930
 
789
 
                if (rule.pattern && rule.pattern.global) {
 
931
                if (rule.pattern && rule.pattern.global || typeof(rule.pattern) == 'undefined') {
790
932
                        this.rules.push(jQuery.extend({owner: this}, rule));
791
 
                } else if (typeof(console) != "undefined") {
 
933
                } else {
792
934
                        Syntax.log("Syntax Error: Malformed rule: ", rule);
793
935
                }
794
936
        }
795
937
};
796
938
 
797
 
Syntax.Brush.prototype.getMatchesForRule = function (text, rule, offset) {
 
939
Syntax.Brush.prototype.getMatchesForRule = function (text, rule) {
798
940
        var matches = [], match = null;
799
941
        
800
942
        // Short circuit (user defined) function:
801
 
        if (typeof rule.apply != "undefined") {
802
 
                return rule.apply(text, rule, offset);
803
 
        }
804
 
        
805
 
        // Duplicate the pattern so that the function is reentrant.
806
 
        var pattern = new RegExp;
807
 
        pattern.compile(rule.pattern);
808
 
        
809
 
        while((match = pattern.exec(text)) !== null) {
810
 
                if (rule.matches) {
811
 
                        matches = matches.concat(rule.matches(match, rule));
812
 
                } else if (rule.brush) {
813
 
                        matches.push(Syntax.brushes[rule.brush].buildTree(match[0], match.index));
814
 
                } else {
815
 
                        matches.push(new Syntax.Match(match.index, match[0].length, rule, match[0]));
816
 
                }
817
 
        }
818
 
        
819
 
        if (offset && offset > 0) {
820
 
                for (var i = 0; i < matches.length; i += 1) {
821
 
                        matches[i].shift(offset);
822
 
                }
 
943
        if (typeof(rule.apply) != 'undefined') {
 
944
                matches = rule.apply(text, rule);
823
945
        }
824
946
        
825
947
        if (rule.debug) {
826
 
                Syntax.log("matches", matches);
 
948
                Syntax.log("Syntax matches:", rule, text, matches);
827
949
        }
828
950
        
829
951
        return matches;
830
952
};
831
953
 
832
 
Syntax.Brush.prototype.getMatches = function(text, offset) {
 
954
Syntax.Brush.prototype.getRuleForKlass = function (klass) {
 
955
        for (var i = 0; i < this.rules.length; i += 1) {
 
956
                if (this.rules[i].klass == klass) {
 
957
                        return this.rules[i];
 
958
                }
 
959
        }
 
960
        
 
961
        return null;
 
962
}
 
963
 
 
964
// Get all matches from a given block of text.
 
965
Syntax.Brush.prototype.getMatches = function(text) {
833
966
        var matches = [];
834
967
        
835
968
        for (var i = 0; i < this.rules.length; i += 1) {
836
 
                matches = matches.concat(this.getMatchesForRule(text, this.rules[i], offset));
 
969
                matches = matches.concat(this.getMatchesForRule(text, this.rules[i]));
837
970
        }
838
971
        
839
972
        return matches;
840
973
};
841
974
 
 
975
// A helper function for building a tree from a specific rule.
 
976
// Typically used where sub-trees are required, e.g. CSS brush in HTML brush.
 
977
Syntax.Brush.buildTree = function(rule, text, offset, additionalMatches) {
 
978
        var match = Syntax.brushes[rule.brush].buildTree(text, offset, additionalMatches);
 
979
        
 
980
        jQuery.extend(match.expression, rule);
 
981
        
 
982
        return match;
 
983
}
 
984
 
 
985
// This function builds a tree from a given block of text.
 
986
// This is done by applying all rules to the text to get a complete list of matches,
 
987
// sorting them in order, and inserting them into a syntax tree data structure.
 
988
// Additional matches are forcefully inserted into the tree.
 
989
// Provide an offset if the text is offset in a larger block of text. Matches
 
990
// will be shifted along appropriately.
842
991
Syntax.Brush.prototype.buildTree = function(text, offset, additionalMatches) {
843
992
        offset = offset || 0;
844
993
        
845
994
        // Fixes code that uses \r\n for line endings. /$/ matches both \r\n, which is a problem..
846
 
        text = text.replace(/\r/g, "");
847
 
        
848
 
        var matches = this.getMatches(text, offset);
 
995
        text = text.replace(/\r/g, '');
 
996
        
 
997
        var matches = this.getMatches(text);
 
998
        
 
999
        // Shift matches if offset is provided.
 
1000
        if (offset && offset > 0) {
 
1001
                for (var i = 0; i < matches.length; i += 1) {
 
1002
                        matches[i].shift(offset);
 
1003
                }
 
1004
        }
849
1005
        
850
1006
        var top = new Syntax.Match(offset, text.length, {klass: this.allKlasses().join(" "), allow: '*', owner: this}, text);
851
1007
 
867
1023
        return top;
868
1024
};
869
1025
 
870
 
// Matches is optional, and provides a set of pre-existing matches.
871
 
Syntax.Brush.prototype.process = function(text, matches) {
 
1026
// This function builds a syntax tree from the given text and matches (optional).
 
1027
// The syntax tree is then flattened into html using a variety of functions.
 
1028
//
 
1029
// By default, you can't control reduction process through this function, but
 
1030
// it is possible to control the element conversion process by replace
 
1031
// .reduce(null, ...) with  .reduce(reduceCallback, ...)
 
1032
// See Syntax.Match.defaultReduceCallback for more details about interface.
 
1033
//
 
1034
// Matches is optional, and provides a set of pre-existing matches to add
 
1035
// to the tree.
 
1036
// Options are passed to element level processing functions.
 
1037
Syntax.Brush.prototype.process = function(text, matches, options) {
872
1038
        var top = this.buildTree(text, 0, matches);
873
1039
        
874
1040
        var lines = top.split(/\n/g);
875
1041
        
876
 
        var html = jQuery('<pre class="syntax"></pre>');
 
1042
        var html = document.createElement('pre');
 
1043
        html.className = 'syntax';
877
1044
        
878
1045
        for (var i = 0; i < lines.length; i += 1) {
879
1046
                var line = lines[i].reduce(null, function (container, match) {
880
1047
                        if (match.expression) {
881
1048
                                if (match.expression.process) {
882
 
                                        container = match.expression.process(container, match);
 
1049
                                        container = match.expression.process(container, match, options);
883
1050
                                }
884
1051
                                
885
 
                                var process = match.expression.owner.processes[match.expression.klass];
886
 
                                if (process) {
887
 
                                        container = process(container, match);
 
1052
                                if (match.expression.owner) {
 
1053
                                        var process = match.expression.owner.processes[match.expression.klass];
 
1054
                                        if (process) {
 
1055
                                                container = process(container, match, options);
 
1056
                                        }
888
1057
                                }
889
1058
                        }
890
1059
                        return container;
891
1060
                });
892
1061
                
893
 
                html.append(line);
 
1062
                html.appendChild(line);
894
1063
        }
895
1064
        
896
1065
        return html;
897
1066
};
898
1067
 
 
1068
// Highlights a given block of text with a given set of options.
 
1069
// options.brush should specify the brush to use, either by direct reference
 
1070
// or name.
 
1071
// Callback will be called with (highlighted_html, brush_used, original_text, options)
 
1072
Syntax.highlightText = function(text, options, callback) {
 
1073
        var brushName = (options.brush || 'plain').toLowerCase();
 
1074
        
 
1075
        brushName = Syntax.aliases[brushName] || brushName;
 
1076
        
 
1077
        Syntax.brushes.get(brushName, function(brush) {
 
1078
                if (options.tabWidth) {
 
1079
                        // Calculate the tab expansion and offsets
 
1080
                        replacement = Syntax.convertTabsToSpaces(text, options.tabWidth);
 
1081
                        
 
1082
                        // Update any existing matches
 
1083
                        if (options.matches && options.matches.length) {
 
1084
                                var linearOffsets = Syntax.convertToLinearOffsets(replacement.offsets, text.length);
 
1085
                                options.matches = Syntax.updateMatchesWithOffsets(options.matches, linearOffsets, replacement.text);
 
1086
                        }
 
1087
                        
 
1088
                        text = replacement.text;
 
1089
                }
 
1090
                
 
1091
                var html = brush.process(text, options.matches, options);
 
1092
                
 
1093
                if (options.linkify !== false) {
 
1094
                        jQuery('span.href', html).each(function(){
 
1095
                                jQuery(this).replaceWith(jQuery('<a>').attr('href', this.innerHTML).text(this.innerHTML));
 
1096
                        });
 
1097
                }
 
1098
                
 
1099
                callback(html, brush, text, options);
 
1100
        });
 
1101
}
 
1102
 
 
1103
// Highlight a given set of elements with a set of options.
 
1104
// Callback will be called once per element with (options, highlighted_html, original_container)
899
1105
Syntax.highlight = function (elements, options, callback) {
900
1106
        if (typeof(options) === 'function') {
901
1107
                callback = options;
903
1109
        }
904
1110
        
905
1111
        options.layout = options.layout || 'preformatted';
 
1112
        options.matches = [];
906
1113
        
907
1114
        if (typeof(options.tabWidth) === 'undefined') {
908
1115
                options.tabWidth = 4;
911
1118
        elements.each(function () {
912
1119
                var container = jQuery(this);
913
1120
                
914
 
                // We can augment the plain text to extract existing annotations.
915
 
                var matches = Syntax.extractElementMatches(container);
916
 
                var text = Syntax.getCDATA(container);
 
1121
                // We can augment the plain text to extract existing annotations (e.g. <span class="foo">...</span>).
 
1122
                options.matches = options.matches.concat(Syntax.extractElementMatches(container));
 
1123
                
 
1124
                var text = Syntax.innerText(this);
917
1125
                
918
1126
                var match = text.match(/-\*- mode: (.+?);(.*?)-\*-/i);
919
1127
                var endOfSecondLine = text.indexOf("\n", text.indexOf("\n") + 1);
920
 
                
 
1128
 
921
1129
                if (match && match.index < endOfSecondLine) {
922
1130
                        options.brush = options.brush || match[1];
923
1131
                        var modeline = match[2];
924
 
                        
 
1132
 
925
1133
                        var mode = /([a-z\-]+)\:(.*?)\;/gi;
926
 
                        
 
1134
 
927
1135
                        while((match = mode.exec(modeline)) !== null) {
928
1136
                                var setter = Syntax.modeLineOptions[match[1]];
929
 
                                
 
1137
 
930
1138
                                if (setter) {
931
1139
                                        setter(match[1], match[2], options);
932
1140
                                }
933
1141
                        }
934
1142
                }
935
1143
                
936
 
                var brushName = (options.brush || 'plain').toLowerCase();
937
 
                
938
 
                brushName = Syntax.aliases[brushName] || brushName;
939
 
                
940
 
                Syntax.brushes.get(brushName, function(brush) {
941
 
                        if (options.tabWidth) {
942
 
                                // Calculate the tab expansion and offsets
943
 
                                replacement = Syntax.convertTabsToSpaces(text, options.tabWidth);
944
 
                                
945
 
                                // Update any existing matches
946
 
                                if (matches && matches.length) {
947
 
                                        var linearOffsets = Syntax.convertToLinearOffsets(replacement.offsets, text.length);
948
 
                                        matches = Syntax.updateMatchesWithOffsets(matches, linearOffsets, replacement.text);
949
 
                                }
950
 
                                
951
 
                                text = replacement.text;
952
 
                        }
953
 
                        
954
 
                        var html = brush.process(text, matches);
955
 
                        
956
 
                        if (options.linkify !== false) {
957
 
                                jQuery('span.href', html).each(function(){
958
 
                                        jQuery(this).replaceWith(jQuery('<a>').attr('href', this.innerHTML).text(this.innerHTML));
959
 
                                });
960
 
                        }
961
 
                        
 
1144
                Syntax.highlightText(text, options, function(html, brush/*, text, options*/) {
962
1145
                        Syntax.layouts.get(options.layout, function(layout) {
963
 
                                html = layout(options, html, container);
 
1146
                                html = layout(options, $(html), $(container));
 
1147
 
 
1148
                                // If there is a theme specified, ensure it is added to the top level class.
 
1149
                                if (options.theme) {
 
1150
                                        // Load dependencies
 
1151
                                        var themes = Syntax.themes[options.theme];
 
1152
                                        for (var i = 0; i < themes.length; i += 1) {
 
1153
                                                html.addClass("syntax-theme-" + themes[i]);
 
1154
                                        }
 
1155
 
 
1156
                                        // Add the base theme
 
1157
                                        html.addClass("syntax-theme-" + options.theme);
 
1158
                                }
964
1159
 
965
1160
                                if (brush.postprocess) {
966
1161
                                        html = brush.postprocess(options, html, container);