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) {
30
for (var i = 0; elems[i]; i++) {
33
// Get the text from text nodes and CDATA nodes
34
if (elem.nodeType === 3 || elem.nodeType === 4) {
35
cdata += elem.nodeValue;
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;
44
arguments.callee(elem.childNodes);
46
// Traverse everything else, except comment nodes
47
} else if (elem.nodeType !== 8) {
48
arguments.callee(elem.childNodes);
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) {
32
if (element.nodeName == 'BR') {
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;
42
return text.replace(/\r\n?/g, '\n');
56
45
// Convert to stack based implementation
67
56
offset += elem.nodeValue.length;
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);
73
matches.push(new Syntax.Match(offset, text.length, expr, text));
61
matches.push(new Syntax.Match(offset, text.length, {
62
klass: elem.className,
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);
123
118
return {text: text, offsets: offsets};
121
// This function converts from a compressed set of offsets of the form:
123
// [offset, width, totalOffset],
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 = [];
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]);
143
// If so, move to the next offset.
148
// If there is no next offset we assume this one to the end.
134
149
changes.push(offsets[current][2]);
140
152
changes.push(changes[changes.length-1] || 0);
221
// Used to create processing functions that automatically link to remote documentation.
204
222
Syntax.lib.webLinkProcess = function (queryURI, lucky) {
206
224
queryURI = "http://www.google.com/search?btnI=I&q=" + encodeURIComponent(queryURI + " ");
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)
232
var a = document.createElement('a');
233
a.href = queryURI + encodeURIComponent(Syntax.innerText(element));
234
a.className = element.className;
236
// Move children from <element> to <a>
237
while (element.childNodes.length > 0)
238
a.appendChild(element.childNodes[0]);
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;
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']};
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};
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'};
233
263
Syntax.lib.xmlComment = {pattern: /(<|<)!--[\s\S]*?--(>|>)/gm, klass: 'comment'};
234
264
Syntax.lib.webLink = {pattern: /\w+:\/\/[\w\-.\/?%&=@:;#]*/g, klass: 'href'};
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);
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);
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);
299
container[0].appendChild(node);
331
container.appendChild(node);
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');
306
339
append = append || Syntax.Match.defaultReduceCallback;
308
341
if (this.expression && this.expression.klass) {
309
container.addClass(this.expression.klass);
342
if (container.className.length > 0)
343
container.className += ' ';
345
container.className += this.expression.klass;
312
348
for (var i = 0; i < this.children.length; i += 1) {
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))
433
return this._insert(match);
477
var top = this, i = 0;
478
while (i < top.children.length) {
479
if (top.children[i].contains(match)) {
480
top = top.children[i];
487
return top._insertWhole(match);
489
return this._insert(match);
493
Syntax.Match.prototype._insertWhole = function(match) {
494
var parts = this.bisectAtOffsets([match.offset, match.endOffset])
498
this.children = this.children.concat(parts[0].children);
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;
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];
514
if (match.canContain(child)) {
515
match.children.push(child);
519
this.children.push(match);
523
this.children = this.children.concat(parts[2].children);
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!");
522
616
return child._insert(match);
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]--|
724
821
this.processes = {};
727
Syntax.Brush.convertStringToTokenPattern = function (pattern, escape) {
728
var prefix = "\\b", postfix = "\\b";
730
if (!pattern.match(/^\w/)) {
731
if (!pattern.match(/\w$/)) {
732
prefix = postfix = "";
737
if (!pattern.match(/\w$/)) {
743
pattern = RegExp.escape(pattern)
745
return prefix + pattern + postfix;
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);
846
Syntax.Brush.convertStringToTokenPattern = function (pattern, escape) {
847
var prefix = "\\b", postfix = "\\b";
849
if (!pattern.match(/^\w/)) {
850
if (!pattern.match(/\w$/)) {
851
prefix = postfix = "";
856
if (!pattern.match(/\w$/)) {
862
pattern = RegExp.escape(pattern)
864
return prefix + pattern + postfix;
867
Syntax.Brush.MatchPattern = function (text, rule) {
871
// Duplicate the pattern so that the function is reentrant.
872
var matches = [], pattern = new RegExp;
873
pattern.compile(rule.pattern);
875
while((match = pattern.exec(text)) !== null) {
877
matches = matches.concat(rule.matches(match, rule));
878
} else if (rule.brush) {
879
matches.push(Syntax.Brush.buildTree(rule, match[0], match.index));
881
matches.push(new Syntax.Match(match.index, match[0].length, rule, match[0]));
884
if (rule.incremental) {
885
// Don't start scanning from the end of the match..
886
pattern.lastIndex = match.index + 1;
770
893
Syntax.Brush.prototype.push = function () {
771
894
if (jQuery.isArray(arguments[0])) {
772
895
var patterns = arguments[0], rule = arguments[1];
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 += "|";
904
if (p instanceof RegExp) {
907
all += Syntax.Brush.convertStringToTokenPattern(p, true);
913
this.push(jQuery.extend({
914
pattern: new RegExp(all, rule.options || 'g')
778
917
var rule = arguments[0];
785
924
if (typeof(XRegExp) !== 'undefined') {
786
925
rule.pattern = new XRegExp(rule.pattern);
928
// Default pattern extraction algorithm
929
rule.apply = rule.apply || Syntax.Brush.MatchPattern;
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") {
792
934
Syntax.log("Syntax Error: Malformed rule: ", rule);
797
Syntax.Brush.prototype.getMatchesForRule = function (text, rule, offset) {
939
Syntax.Brush.prototype.getMatchesForRule = function (text, rule) {
798
940
var matches = [], match = null;
800
942
// Short circuit (user defined) function:
801
if (typeof rule.apply != "undefined") {
802
return rule.apply(text, rule, offset);
805
// Duplicate the pattern so that the function is reentrant.
806
var pattern = new RegExp;
807
pattern.compile(rule.pattern);
809
while((match = pattern.exec(text)) !== null) {
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));
815
matches.push(new Syntax.Match(match.index, match[0].length, rule, match[0]));
819
if (offset && offset > 0) {
820
for (var i = 0; i < matches.length; i += 1) {
821
matches[i].shift(offset);
943
if (typeof(rule.apply) != 'undefined') {
944
matches = rule.apply(text, rule);
825
947
if (rule.debug) {
826
Syntax.log("matches", matches);
948
Syntax.log("Syntax matches:", rule, text, matches);
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];
964
// Get all matches from a given block of text.
965
Syntax.Brush.prototype.getMatches = function(text) {
833
966
var matches = [];
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]));
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);
980
jQuery.extend(match.expression, rule);
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;
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, "");
848
var matches = this.getMatches(text, offset);
995
text = text.replace(/\r/g, '');
997
var matches = this.getMatches(text);
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);
850
1006
var top = new Syntax.Match(offset, text.length, {klass: this.allKlasses().join(" "), allow: '*', owner: this}, text);
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.
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.
1034
// Matches is optional, and provides a set of pre-existing matches to add
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);
874
1040
var lines = top.split(/\n/g);
876
var html = jQuery('<pre class="syntax"></pre>');
1042
var html = document.createElement('pre');
1043
html.className = 'syntax';
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);
885
var process = match.expression.owner.processes[match.expression.klass];
887
container = process(container, match);
1052
if (match.expression.owner) {
1053
var process = match.expression.owner.processes[match.expression.klass];
1055
container = process(container, match, options);
890
1059
return container;
1062
html.appendChild(line);
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
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();
1075
brushName = Syntax.aliases[brushName] || brushName;
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);
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);
1088
text = replacement.text;
1091
var html = brush.process(text, options.matches, options);
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));
1099
callback(html, brush, text, options);
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;
911
1118
elements.each(function () {
912
1119
var container = jQuery(this);
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));
1124
var text = Syntax.innerText(this);
918
1126
var match = text.match(/-\*- mode: (.+?);(.*?)-\*-/i);
919
1127
var endOfSecondLine = text.indexOf("\n", text.indexOf("\n") + 1);
921
1129
if (match && match.index < endOfSecondLine) {
922
1130
options.brush = options.brush || match[1];
923
1131
var modeline = match[2];
925
1133
var mode = /([a-z\-]+)\:(.*?)\;/gi;
927
1135
while((match = mode.exec(modeline)) !== null) {
928
1136
var setter = Syntax.modeLineOptions[match[1]];
931
1139
setter(match[1], match[2], options);
936
var brushName = (options.brush || 'plain').toLowerCase();
938
brushName = Syntax.aliases[brushName] || brushName;
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);
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);
951
text = replacement.text;
954
var html = brush.process(text, matches);
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));
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));
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]);
1156
// Add the base theme
1157
html.addClass("syntax-theme-" + options.theme);
965
1160
if (brush.postprocess) {
966
1161
html = brush.postprocess(options, html, container);