in src/main/java/org/apache/commons/codec/language/Metaphone.java [87:307]
public String metaphone(final String txt) {
boolean hard = false;
final int txtLength;
if (txt == null || (txtLength = txt.length()) == 0) {
return "";
}
// single character is itself
if (txtLength == 1) {
return txt.toUpperCase(java.util.Locale.ENGLISH);
}
final char[] inwd = txt.toUpperCase(java.util.Locale.ENGLISH).toCharArray();
final StringBuilder local = new StringBuilder(40); // manipulate
final StringBuilder code = new StringBuilder(10); // output
// handle initial 2 characters exceptions
switch (inwd[0]) {
case 'K':
case 'G':
case 'P': /* looking for KN, etc */
if (inwd[1] == 'N') {
local.append(inwd, 1, inwd.length - 1);
} else {
local.append(inwd);
}
break;
case 'A': /* looking for AE */
if (inwd[1] == 'E') {
local.append(inwd, 1, inwd.length - 1);
} else {
local.append(inwd);
}
break;
case 'W': /* looking for WR or WH */
if (inwd[1] == 'R') { // WR -> R
local.append(inwd, 1, inwd.length - 1);
break;
}
if (inwd[1] == 'H') {
local.append(inwd, 1, inwd.length - 1);
local.setCharAt(0, 'W'); // WH -> W
} else {
local.append(inwd);
}
break;
case 'X': /* initial X becomes S */
inwd[0] = 'S';
local.append(inwd);
break;
default:
local.append(inwd);
} // now local has working string with initials fixed
final int wdsz = local.length();
int n = 0;
while (code.length() < this.getMaxCodeLen() && n < wdsz) { // max code size of 4 works well
final char symb = local.charAt(n);
// remove duplicate letters except C
if (symb != 'C' && isPreviousChar(local, n, symb)) {
n++;
} else { // not dup
switch (symb) {
case 'A':
case 'E':
case 'I':
case 'O':
case 'U':
if (n == 0) {
code.append(symb);
}
break; // only use vowel if leading char
case 'B':
if (isPreviousChar(local, n, 'M') && isLastChar(wdsz, n)) { // B is silent if word ends in MB
break;
}
code.append(symb);
break;
case 'C': // lots of C special cases
/* discard if SCI, SCE or SCY */
if (isPreviousChar(local, n, 'S') && !isLastChar(wdsz, n) && FRONTV.indexOf(local.charAt(n + 1)) >= 0) {
break;
}
if (regionMatch(local, n, "CIA")) { // "CIA" -> X
code.append('X');
break;
}
if (!isLastChar(wdsz, n) && FRONTV.indexOf(local.charAt(n + 1)) >= 0) {
code.append('S');
break; // CI,CE,CY -> S
}
if (isPreviousChar(local, n, 'S') && isNextChar(local, n, 'H')) { // SCH->sk
code.append('K');
break;
}
if (isNextChar(local, n, 'H')) { // detect CH
if (n == 0 && wdsz >= 3 && isVowel(local, 2)) { // CH consonant -> K consonant
code.append('K');
} else {
code.append('X'); // CHvowel -> X
}
} else {
code.append('K');
}
break;
case 'D':
if (!isLastChar(wdsz, n + 1) && isNextChar(local, n, 'G') && FRONTV.indexOf(local.charAt(n + 2)) >= 0) { // DGE DGI DGY -> J
code.append('J');
n += 2;
} else {
code.append('T');
}
break;
case 'G': // GH silent at end or before consonant
if (isLastChar(wdsz, n + 1) && isNextChar(local, n, 'H')) {
break;
}
if (!isLastChar(wdsz, n + 1) && isNextChar(local, n, 'H') && !isVowel(local, n + 2)) {
break;
}
if (n > 0 && (regionMatch(local, n, "GN") || regionMatch(local, n, "GNED"))) {
break; // silent G
}
// NOTE: Given that duplicated chars are removed, I don't see how this can ever be true
hard = isPreviousChar(local, n, 'G');
if (!isLastChar(wdsz, n) && FRONTV.indexOf(local.charAt(n + 1)) >= 0 && !hard) {
code.append('J');
} else {
code.append('K');
}
break;
case 'H':
if (isLastChar(wdsz, n)) {
break; // terminal H
}
if (n > 0 && VARSON.indexOf(local.charAt(n - 1)) >= 0) {
break;
}
if (isVowel(local, n + 1)) {
code.append('H'); // Hvowel
}
break;
case 'F':
case 'J':
case 'L':
case 'M':
case 'N':
case 'R':
code.append(symb);
break;
case 'K':
if (n > 0) { // not initial
if (!isPreviousChar(local, n, 'C')) {
code.append(symb);
}
} else {
code.append(symb); // initial K
}
break;
case 'P':
if (isNextChar(local, n, 'H')) {
// PH -> F
code.append('F');
} else {
code.append(symb);
}
break;
case 'Q':
code.append('K');
break;
case 'S':
if (regionMatch(local, n, "SH") || regionMatch(local, n, "SIO") || regionMatch(local, n, "SIA")) {
code.append('X');
} else {
code.append('S');
}
break;
case 'T':
if (regionMatch(local, n, "TIA") || regionMatch(local, n, "TIO")) {
code.append('X');
break;
}
if (regionMatch(local, n, "TCH")) {
// Silent if in "TCH"
break;
}
// substitute numeral 0 for TH (resembles theta after all)
if (regionMatch(local, n, "TH")) {
code.append('0');
} else {
code.append('T');
}
break;
case 'V':
code.append('F');
break;
case 'W':
case 'Y': // silent if not followed by vowel
if (!isLastChar(wdsz, n) && isVowel(local, n + 1)) {
code.append(symb);
}
break;
case 'X':
code.append('K');
code.append('S');
break;
case 'Z':
code.append('S');
break;
default:
// do nothing
break;
} // end switch
n++;
} // end else from symb != 'C'
if (code.length() > this.getMaxCodeLen()) {
code.setLength(this.getMaxCodeLen());
}
}
return code.toString();
}