From f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aab Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Fri, 9 Sep 2011 20:54:43 +0900 Subject: [PATCH] Have the spell checker honor case Bug: 5281103 Change-Id: I415c84dbb55f1eeb5deb9f248b4056881982ee13 --- .../com/android/inputmethod/latin/Utils.java | 11 ++- .../AndroidSpellCheckerService.java | 68 +++++++++++++------ 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index 6263ebefa..c35273edd 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -757,10 +757,19 @@ public class Utils { return toTitleCase(locale.getLanguage(), locale); } - private static String toTitleCase(String s, Locale locale) { + public static String toTitleCase(String s, Locale locale) { if (s.length() <= 1) { + // TODO: is this really correct? Shouldn't this be s.toUpperCase()? return s; } + // TODO: fix the bugs below + // - This does not work for Greek, because it returns upper case instead of title case. + // - It does not work for Serbian, because it fails to account for the "lj" character, + // which should be "Lj" in title case and "LJ" in upper case. + // - It does not work for Dutch, because it fails to account for the "ij" digraph, which + // are two different characters but both should be capitalized as "IJ" as if they were + // a single letter. + // - It also does not work with unicode surrogate code points. return s.toUpperCase(locale).charAt(0) + s.substring(1); } } diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index ae47ab22b..3cf8788aa 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -55,6 +55,10 @@ public class AndroidSpellCheckerService extends SpellCheckerService { private static final boolean DBG = false; private static final int POOL_SIZE = 2; + private static final int CAPITALIZE_NONE = 0; // No caps, or mixed case + private static final int CAPITALIZE_FIRST = 1; // First only + private static final int CAPITALIZE_ALL = 2; // All caps + private final static String[] EMPTY_STRING_ARRAY = new String[0]; private final static SuggestionsInfo EMPTY_SUGGESTIONS_INFO = new SuggestionsInfo(0, EMPTY_STRING_ARRAY); @@ -139,7 +143,8 @@ public class AndroidSpellCheckerService extends SpellCheckerService { return true; } - public Result getResults(final CharSequence originalText, final double threshold) { + public Result getResults(final CharSequence originalText, final double threshold, + final int capitalizeType, final Locale locale) { final String[] gatheredSuggestions; final boolean looksLikeTypo; if (0 == mLength) { @@ -166,6 +171,19 @@ public class AndroidSpellCheckerService extends SpellCheckerService { } Collections.reverse(mSuggestions); Utils.removeDupes(mSuggestions); + if (CAPITALIZE_ALL == capitalizeType) { + for (int i = 0; i < mSuggestions.size(); ++i) { + // get(i) returns a CharSequence which is actually a String so .toString() + // should return the same object. + mSuggestions.set(i, mSuggestions.get(i).toString().toUpperCase(locale)); + } + } else if (CAPITALIZE_FIRST == capitalizeType) { + for (int i = 0; i < mSuggestions.size(); ++i) { + // Likewise + mSuggestions.set(i, Utils.toTitleCase(mSuggestions.get(i).toString(), + locale)); + } + } // This returns a String[], while toArray() returns an Object[] which cannot be cast // into a String[]. gatheredSuggestions = mSuggestions.toArray(EMPTY_STRING_ARRAY); @@ -226,6 +244,25 @@ public class AndroidSpellCheckerService extends SpellCheckerService { return new DictAndProximity(dictionaryCollection, proximityInfo); } + // This method assumes the text is not empty or null. + private static int getCapitalizationType(String text) { + // If the first char is not uppercase, then the word is either all lower case, + // and in either case we return CAPITALIZE_NONE. + if (!Character.isUpperCase(text.codePointAt(0))) return CAPITALIZE_NONE; + final int len = text.codePointCount(0, text.length()); + int capsCount = 1; + for (int i = 1; i < len; ++i) { + if (1 != capsCount && i != capsCount) break; + if (Character.isUpperCase(text.codePointAt(i))) ++capsCount; + } + // We know the first char is upper case. So we want to test if either everything + // else is lower case, or if everything else is upper case. If the string is + // exactly one char long, then we will arrive here with capsCount 1, and this is + // correct, too. + if (1 == capsCount) return CAPITALIZE_FIRST; + return (len == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE); + } + private static class AndroidSpellCheckerSession extends Session { // Immutable, but need the locale which is not available in the constructor yet private DictionaryPool mDictionaryPool; @@ -276,31 +313,18 @@ public class AndroidSpellCheckerService extends SpellCheckerService { WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE); } + final int capitalizeType = getCapitalizationType(text); boolean isInDict = true; try { final DictAndProximity dictInfo = mDictionaryPool.take(); dictInfo.mDictionary.getWords(composer, suggestionsGatherer, dictInfo.mProximityInfo); isInDict = dictInfo.mDictionary.isValidWord(text); - if (!isInDict && Character.isUpperCase(text.codePointAt(0))) { - // If the first char is not uppercase, then the word is either all lower case, - // in which case we already tested it, or mixed case, in which case we don't - // want to test a lower-case version of it. Hence the test above. - // Also note that by isEmpty() test at the top of the method codePointAt(0) is - // guaranteed to be there. - final int len = text.codePointCount(0, text.length()); - int capsCount = 1; - for (int i = 1; i < len; ++i) { - if (1 != capsCount && i != capsCount) break; - if (Character.isUpperCase(text.codePointAt(i))) ++capsCount; - } - // We know the first char is upper case. So we want to test if either everything - // else is lower case, or if everything else is upper case. If the string is - // exactly one char long, then we will arrive here with capsCount 0, and this is - // correct, too. - if (1 == capsCount || len == capsCount) { - isInDict = dictInfo.mDictionary.isValidWord(text.toLowerCase(mLocale)); - } + if (!isInDict && CAPITALIZE_NONE != capitalizeType) { + // We want to test the word again if it's all caps or first caps only. + // If it's fully down, we already tested it, if it's mixed case, we don't + // want to test a lowercase version of it. + isInDict = dictInfo.mDictionary.isValidWord(text.toLowerCase(mLocale)); } if (!mDictionaryPool.offer(dictInfo)) { Log.e(TAG, "Can't re-insert a dictionary into its pool"); @@ -310,8 +334,8 @@ public class AndroidSpellCheckerService extends SpellCheckerService { return EMPTY_SUGGESTIONS_INFO; } - final SuggestionsGatherer.Result result = - suggestionsGatherer.getResults(text, mService.mTypoThreshold); + final SuggestionsGatherer.Result result = suggestionsGatherer.getResults(text, + mService.mTypoThreshold, capitalizeType, mLocale); if (DBG) { Log.i(TAG, "Spell checking results for " + text + " with suggestion limit "