/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* Constructs a new munger entry, using a regexp or lambda match function, and * a class name (to be applied by the munger itself) or lambda replace * function, and the default enabled state and a start priority (used if two * rules match at the same index), as well as a default tag (when the munger * adds it based on the class name) name. * * Regular Expressions for matching should ensure that the first capturing * group is the one that contains the matched text. Non-capturing groups, of * zero-width or otherwise can be used before and after, to ensure the right * things are matched (e.g. to ensure whitespace before something). * * Note that for RegExp matching, the munger will search for the matched text * (from the first capturing group) from the leftmost point of the entire * match. This means that if the text that matched the first group occurs in * any part of the match before the group, the munger will apply to the wrong * bit. This is not usually a problem, but if it is you should use a * lambdaMatch function and be sure to return the new style return value, * which specifically indicates the start. * * The lambda match and lambda replace functions have this signature: * lambdaMatch(text, containerTag, data, mungerEntry) * lambdaReplace(text, containerTag, data, mungerEntry) * - text is the entire text to find a match in/that has matched * - containerTag is the element containing the text (not useful?) * - data is a generic object containing properties kept throughout * - mungerEntry is the CMungerEntry object for the munger itself * * The lambdaReplace function is expected to do everything needed to put * |text| into |containerTab| ready for display. * * The return value for lambda match functions should be either: * - (old style) just the text that matched * (the munger will search for this text, and uses the first match) * - (new style) an object with properties: * - start (start index, 0 = first character) * - text (matched text) * (note that |text| must start at index |start|) * * The return value for lambda replace functions are not used. * */ function CMungerEntry(name, regex, className, priority, startPriority, enable, tagName) { this.name = name; if (name[0] != ".") this.description = getMsg("munger." + name, null, null); this.enabled = (typeof enable == "undefined" ? true : enable); this.enabledDefault = this.enabled; this.startPriority = (startPriority) ? startPriority : 0; this.priority = priority; this.tagName = (tagName) ? tagName : "html:span"; if (isinstance(regex, RegExp)) this.regex = regex; else this.lambdaMatch = regex; if (typeof className == "function") this.lambdaReplace = className; else this.className = className; } function CMunger(textMunger) { this.entries = new Array(); this.tagName = "html:span"; this.enabled = true; if (textMunger) this.insertPlainText = textMunger; } CMunger.prototype.enabled = true; CMunger.prototype.insertPlainText = insertText; CMunger.prototype.getRule = function mng_getrule(name) { for (var p in this.entries) { if (isinstance(this.entries[p], Object)) { if (name in this.entries[p]) return this.entries[p][name]; } } return null; } CMunger.prototype.addRule = function mng_addrule(name, regex, className, priority, startPriority, enable) { if (typeof this.entries[priority] != "object") this.entries[priority] = new Object(); var entry = new CMungerEntry(name, regex, className, priority, startPriority, enable); this.entries[priority][name] = entry; } CMunger.prototype.delRule = function mng_delrule(name) { for (var i in this.entries) { if (typeof this.entries[i] == "object") { if (name in this.entries[i]) delete this.entries[i][name]; } } } CMunger.prototype.munge = function mng_munge(text, containerTag, data) { if (!containerTag) containerTag = document.createElementNS(XHTML_NS, this.tagName); // Starting from the top, for each valid priority, check all the rules, // return as soon as something matches. if (this.enabled) { for (var i = this.entries.length - 1; i >= 0; i--) { if (i in this.entries) { if (this.mungePriority(i, text, containerTag, data)) return containerTag; } } } // If nothing matched, we don't have to do anything, // just insert text (if any). if (text) this.insertPlainText(text, containerTag, data); return containerTag; } CMunger.prototype.mungePriority = function mng_mungePriority(priority, text, containerTag, data) { var matches = new Object(); var entry; // Find all the matches in this priority for (entry in this.entries[priority]) { var munger = this.entries[priority][entry]; if (!munger.enabled) continue; var match = null; if (typeof munger.lambdaMatch == "function") { var rval = munger.lambdaMatch(text, containerTag, data, munger); if (typeof rval == "string") match = { start: text.indexOf(rval), text: rval }; else if (typeof rval == "object") match = rval; } else { var ary = text.match(munger.regex); if ((ary != null) && (ary[1])) match = { start: text.indexOf(ary[1]), text: ary[1] }; } if (match && (match.start >= 0)) { match.munger = munger; matches[entry] = match; } } // Find the first matching entry... var firstMatch = { start: text.length, munger: null }; var firstPriority = 0; for (entry in matches) { // If it matches before the existing first, or at the same spot but // with a higher start-priority, this is a better match. if (matches[entry].start < firstMatch.start || ((matches[entry].start == firstMatch.start) && (this.entries[priority][entry].startPriority > firstPriority))) { firstMatch = matches[entry]; firstPriority = this.entries[priority][entry].startPriority; } } // Replace it. if (firstMatch.munger) { var munger = firstMatch.munger; firstMatch.end = firstMatch.start + firstMatch.text.length; // Need to deal with the text before the match, if there is any. var beforeText = text.substr(0, firstMatch.start); if (firstMatch.start > 0) this.munge(beforeText, containerTag, data); if (typeof munger.lambdaReplace == "function") { // The munger rule itself should take care of munging the 'inside' // of the match. munger.lambdaReplace(firstMatch.text, containerTag, data, munger); this.munge(text.substr(firstMatch.end), containerTag, data); return containerTag; } else { var tag = document.createElementNS(XHTML_NS, munger.tagName); tag.setAttribute("class", munger.className + calcClass(data)); // Don't let this rule match again when we recurse. munger.enabled = false; this.munge(firstMatch.text, tag, data); munger.enabled = true; containerTag.appendChild(tag); this.munge(text.substr(firstMatch.end), containerTag, data); return containerTag; } } return null; } function insertText(text, containerTag, data) { var textNode = document.createTextNode(text); containerTag.appendChild(textNode); }