aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin/StringUtils.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/latin/StringUtils.java')
-rw-r--r--java/src/com/android/inputmethod/latin/StringUtils.java233
1 files changed, 214 insertions, 19 deletions
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index a43b90525..df7709892 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -18,10 +18,12 @@ package com.android.inputmethod.latin;
import android.text.TextUtils;
+import com.android.inputmethod.keyboard.Keyboard; // For character constants
+
import java.util.ArrayList;
import java.util.Locale;
-public class StringUtils {
+public final class StringUtils {
private StringUtils() {
// This utility class is not publicly instantiable.
}
@@ -53,7 +55,7 @@ public class StringUtils {
if (TextUtils.isEmpty(csv)) return "";
final String[] elements = csv.split(",");
if (!containsInArray(key, elements)) return csv;
- final ArrayList<String> result = new ArrayList<String>(elements.length - 1);
+ final ArrayList<String> result = CollectionUtils.newArrayList(elements.length - 1);
for (final String element : elements) {
if (!key.equals(element)) result.add(element);
}
@@ -123,23 +125,6 @@ public class StringUtils {
}
/**
- * Returns true if cs contains any upper case characters.
- *
- * @param cs the CharSequence to check
- * @return {@code true} if cs contains any upper case characters, {@code false} otherwise.
- */
- public static boolean hasUpperCase(final CharSequence cs) {
- final int length = cs.length();
- for (int i = 0, cp = 0; i < length; i += Character.charCount(cp)) {
- cp = Character.codePointAt(cs, i);
- if (Character.isUpperCase(cp)) {
- return true;
- }
- }
- return false;
- }
-
- /**
* Remove duplicates from an array of strings.
*
* This method will always keep the first occurrence of all strings at their position
@@ -184,6 +169,9 @@ public class StringUtils {
final char[] characters = string.toCharArray();
final int length = characters.length;
final int[] codePoints = new int[Character.codePointCount(characters, 0, length)];
+ if (length <= 0) {
+ return new int[0];
+ }
int codePoint = Character.codePointAt(characters, 0);
int dsti = 0;
for (int srci = Character.charCount(codePoint);
@@ -194,4 +182,211 @@ public class StringUtils {
codePoints[dsti] = codePoint;
return codePoints;
}
+
+ /**
+ * Determine what caps mode should be in effect at the current offset in
+ * the text. Only the mode bits set in <var>reqModes</var> will be
+ * checked. Note that the caps mode flags here are explicitly defined
+ * to match those in {@link InputType}.
+ *
+ * This code is a straight copy of TextUtils.getCapsMode (modulo namespace and formatting
+ * issues). This will change in the future as we simplify the code for our use and fix bugs.
+ *
+ * @param cs The text that should be checked for caps modes.
+ * @param reqModes The modes to be checked: may be any combination of
+ * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
+ * {@link TextUtils#CAP_MODE_SENTENCES}.
+ * @param locale The locale to consider for capitalization rules
+ * @param hasSpaceBefore Whether we should consider there is a space inserted at the end of cs
+ *
+ * @return Returns the actual capitalization modes that can be in effect
+ * at the current position, which is any combination of
+ * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
+ * {@link TextUtils#CAP_MODE_SENTENCES}.
+ */
+ public static int getCapsMode(final CharSequence cs, final int reqModes, final Locale locale,
+ final boolean hasSpaceBefore) {
+ // Quick description of what we want to do:
+ // CAP_MODE_CHARACTERS is always on.
+ // CAP_MODE_WORDS is on if there is some whitespace before the cursor.
+ // CAP_MODE_SENTENCES is on if there is some whitespace before the cursor, and the end
+ // of a sentence just before that.
+ // We ignore opening parentheses and the like just before the cursor for purposes of
+ // finding whitespace for WORDS and SENTENCES modes.
+ // The end of a sentence ends with a period, question mark or exclamation mark. If it's
+ // a period, it also needs not to be an abbreviation, which means it also needs to either
+ // be immediately preceded by punctuation, or by a string of only letters with single
+ // periods interleaved.
+
+ // Step 1 : check for cap MODE_CHARACTERS. If it's looked for, it's always on.
+ if ((reqModes & (TextUtils.CAP_MODE_WORDS | TextUtils.CAP_MODE_SENTENCES)) == 0) {
+ // Here we are not looking for MODE_WORDS or MODE_SENTENCES, so since we already
+ // evaluated MODE_CHARACTERS, we can return.
+ return TextUtils.CAP_MODE_CHARACTERS & reqModes;
+ }
+
+ // Step 2 : Skip (ignore at the end of input) any opening punctuation. This includes
+ // opening parentheses, brackets, opening quotes, everything that *opens* a span of
+ // text in the linguistic sense. In RTL languages, this is still an opening sign, although
+ // it may look like a right parenthesis for example. We also include double quote and
+ // single quote since they aren't start punctuation in the unicode sense, but should still
+ // be skipped for English. TODO: does this depend on the language?
+ int i;
+ if (hasSpaceBefore) {
+ i = cs.length() + 1;
+ } else {
+ for (i = cs.length(); i > 0; i--) {
+ final char c = cs.charAt(i - 1);
+ if (c != Keyboard.CODE_DOUBLE_QUOTE && c != Keyboard.CODE_SINGLE_QUOTE
+ && Character.getType(c) != Character.START_PUNCTUATION) {
+ break;
+ }
+ }
+ }
+
+ // We are now on the character that precedes any starting punctuation, so in the most
+ // frequent case this will be whitespace or a letter, although it may occasionally be a
+ // start of line, or some symbol.
+
+ // Step 3 : Search for the start of a paragraph. From the starting point computed in step 2,
+ // we go back over any space or tab char sitting there. We find the start of a paragraph
+ // if the first char that's not a space or tab is a start of line (as in \n, start of text,
+ // or some other similar characters).
+ int j = i;
+ char prevChar = Keyboard.CODE_SPACE;
+ if (hasSpaceBefore) --j;
+ while (j > 0) {
+ prevChar = cs.charAt(j - 1);
+ if (!Character.isSpaceChar(prevChar) && prevChar != Keyboard.CODE_TAB) break;
+ j--;
+ }
+ if (j <= 0 || Character.isWhitespace(prevChar)) {
+ // There are only spacing chars between the start of the paragraph and the cursor,
+ // defined as a isWhitespace() char that is neither a isSpaceChar() nor a tab. Both
+ // MODE_WORDS and MODE_SENTENCES should be active.
+ return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS
+ | TextUtils.CAP_MODE_SENTENCES) & reqModes;
+ }
+ if (i == j) {
+ // If we don't have whitespace before index i, it means neither MODE_WORDS
+ // nor mode sentences should be on so we can return right away.
+ return TextUtils.CAP_MODE_CHARACTERS & reqModes;
+ }
+ if ((reqModes & TextUtils.CAP_MODE_SENTENCES) == 0) {
+ // Here we know we have whitespace before the cursor (if not, we returned in the above
+ // if i == j clause), so we need MODE_WORDS to be on. And we don't need to evaluate
+ // MODE_SENTENCES so we can return right away.
+ return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
+ }
+ // Please note that because of the reqModes & CAP_MODE_SENTENCES test a few lines above,
+ // we know that MODE_SENTENCES is being requested.
+
+ // Step 4 : Search for MODE_SENTENCES.
+ // English is a special case in that "American typography" rules, which are the most common
+ // in English, state that a sentence terminator immediately following a quotation mark
+ // should be swapped with it and de-duplicated (included in the quotation mark),
+ // e.g. <<Did he say, "let's go home?">>
+ // No other language has such a rule as far as I know, instead putting inside the quotation
+ // mark as the exact thing quoted and handling the surrounding punctuation independently,
+ // e.g. <<Did he say, "let's go home"?>>
+ // Hence, specifically for English, we treat this special case here.
+ if (Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) {
+ for (; j > 0; j--) {
+ // Here we look to go over any closing punctuation. This is because in dominant
+ // variants of English, the final period is placed within double quotes and maybe
+ // other closing punctuation signs. This is generally not true in other languages.
+ final char c = cs.charAt(j - 1);
+ if (c != Keyboard.CODE_DOUBLE_QUOTE && c != Keyboard.CODE_SINGLE_QUOTE
+ && Character.getType(c) != Character.END_PUNCTUATION) {
+ break;
+ }
+ }
+ }
+
+ if (j <= 0) return TextUtils.CAP_MODE_CHARACTERS & reqModes;
+ char c = cs.charAt(--j);
+
+ // We found the next interesting chunk of text ; next we need to determine if it's the
+ // end of a sentence. If we have a question mark or an exclamation mark, it's the end of
+ // a sentence. If it's neither, the only remaining case is the period so we get the opposite
+ // case out of the way.
+ if (c == Keyboard.CODE_QUESTION_MARK || c == Keyboard.CODE_EXCLAMATION_MARK) {
+ return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_SENTENCES) & reqModes;
+ }
+ if (c != Keyboard.CODE_PERIOD || j <= 0) {
+ return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
+ }
+
+ // We found out that we have a period. We need to determine if this is a full stop or
+ // otherwise sentence-ending period, or an abbreviation like "e.g.". An abbreviation
+ // looks like (\w\.){2,}
+ // To find out, we will have a simple state machine with the following states :
+ // START, WORD, PERIOD, ABBREVIATION
+ // On START : (just before the first period)
+ // letter => WORD
+ // whitespace => end with no caps (it was a stand-alone period)
+ // otherwise => end with caps (several periods/symbols in a row)
+ // On WORD : (within the word just before the first period)
+ // letter => WORD
+ // period => PERIOD
+ // otherwise => end with caps (it was a word with a full stop at the end)
+ // On PERIOD : (period within a potential abbreviation)
+ // letter => LETTER
+ // otherwise => end with caps (it was not an abbreviation)
+ // On LETTER : (letter within a potential abbreviation)
+ // letter => LETTER
+ // period => PERIOD
+ // otherwise => end with no caps (it was an abbreviation)
+ // "Not an abbreviation" in the above chart essentially covers cases like "...yes.". This
+ // should capitalize.
+
+ final int START = 0;
+ final int WORD = 1;
+ final int PERIOD = 2;
+ final int LETTER = 3;
+ final int caps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS
+ | TextUtils.CAP_MODE_SENTENCES) & reqModes;
+ final int noCaps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
+ int state = START;
+ while (j > 0) {
+ c = cs.charAt(--j);
+ switch (state) {
+ case START:
+ if (Character.isLetter(c)) {
+ state = WORD;
+ } else if (Character.isWhitespace(c)) {
+ return noCaps;
+ } else {
+ return caps;
+ }
+ break;
+ case WORD:
+ if (Character.isLetter(c)) {
+ state = WORD;
+ } else if (c == Keyboard.CODE_PERIOD) {
+ state = PERIOD;
+ } else {
+ return caps;
+ }
+ break;
+ case PERIOD:
+ if (Character.isLetter(c)) {
+ state = LETTER;
+ } else {
+ return caps;
+ }
+ break;
+ case LETTER:
+ if (Character.isLetter(c)) {
+ state = LETTER;
+ } else if (c == Keyboard.CODE_PERIOD) {
+ state = PERIOD;
+ } else {
+ return noCaps;
+ }
+ }
+ }
+ // Here we arrived at the start of the line. This should behave exactly like whitespace.
+ return (START == state || LETTER == state) ? noCaps : caps;
+ }
}