// This file is part of the “jQuery.Syntax” project, and is distributed under the MIT License. // Copyright © 2011 Samuel G. D. Williams. <www.oriontransfer.co.nz> // See <jquery.syntax.js> for licensing details.

if (!RegExp.prototype.indexOf) {

RegExp.indexOf = function (match, index) {
        return match[0].indexOf(match[index]) + match.index;
};

}

if (!RegExp.prototype.escape) {

RegExp.escape = function (pattern) {
        return pattern.replace(/[\-\[\]{}()*+?.\\\^$|,#\s]/g, "\\$&");
};

}

if (!String.prototype.repeat) {

String.prototype.repeat = function(l) {
        return new Array(l+1).join(this);
};

}

// Return the inner text of an element - must preserve whitespace. // Avoid returning r characters. Syntax.innerText = function(element) {

var text;

if (!element) {
        return "";
}

if (element.nodeName == 'BR') {
        return '\n';
} else if (element.textContent) {
        // W3C: FF, Safari, Chrome, etc.
        text = element.textContent;
} else if (document.body.innerText) {
        // IE, other older browsers.
        text = element.innerText;
}

return text.replace(/\r\n?/g, '\n');

}

Syntax.extractTextFromSelection = function(selection) {

var buffer = "";

for (var i = 0; i < selection.rangeCount; i += 1) {
        var range = selection.getRangeAt(i),
                text = range.toString();

        buffer += text;
}

return buffer;

}

Syntax.copyCode = function(event) {

var
        selection = window.getSelection(),
        text = Syntax.extractTextFromSelection(selection);

event.clipboardData.setData('text/plain', text);

return false;

}

// Convert to stack based implementation Syntax.extractElementMatches = function (elems, offset) {

var matches = [], current = [elems];
offset = offset || 0;

(function (elems) {
        for (var i = 0; elems[i]; i++) {
                var text = null, elem = elems[i];

                if (elem.nodeType === 3 || elem.nodeType === 4) {
                        offset += elem.nodeValue.length;

                } else if (elem.nodeType === 1) {
                        var text = Syntax.innerText(elem);

                        matches.push(new Syntax.Match(offset, text.length, {
                                klass: elem.className,
                                force: true,
                                element: elem,
                                allow: '*'
                        }, text));
                }

                // Traverse everything, except comment nodes
                if (elem.nodeType !== 8 && elem.children) {
                        arguments.callee(elem.childNodes, offset);
                }
        }
})(elems);

// Remove the top level element, since this will be recreated based on the supplied configuration.
// Maybe there is a better way to achieve this?
matches.shift();

return matches;

}

// A helper function which automatically matches expressions with capture groups from the regular expression match. // Each argument position corresponds to the same index regular expression group. // Or, override by providing rule.index Syntax.extractMatches = function() {

var rules = arguments;

return function(match, expr) {
        var matches = [];

        for (var i = 0; i < rules.length; i += 1) {
                var rule = rules[i], index = i+1;

                if (rule == null) {
                        continue;
                }

                if (typeof(rule.index) != 'undefined') {
                        index = rule.index;
                }

                if (rule.debug) {
                        console.log("extractMatches", rule, index, match[index], match);
                }

                if (match[index].length > 0) {
                        if (rule.brush) {
                                matches.push(Syntax.Brush.buildTree(rule, match[index], RegExp.indexOf(match, index)));
                        } else {
                                var expression = jQuery.extend({owner: expr.owner}, rule);

                                matches.push(new Syntax.Match(RegExp.indexOf(match, index), match[index].length, expression, match[index]));
                        }
                }
        }

        return matches;
};

};

// Used to create processing functions that automatically link to remote documentation. Syntax.lib.webLinkProcess = function (queryURI, lucky) {

if (lucky) {
        queryURI = "http://www.google.com/search?btnI=I&q=" + encodeURIComponent(queryURI + " ");
}

return function (element, match, options) {
        // Per-code block linkification control.
        if (options.linkify === false)
                return element;

        var a = document.createElement('a');
        a.href = queryURI + encodeURIComponent(Syntax.innerText(element));
        a.className = element.className;

        // Move children from <element> to <a>
        while (element.childNodes.length > 0)
                a.appendChild(element.childNodes[0]);

        return a;
};

};

// Global brush registration function. Syntax.register = function (name, callback) {

var brush = Syntax.brushes[name] = new Syntax.Brush();
brush.klass = name;

callback(brush);

};

// Library of helper patterns Syntax.lib.cStyleComment = {pattern: //*[sS]*?*//gm, klass: 'comment', allow: ['href']}; Syntax.lib.cppStyleComment = {pattern: ///.*$/gm, klass: 'comment', allow: ['href']}; Syntax.lib.perlStyleComment = {pattern: /#.*$/gm, klass: 'comment', allow: ['href']};

// A hackity hack: Syntax.lib.perlStyleRegularExpression = {pattern: /B/(|\.)/[a-z](?=s*($|))/gm, klass: 'constant', incremental: true}; Syntax.lib.rubyStyleRegularExpression = {pattern: /B/(|\.)/[a-z](?=s*($||do))/gm, klass: 'constant', incremental: true};

Syntax.lib.cStyleFunction = {pattern: /([a-z_]*)s*(/gi, matches: Syntax.extractMatches({klass: 'function'})}; Syntax.lib.camelCaseType = {pattern: /b_*[w]*b/g, klass: 'type'}; Syntax.lib.cStyleType = {pattern: /b[a-z][w]*_tb/gi, klass: 'type'};

Syntax.lib.xmlComment = {pattern: /(&lt;|<)!--*?–(&gt;|>)/gm, klass: 'comment'}; Syntax.lib.webLink = {pattern: /w+://*/g, klass: 'href'};

Syntax.lib.hexNumber = {pattern: /b0x+/g, klass: 'constant'}; Syntax.lib.decimalNumber = {pattern: /b?[0-9]*.?+([eE]?[0-9]+)?/g, klass: 'constant'};

Syntax.lib.doubleQuotedString = {pattern: /“([^\”n]|\.)*“/g, klass: 'string'}; Syntax.lib.singleQuotedString = {pattern: /'([^\'n]|\.)*'/g, klass: 'string'}; Syntax.lib.multiLineDoubleQuotedString = {pattern: /”([^\“]|\.)*”/g, klass: 'string'}; Syntax.lib.multiLineSingleQuotedString = {pattern: /'([^\']|\.)*'/g, klass: 'string'}; Syntax.lib.stringEscape = {pattern: /\./g, klass: 'escape', only: ['string']};

// Main match constructor. Make sure value is the correct size. Syntax.Match = function (offset, length, expression, value) {

this.offset = offset;
this.endOffset = offset + length;
this.length = length;
this.expression = expression;
this.value = value;
this.children = [];
this.parent = null;

// When a node is bisected, this points to the next part.
this.next = null;

};

// Shifts an entire tree forward or backwards. Syntax.Match.prototype.shift = function (offset, text) {

this.adjust(offset, null, text);

for (var i = 0; i < this.children.length; i++) {
        this.children[i].shift(offset, text)
}

};

// C the current match to have different offset and length. Syntax.Match.prototype.adjust = function (offset, length, text) {

this.offset += offset;
this.endOffset += offset;

if (length) {
        this.length = length;
        this.endOffset = this.offset + length;
}

if (text) {
        this.value = text.substr(this.offset, this.length);
}

};

// Sort helper for sorting matches in forward order (e.g. same as the text that they were extracted from) Syntax.Match.sort = function (a,b) {

return (a.offset - b.offset) || (b.length - a.length);

};

// Is the given match contained in the range of the parent match? Syntax.Match.prototype.contains = function (match) {

return (match.offset >= this.offset) && (match.endOffset <= this.endOffset);

};

// Reduce a givent tree node into an html node. Syntax.Match.defaultReduceCallback = function (node, container) {

// We avoid using jQuery in this function since it is incredibly performance sensitive.
// Using jQuery jQuery.fn.append() can reduce performance by as much as 1/3rd.
if (typeof(node) === 'string') {
        node = document.createTextNode(node);
}

container.appendChild(node);

};

// Convert a tree of matches into some flat form (typically HTML nodes). Syntax.Match.prototype.reduce = function (append, process) {

var start = this.offset;
var container = document.createElement('span');

append = append || Syntax.Match.defaultReduceCallback;

if (this.expression && this.expression.klass) {
        if (container.className.length > 0)
                container.className += ' ';

        container.className += this.expression.klass;
}

if (this.className) {
        container.className += ' ';
        container.className += this.className;
}

for (var i = 0; i < this.children.length; i += 1) {
        var child = this.children[i], end = child.offset;

        if (child.offset < this.offset) {
                console.log("Syntax Warning: Offset of child", child, "is before offset of parent", this);
        }

        var text = this.value.substr(start - this.offset, end - start);

        append(text, container);
        append(child.reduce(append, process), container);

        start = child.endOffset;
}

if (start === this.offset) {
        append(this.value, container);
} else if (start < this.endOffset) {
        append(this.value.substr(start - this.offset, this.endOffset - start), container);
} else if (start > this.endOffset) {
        console.log("Syntax Warning: Start position " + start + " exceeds end of value " + this.endOffset);
}

if (process) {
        container = process(container, this);
}

return container;

};

// Main nesting check - can a match contain the given match? Syntax.Match.prototype.canContain = function (match) {

// This is a special conditional for explicitly added ranges by the user.
// Since user added it, we honour it no matter what.
if (match.expression.force) {
        return true;
}

// Can't add anything into complete trees.
if (this.complete) {
        return false;
}

// match.expression.only will be checked on insertion using this.canHaveChild(match)
if (match.expression.only) {
        return true;
}

// If allow is undefined, default behaviour is no children.
if (typeof(this.expression.allow) === 'undefined') {
        return false;
}

// false if {disallow: [..., klass, ...]}
if (jQuery.isArray(this.expression.disallow) && jQuery.inArray(match.expression.klass, this.expression.disallow) !== -1) {
        return false;
}

// true if {allow: '*'}
if (this.expression.allow === '*') {
        return true;
}

// true if {allow: [..., klass, ...]}
if (jQuery.isArray(this.expression.allow) && jQuery.inArray(match.expression.klass, this.expression.allow) !== -1) {
        return true;
}

return false;

};

// Return true if the given match can be spliced in as a child. // Checked automatically when calling _splice. Syntax.Match.prototype.canHaveChild = function(match) {

var only = match.expression.only;

// This condition is fairly slow
if (only) {
        var cur = this;

        while (cur !== null) {
                if (jQuery.inArray(cur.expression.klass, only) !== -1) {
                        return true;
                }

                cur = cur.parent;

                // We don't traverse into other trees.
                if (cur && cur.complete) {
                        break;
                }
        }

        return false;
}

return true;

};

// Add a child into the list of children for a given match, if it is acceptable to do so. // Updates the owner of the match. // Returns null if splice failed. Syntax.Match.prototype._splice = function(i, match) {

if (this.canHaveChild(match)) {
        this.children.splice(i, 0, match);
        match.parent = this;

        // For matches added using tags.
        if (!match.expression.owner) {
                match.expression.owner = this.expression.owner;
        }

        return this;
} else {
        return null;
}

};

// This function implements a full insertion procedure, and will break up the match to fit. // This operation is potentially very expensive, but is used to insert custom ranges into // the tree, if they are specified by the user. A custom <span> may cover multiple leafs in // the tree, thus some parts of the tree may need to be split. This behavior is controlled // by whole - if true, the tree is split, if false, the match is split. // You should avoid using this function except in very specific cases. Syntax.Match.prototype.insert = function(match, whole) {

if (!this.contains(match))
        return null;

if (whole) {
        var top = this, i = 0;
        while (i < top.children.length) {
                if (top.children[i].contains(match)) {
                        top = top.children[i];
                        i = 0;
                } else {
                        i += 1;
                }
        }

        return top._insertWhole(match);
} else {
        return this._insert(match);
}

}

Syntax.Match.prototype._insertWhole = function(match) {

var parts = this.bisectAtOffsets([match.offset, match.endOffset])
this.children = [];

if (parts[0]) {
        this.children = this.children.concat(parts[0].children);
}

if (parts[1]) {
        match.children = [];

        // Update the match's expression based on the current position in the tree:
        if (this.expression && this.expression.owner) {
                match.expression = this.expression.owner.getRuleForKlass(match.expression.klass) || match.expression;
        }

        // This probably isn't ideal, it would be better to convert all children and children-of-children
        // into a linear array and reinsert - it would be slightly more accurate in many cases.
        for (var i = 0; i < parts[1].children.length; i += 1) {
                var child = parts[1].children[i];

                if (match.canContain(child)) {
                        match.children.push(child);
                }
        }

        this.children.push(match);
}

if (parts[2]) {
        this.children = this.children.concat(parts[2].children);
}

return this;

}

// This is not a general tree insertion function. It is optimised to run in almost constant // time, but data must be inserted in sorted order, otherwise you will have problems. // This function also ensures that matches won't be broken up unless absolutely necessary. Syntax.Match.prototype.insertAtEnd = function(match) {

if (!this.contains(match)) {
        console.log("Syntax Error: Child is not contained in parent node!");
        return null;
}

if (!this.canContain(match)) {
        return null;
}

if (this.children.length > 0) {
        var i = this.children.length-1;
        var child = this.children[i];

        if (match.offset < child.offset) {
                // Displacement: Before or LHS Overlap
                // This means that the match has actually occurred before the last child.
                // This is a bit of an unusual situation because the matches SHOULD be in
                // sorted order.
                // However, we are sure that the match is contained in this node. This situation
                // sometimes occurs when sorting existing branches with matches that are supposed
                // to be within that branch. When we insert the match into the branch, there are
                // matches that technically should have been inserted afterwards.
                // Normal usage should avoid this case, and this is best for performance.
                if (match.force) {
                        return this._insert(match);
                } else {
                        return null;
                }
        } else if (match.offset < child.endOffset) {
                if (match.endOffset <= child.endOffset) { 
                        // Displacement: Contains
                        //console.log("displacement => contains");
                        var result = child.insertAtEnd(match);
                        return result;
                } else {
                        // Displacement: RHS Overlap
                        if (match.force) {
                                return this._insert(match);
                        } else {
                                return null;
                        }
                }
        } else {
                // Displacement: After
                return this._splice(i+1, match);
        }
} else {
        // Displacement: Contains [but currently no children]
        return this._splice(0, match);
}

};

// This insertion function is relatively complex because it is required to split the match over // several children. This function is used infrequently and is mostly for completeness. However, // it might be possible to remove it to reduce code. Syntax.Match.prototype._insert = function(match) {

if (this.children.length == 0)
        return this._splice(0, match);

for (var i = 0; i < this.children.length; i += 1) {
        var child = this.children[i];

        // If the match ends before this child, it must be before it.
        if (match.endOffset <= child.offset)
                return this._splice(i, match);

        // If the match starts after this child, we continue.
        if (match.offset >= child.endOffset)
                continue;

        // There are four possibilities... 
        // ... with the possibility of overlapping children on the RHS.
        //           {------child------}   {---possibly some other child---}
        //   |----------complete overlap---------|
        //   |--lhs overlap--|
        //             |--contains--|
        //                       |--rhs overlap--|

        // First, the easiest case:
        if (child.contains(match)) {
                return child._insert(match);
        }

        // console.log("Bisect at offsets", match, child.offset, child.endOffset);
        var parts = match.bisectAtOffsets([child.offset, child.endOffset]);
        // console.log("parts =", parts);
        // We now have at most three parts
        //           {------child------}   {---possibly some other child---}
        //   |--[0]--|-------[1]-------|--[2]--|

        // console.log("parts", parts);

        if (parts[0]) {
                this._splice(i, parts[0])
        }

        if (parts[1]) {
                child.insert(parts[1])
        }

        // Continue insertion at this level with remainder.
        if (parts[2]) {
                match = parts[2]
        } else {
                return this;
        }
}

// If we got this far, the match wasn't [completely] inserted into the list of existing children, so it must be on the end.
this._splice(this.children.length, match);

}

// This algorithm recursively bisects the tree at a given offset, but it does this efficiently by folding multiple bisections // at a time. // Splits: / / / // Tree: |————————-Top————————-| // |————A——————–| |——C——-| // |——-B———-| // Step (1): // Split Top into 4 parts: // |——/—————-/——————-/———| // For each part, check if there are any children that cover this part. // If there is a child, recursively call bisect with all splits. // Step (1-1): // Split A into parts: // |——/—–A———-/———| // For each part, check if there are any children that cover this part. // If there is a child, recursively call bisect with all splits. // Step (1-1-1): // Split B into parts: // |——-B—/——| // No children covered by split. Return array of two parts, B1, B2. // Step (1-2): // Enumerate the results of splitting the child and merge piece-wise into own parts // |——/—–A———-/———| // |——B1—|–B2–| // Finished merging children, return array of three parts, A1, A2, A3 // Step (2): // Enumerate the results of splitting the child and merge piece-wise into own parts. // |——/—————-/——————-/———| // |–A1–|——-A2——-|—-A3—| // |——B1—|–B2–| // Continue by splitting next child, C. // Once all children have been split and merged, return all parts, T1, T2, T3, T4. // The new tree: // |–T1–|——-T2——-|——–T3———|—T4—| // |–A1–|——-A2——-|—-A3—| |–C1–|—C2–| // |——B1—|–B2–| // // The new structure is as follows: // T1 <- A1 // T2 <- A2 <- B1 // T3 <- A3 <- B2 // - C1 // T4 <- C2 // Syntax.Match.prototype.bisectAtOffsets = function(splits) {

var parts = [], start = this.offset, prev = null, children = jQuery.merge([], this.children);

// Copy the array so we can modify it.
splits = splits.slice(0);

// We need to split including the last part.
splits.push(this.endOffset);

splits.sort(function (a,b) {
        return a-b;
});

// We build a set of top level matches by looking at each split point and
// creating a new match from the end of the previous match to the split point.
for (var i = 0; i < splits.length; i += 1) {
        var offset = splits[i];

        // The split offset is past the end of the match, so there are no more possible
        // splits.
        if (offset > this.endOffset) {
                break;
        }

        // We keep track of null parts if the offset is less than the start
        // so that things align up as expected with the requested splits.
        if (
                offset < this.offset // If the split point is less than the start of the match.
                || (offset - start) == 0 // If the match would have effectively zero length.
        ) {
                parts.push(null); // Preserve alignment with splits.
                start = offset;
                continue;
        }

        // Even if the previous split was out to the left, we align up the start
        // to be at the start of the match we are bisecting.
        if (start < this.offset)
                start = this.offset;

        var match = new Syntax.Match(start, offset - start, this.expression);
        match.value = this.value.substr(start - this.offset, match.length);

        if (prev) {
                prev.next = match;
        }

        prev = match;

        start = match.endOffset;
        parts.push(match);
}

// We only need to split to produce the number of parts we have.
splits.length = parts.length;

for (var i = 0; i < parts.length; i += 1) {
        if (parts[i] == null)
                continue;

        var offset = splits[0];

        while (children.length > 0) {
                if (children[0].endOffset <= parts[i].endOffset) {
                        parts[i].children.push(children.shift());
                } else {
                        break;
                }
        }

        if (children.length) {
                // We may have an intersection
                if (children[0].offset < parts[i].endOffset) {
                        var children_parts = children.shift().bisectAtOffsets(splits), j = 0;

                        // children_parts are the bisected children which need to be merged with parts
                        // in a linear fashion
                        for (; j < children_parts.length; j += 1) {
                                if (children_parts[j] == null) continue; // Preserve alignment with splits.

                                parts[i+j].children.push(children_parts[j]);
                        }

                        // Skip any parts which have been populated already
                        // (i is incremented at the start of the loop, splits shifted at the end)
                        i += (children_parts.length-2);
                        splits.splice(0, children_parts.length-2);
                }
        }

        splits.shift();
}

if (children.length) {
        console.log("Syntax Error: Children nodes not consumed", children.length, " remaining!");
}

return parts;

};

// Split a match at points in the tree that match a specific regular expression pattern. // Uses the fast tree bisection algorithm, performance should be bounded O(S log N) where N is // the total number of matches and S is the number of splits (?). Syntax.Match.prototype.split = function(pattern) {

var splits = [], match;

while ((match = pattern.exec(this.value)) !== null) {
        splits.push(pattern.lastIndex);
}

var matches = this.bisectAtOffsets(splits);

// Remove any null placeholders.
return jQuery.grep(matches, function(n,i){
        return n;
});

};

Syntax.Match.prototype.splitLines = function() {

var lines = this.split(/\n/g);

for (var i = 0; i < lines.length; i += 1) {
        var line = lines[i];
        var indentOffset = line.value.search(/\S/);

        var top = new Syntax.Match(line.offset, line.length, line.expression, line.value);

        if (indentOffset > 0) {
                var parts = line.bisectAtOffsets([line.offset + indentOffset]);
                top.children = parts;
                parts[0].expression = {klass: 'indent'};
                parts[1].expression = {klass: 'text'};
        } else {
                line.expression = {klass: 'text'};
                top.children = [line];
        }

        lines[i] = top;
}

return lines;

}

Syntax.Brush = function () {

// The primary class of this brush. Must be unique.
this.klass = null;

// A sequential list of rules for extracting matches.
this.rules = [];

// A list of all parents that this brush derives from.
this.parents = [];

// A list of processes that may be run after extracting matches.
this.processes = {};

};

// Add a parent to the brush. This brush should be loaded as a dependency. Syntax.Brush.prototype.derives = function (name) {

this.parents.push(name);
this.rules.push({
        apply: function(text, expr) {
                return Syntax.brushes[name].getMatches(text);
        }
});

};

// Return an array of all classes that the brush consists of. // A derivied brush is its own klass + the klass of any and all parents. Syntax.Brush.prototype.allKlasses = function () {

var klasses = [this.klass];

for (var i = 0; i < this.parents.length; i += 1) {
        klasses = klasses.concat(Syntax.brushes[this.parents[i]].allKlasses());
}

return klasses;

}

Syntax.Brush.convertStringToTokenPattern = function (pattern, escape) {

var prefix = "\\b", postfix = "\\b";

if (!pattern.match(/^\w/)) {
        if (!pattern.match(/\w$/)) {
                prefix = postfix = "";
        } else {
                prefix = "\\B";
        }
} else {
        if (!pattern.match(/\w$/)) {
                postfix = "\\B";
        }
}

if (escape)
        pattern = RegExp.escape(pattern)

return prefix + pattern + postfix;

}

Syntax.Brush.MatchPattern = function (text, rule) {

if (!rule.pattern)
        return [];

// Duplicate the pattern so that the function is reentrant.
var matches = [], pattern = new RegExp;
pattern.compile(rule.pattern);

while((match = pattern.exec(text)) !== null) {
        if (rule.matches) {
                matches = matches.concat(rule.matches(match, rule));
        } else if (rule.brush) {
                matches.push(Syntax.Brush.buildTree(rule, match[0], match.index));
        } else {
                matches.push(new Syntax.Match(match.index, match[0].length, rule, match[0]));
        }

        if (rule.incremental) {
                // Don't start scanning from the end of the match..
                pattern.lastIndex = match.index + 1;
        }
}

return matches;

}

Syntax.Brush.prototype.push = function () {

if (jQuery.isArray(arguments[0])) {
        var patterns = arguments[0], rule = arguments[1];

        var all = "(";

        for (var i = 0; i < patterns.length; i += 1) {
                if (i > 0) all += "|";

                var p = patterns[i];

                if (p instanceof RegExp) {
                        all += p.source;
                } else {
                        all += Syntax.Brush.convertStringToTokenPattern(p, true);
                }
        }

        all += ")";

        this.push(jQuery.extend({
                pattern: new RegExp(all, rule.options || 'g')
        }, rule));
} else {
        var rule = arguments[0];

        if (typeof(rule.pattern) === 'string') {
                rule.string = rule.pattern;
                rule.pattern = new RegExp(Syntax.Brush.convertStringToTokenPattern(rule.string, true), rule.options || 'g')
        }

        if (typeof(XRegExp) !== 'undefined') {
                rule.pattern = new XRegExp(rule.pattern);
        }

        // Default pattern extraction algorithm
        rule.apply = rule.apply || Syntax.Brush.MatchPattern;

        if (rule.pattern && rule.pattern.global || typeof(rule.pattern) == 'undefined') {
                this.rules.push(jQuery.extend({owner: this}, rule));
        } else {
                console.log("Syntax Error: Malformed rule: ", rule);
        }
}

};

Syntax.Brush.prototype.getMatchesForRule = function (text, rule) {

var matches = [], match = null;

// Short circuit (user defined) function:
if (typeof(rule.apply) != 'undefined') {
        matches = rule.apply(text, rule);
}

if (rule.debug) {
        console.log("Syntax matches:", rule, text, matches);
}

return matches;

};

Syntax.Brush.prototype.getRuleForKlass = function (klass) {

for (var i = 0; i < this.rules.length; i += 1) {
        if (this.rules[i].klass == klass) {
                return this.rules[i];
        }
}

return null;

}

// Get all matches from a given block of text. Syntax.Brush.prototype.getMatches = function(text) {

var matches = [];

for (var i = 0; i < this.rules.length; i += 1) {
        matches = matches.concat(this.getMatchesForRule(text, this.rules[i]));
}

return matches;

};

// A helper function for building a tree from a specific rule. // Typically used where sub-trees are required, e.g. CSS brush in HTML brush. Syntax.Brush.buildTree = function(rule, text, offset, additionalMatches) {

var match = Syntax.brushes[rule.brush].buildTree(text, offset, additionalMatches);

jQuery.extend(match.expression, rule);

return match;

}

// This function builds a tree from a given block of text. // This is done by applying all rules to the text to get a complete list of matches, // sorting them in order, and inserting them into a syntax tree data structure. // Additional matches are forcefully inserted into the tree. // Provide an offset if the text is offset in a larger block of text. Matches // will be shifted along appropriately. Syntax.Brush.prototype.buildTree = function(text, offset, additionalMatches) {

offset = offset || 0;

// Fixes code that uses \r\n for line endings. /$/ matches both \r\n, which is a problem..
text = text.replace(/\r/g, '');

var matches = this.getMatches(text);

// Shift matches if offset is provided.
if (offset && offset > 0) {
        for (var i = 0; i < matches.length; i += 1) {
                matches[i].shift(offset);
        }
}

var top = new Syntax.Match(offset, text.length, {klass: this.allKlasses().join(" "), allow: '*', owner: this}, text);

// This sort is absolutely key to the functioning of the tree insertion algorithm.
matches.sort(Syntax.Match.sort);

for (var i = 0; i < matches.length; i += 1) {
        top.insertAtEnd(matches[i]);
}

if (additionalMatches) {
        for (var i = 0; i < additionalMatches.length; i += 1) {
                top.insert(additionalMatches[i], true);
        }
}

top.complete = true;

return top;

};

// This function builds a syntax tree from the given text and matches (optional). // The syntax tree is then flattened into html using a variety of functions. // // By default, you can't control reduction process through this function, but // it is possible to control the element conversion process by replace // .reduce(null, …) with .reduce(reduceCallback, …) // See Syntax.Match.defaultReduceCallback for more details about interface. // // Matches is optional, and provides a set of pre-existing matches to add // to the tree. // Options are passed to element level processing functions. Syntax.Brush.prototype.process = function(text, matches, options) {

var top = this.buildTree(text, 0, matches);

var lines = top.splitLines();

var html = document.createElement('code');
html.className = 'syntax highlighted';

for (var i = 0; i < lines.length; i += 1) {
        var line = lines[i].reduce(null, function (container, match) {
                if (match.expression) {
                        if (match.expression.process) {
                                container = match.expression.process(container, match, options);
                        }

                        if (match.expression.owner) {
                                var process = match.expression.owner.processes[match.expression.klass];
                                if (process) {
                                        container = process(container, match, options);
                                }
                        }
                }
                return container;
        });

        html.appendChild(line);
}

return html;

};

// Highlights a given block of text with a given set of options. // options.brush should specify the brush to use, either by direct reference // or name. // Callback will be called with (highlighted_html, brush_used, original_text, options) Syntax.highlightText = function(text, brush, matches, options, callback) {

brush = (brush || 'plain').toLowerCase();
brush = Syntax.aliases[brush] || brush;

Syntax.brushes.get(brush, function(brush) {
        var html = brush.process(text, matches, options);

        if (options.linkify) {
                jQuery('span.href', html).each(function(){
                        jQuery(this).replaceWith(jQuery('<a>').attr('href', this.innerHTML).text(this.innerHTML));
                });
        }

        callback(html, brush, text, options);
});

}

Syntax.extractBrushName = function (className) {

// brush names are by default lower case - normalize so we can detect it.
className = className.toLowerCase();

var match = className.match(/(brush|language)-([\S]+)/);

if (match) {
        return match[2];
} else {
        var classes = className.split(/ /);

        if (jQuery.inArray("syntax", classes) !== -1) {
                for (var i = 0; i < classes.length; i += 1) {
                        var name = Syntax.aliases[classes[i]];

                        if (name) {
                                return name;
                        }
                }
        }
}

return null;

}

// Highlight a given set of elements with a set of options. // Callback will be called once per element with (options, highlighted_html, original_container) Syntax.highlight = function (elements, options, callback) {

elements.each(function () {
        var container = jQuery(this);
        var brush = options.brush || Syntax.extractBrushName(this.className);
        var text = Syntax.innerText(this);

        // We can augment the plain text to extract existing annotations (e.g. <span class="foo">...</span>).
        var matches = Syntax.extractElementMatches(container);

        if (options.matches) {
                Array.prototype.push(matches, options.matches);
        }

        Syntax.highlightText(text, brush, matches, options, function(html, brush/*, text, options*/) {
                html.oncopy = Syntax.copyCode;

                html = jQuery(html);

                // If there is a theme specified, ensure it is added to the top level class.
                if (options.theme) {
                        // Load dependencies
                        var themes = Syntax.themes[options.theme];
                        for (var i = 0; i < themes.length; i += 1) {
                                html.addClass("syntax-theme-" + themes[i]);
                        }

                        // Add the base theme
                        html.addClass("syntax-theme-" + options.theme);
                }

                if (brush.postprocess) {
                        html = brush.postprocess(options, html, container);
                }

                if (callback) {
                        html = callback(options, html, container);
                }

                if (html && options.replace === true) {
                        container.replaceWith(html);
                }
        });
});

};

// Register the file as being loaded Syntax.loader.core = true;