Converted the base CurseFilter to actionscript.

git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4474 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Ray Greenwell
2006-12-07 03:43:18 +00:00
parent 509f61c83e
commit 0ffa0211bc
2 changed files with 238 additions and 0 deletions
@@ -0,0 +1,218 @@
//
// $Id$
package com.threerings.crowd.chat.client {
import com.threerings.util.Name;
import com.threerings.util.StringUtil;
/**
* A chat filter that can filter out curse words from user chat.
*/
public /*abstract*/ class CurseFilter implements ChatFilter
{
/** Filtration constants. */
public static const DROP :int = 0;
public static const COMIC :int = 1;
public static const VERNACULAR :int = 2;
public static const UNFILTERED :int = 3;
/**
* Creates a curse filter. The curse words should be a string in the
* following format:
*
* <pre>
* *penis*=John_Thomas shit*=barnacle muff=britches
* </pre>
*
* The key/value pairs are separated by spaces, * matches word characters
* and the value after the = is the string into which to convert the text
* when converting to the vernacular. Underscores in the target string will
* be turned into spaces.
*
* <p> And stopWords should be in the following format:
*
* <pre>
* *faggot* rape rapes raped raping
* </pre>
*
* Words are separated by spaces and * matches any other word characters.
*/
public function CurseFilter (curseWords :String, stopWords :String)
{
configureCurseWords(curseWords);
configureStopWords(stopWords);
}
/**
* The client will need to provide a way to look up our current chat filter
* mode.
*/
public function getFilterMode () :int
{
throw new Error("abstract");
}
// from interface ChatFilter
public function filter (
msg :String, otherUser :Name, outgoing :Boolean) :String
{
// first, check against the drop-always list
if (_stopExp.test(msg)) {
return null;
}
// then see what kind of curse filtering the user has configured
var level :int = getFilterMode();
if (level == UNFILTERED) {
return msg;
}
var numStops :int = _curseExps.length;
for (var ii :int = 0; ii < numStops; ii++) {
var regexp :RegExp = (_curseExps[ii] as RegExp);
regexp.lastIndex = 0; // reset the matcher
var result :Object;
var replacement :String;
while (null != (result = regexp.exec(msg))) {
switch (level) {
case DROP:
return null;
case COMIC: default:
replacement = String(_replacements[ii]).replace(
/\ /g, comicChars(int(_comicLength[ii])));
break;
case VERNACULAR:
var vern :String = String(_vernacular[ii]);
var firstChar :String = String(result[2]).charAt(0);
if (StringUtil.isUpperCase(String(result[2]))) {
vern = vern.charAt(0).toUpperCase() + vern.substring(1);
}
replacement = String(_replacements[ii]).replace(
/\ /g, vern);
break;
}
replacement = replacement.replace("$1", String(result[1]));
replacement = replacement.replace("$3", String(result[3]));
// do the replacement. Note the jimmying of lastIndex:
// abjectscript does not make this smooth!
var matchLength :int = String(result[0]).length;
var matchIndex :int = int(result.index);
msg = msg.substring(0, matchIndex) + replacement +
msg.substring(matchIndex + matchLength);
regexp.lastIndex += (replacement.length - matchLength);
}
}
return msg;
}
/**
* Configure the curse word portion of our filtering.
*/
protected function configureCurseWords (curseWords :String) :void
{
var tokens :Array = curseWords.split(" ");
for each (var token :String in tokens) {
token = StringUtil.trim(token);
if (token == "") {
continue;
}
var bits :Array = token.split("=");
if (bits.length != 2) {
Log.getLog(this).warning("Something looks wrong with " +
"your cursewords (" + token + "), skipping.");
continue;
}
var curse :String = String(bits[0]);
var s :String = "";
var p :String = "";
if (curse.charAt(0) == "*") {
curse = curse.substring(1);
p += "([a-zA-Z]*)";
s += "$1";
} else {
p += "()";
}
s += " ";
p += " ";
if (curse.charAt(curse.length - 1) == "*") {
curse = curse.substring(0, curse.length - 1);
p += "([a-zA-Z]*)";
s += "$3";
}
var regexp :RegExp = new RegExp(
"\\b" + p.replace(" ", "(" + curse + ")") + "\\b", "gi");
_curseExps.push(regexp);
_replacements.push(s);
var slang :String = StringUtil.trim(String(bits[1]));
slang = slang.replace(/_/g, " ");
_vernacular.push(slang);
_comicLength.push(curse.length);
}
}
/**
* Configure the words that will stop.
*/
protected function configureStopWords (stopWords :String) :void
{
var tokens :Array = stopWords.split(" ");
var pattern :String = "";
for each (var token :String in tokens) {
token = StringUtil.trim(token);
if (token == "") {
continue;
}
if ("" == pattern) {
pattern += ".*(";
} else {
pattern += "|";
}
pattern += "\\b" +
token.replace(/\*/g, "[A-Za-z]*") + "\\b";
}
pattern += ").*";
_stopExp = new RegExp(pattern, "i");
}
/**
* Return a comicy replacement of the specified length;
*/
protected function comicChars (length :int) :String
{
var str :String = "";
while (length-- > 0) {
str += COMIC_CHARS.charAt(int(Math.random() * COMIC_CHARS.length));
}
return str;
}
/** A regexp that will always cause a message to be dropped if it
* matches. */
protected var _stopExp :RegExp;
/** Regexps for each curseword. */
protected var _curseExps :Array = [];
/** Length of comic-y replacements for each curseword. */
protected var _comicLength :Array = [];
/** Replacements. */
protected var _replacements :Array = [];
/** Replacements for each curseword "in the vernacular". */
protected var _vernacular :Array = [];
/** Comic replacement characters. */
protected const COMIC_CHARS :String = "!@#%&*";
}
}
+20
View File
@@ -45,6 +45,26 @@ public class StringUtil
return (str.lastIndexOf(substr, 0) == 0);
}
/**
* Return true iff the first character is a lower-case character.
*/
public static function isLowerCase (str :String) :Boolean
{
var firstChar :String = str.charAt(0);
return (firstChar.toUpperCase() != firstChar) &&
(firstChar.toLowerCase() == firstChar);
}
/**
* Return true iff the first character is an upper-case character.
*/
public static function isUpperCase (str :String) :Boolean
{
var firstChar :String = str.charAt(0);
return (firstChar.toUpperCase() == firstChar) &&
(firstChar.toLowerCase() != firstChar);
}
/**
* Append 0 or more copies of the padChar String to the input String
* until it is at least the specified length.