Revision control

Copy as Markdown

Other Tools

/* -*- 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);
}