2012-03-08 08:07:02 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2012 The Android Open Source Project
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package com.android.inputmethod.latin;
|
|
|
|
|
|
|
|
import android.text.TextUtils;
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
2012-04-04 05:30:42 +00:00
|
|
|
import java.util.Locale;
|
2012-03-08 08:07:02 +00:00
|
|
|
|
2012-08-29 08:26:00 +00:00
|
|
|
public final class StringUtils {
|
2013-04-04 12:14:37 +00:00
|
|
|
public static final int CAPITALIZE_NONE = 0; // No caps, or mixed case
|
|
|
|
public static final int CAPITALIZE_FIRST = 1; // First only
|
|
|
|
public static final int CAPITALIZE_ALL = 2; // All caps
|
|
|
|
|
2012-03-08 08:07:02 +00:00
|
|
|
private StringUtils() {
|
|
|
|
// This utility class is not publicly instantiable.
|
|
|
|
}
|
|
|
|
|
2012-10-03 06:19:43 +00:00
|
|
|
public static int codePointCount(final String text) {
|
2012-03-08 08:07:02 +00:00
|
|
|
if (TextUtils.isEmpty(text)) return 0;
|
|
|
|
return text.codePointCount(0, text.length());
|
|
|
|
}
|
|
|
|
|
2012-10-03 06:19:43 +00:00
|
|
|
public static boolean containsInArray(final String key, final String[] array) {
|
2012-04-19 07:39:25 +00:00
|
|
|
for (final String element : array) {
|
|
|
|
if (key.equals(element)) return true;
|
2012-03-08 08:07:02 +00:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-10-03 06:19:43 +00:00
|
|
|
public static boolean containsInCsv(final String key, final String csv) {
|
2012-04-19 07:39:25 +00:00
|
|
|
if (TextUtils.isEmpty(csv)) return false;
|
|
|
|
return containsInArray(key, csv.split(","));
|
|
|
|
}
|
|
|
|
|
2012-10-03 06:19:43 +00:00
|
|
|
public static String appendToCsvIfNotExists(final String key, final String csv) {
|
2012-04-19 07:39:25 +00:00
|
|
|
if (TextUtils.isEmpty(csv)) return key;
|
|
|
|
if (containsInCsv(key, csv)) return csv;
|
|
|
|
return csv + "," + key;
|
|
|
|
}
|
|
|
|
|
2012-10-03 06:19:43 +00:00
|
|
|
public static String removeFromCsvIfExists(final String key, final String csv) {
|
2012-04-19 07:39:25 +00:00
|
|
|
if (TextUtils.isEmpty(csv)) return "";
|
|
|
|
final String[] elements = csv.split(",");
|
|
|
|
if (!containsInArray(key, elements)) return csv;
|
2012-08-21 07:34:55 +00:00
|
|
|
final ArrayList<String> result = CollectionUtils.newArrayList(elements.length - 1);
|
2012-04-19 07:39:25 +00:00
|
|
|
for (final String element : elements) {
|
|
|
|
if (!key.equals(element)) result.add(element);
|
|
|
|
}
|
|
|
|
return TextUtils.join(",", result);
|
2012-03-08 08:07:02 +00:00
|
|
|
}
|
|
|
|
|
2012-12-27 05:48:47 +00:00
|
|
|
/**
|
|
|
|
* Find a string that start with specified prefix from an array.
|
|
|
|
*
|
|
|
|
* @param prefix a prefix string to find.
|
|
|
|
* @param array an string array to be searched.
|
|
|
|
* @return the rest part of the string that starts with the prefix.
|
|
|
|
* Returns null if it couldn't be found.
|
|
|
|
*/
|
|
|
|
public static String findPrefixedString(final String prefix, final String[] array) {
|
|
|
|
for (final String element : array) {
|
|
|
|
if (element.startsWith(prefix)) {
|
|
|
|
return element.substring(prefix.length());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2012-03-08 08:07:02 +00:00
|
|
|
/**
|
|
|
|
* Remove duplicates from an array of strings.
|
|
|
|
*
|
2012-04-06 02:49:48 +00:00
|
|
|
* This method will always keep the first occurrence of all strings at their position
|
2012-03-08 08:07:02 +00:00
|
|
|
* in the array, removing the subsequent ones.
|
|
|
|
*/
|
2012-10-03 06:19:43 +00:00
|
|
|
public static void removeDupes(final ArrayList<String> suggestions) {
|
2012-03-08 08:07:02 +00:00
|
|
|
if (suggestions.size() < 2) return;
|
|
|
|
int i = 1;
|
|
|
|
// Don't cache suggestions.size(), since we may be removing items
|
|
|
|
while (i < suggestions.size()) {
|
2012-10-03 06:19:43 +00:00
|
|
|
final String cur = suggestions.get(i);
|
2012-03-08 08:07:02 +00:00
|
|
|
// Compare each suggestion with each previous suggestion
|
|
|
|
for (int j = 0; j < i; j++) {
|
2012-10-03 06:19:43 +00:00
|
|
|
final String previous = suggestions.get(j);
|
2012-03-08 08:07:02 +00:00
|
|
|
if (TextUtils.equals(cur, previous)) {
|
2012-03-13 07:52:42 +00:00
|
|
|
suggestions.remove(i);
|
2012-03-08 08:07:02 +00:00
|
|
|
i--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
2012-04-04 05:30:42 +00:00
|
|
|
|
2013-04-10 08:13:26 +00:00
|
|
|
public static String capitalizeFirstCodePoint(final String s, final Locale locale) {
|
2013-04-10 03:07:41 +00:00
|
|
|
if (s.length() <= 1) {
|
|
|
|
return s.toUpperCase(locale);
|
|
|
|
}
|
2013-04-10 08:13:26 +00:00
|
|
|
// Please refer to the comment below in
|
|
|
|
// {@link #capitalizeFirstAndDowncaseRest(String,Locale)} as this has the same shortcomings
|
2013-04-10 03:07:41 +00:00
|
|
|
final int cutoff = s.offsetByCodePoints(0, 1);
|
|
|
|
return s.substring(0, cutoff).toUpperCase(locale) + s.substring(cutoff);
|
|
|
|
}
|
|
|
|
|
2013-04-10 08:13:26 +00:00
|
|
|
public static String capitalizeFirstAndDowncaseRest(final String s, final Locale locale) {
|
2012-04-04 05:30:42 +00:00
|
|
|
if (s.length() <= 1) {
|
2013-04-10 03:07:41 +00:00
|
|
|
return s.toUpperCase(locale);
|
2012-04-04 05:30:42 +00:00
|
|
|
}
|
|
|
|
// 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.
|
2013-04-05 03:07:16 +00:00
|
|
|
// - It does not work for Dutch, because it fails to account for the "ij" digraph when it's
|
|
|
|
// written as two separate code points. They are two different characters but both should
|
|
|
|
// be capitalized as "IJ" as if they were a single letter in most words (not all). If the
|
|
|
|
// unicode char for the ligature is used however, it works.
|
|
|
|
final int cutoff = s.offsetByCodePoints(0, 1);
|
|
|
|
return s.substring(0, cutoff).toUpperCase(locale) + s.substring(cutoff).toLowerCase(locale);
|
2012-04-04 05:30:42 +00:00
|
|
|
}
|
2012-04-17 06:55:17 +00:00
|
|
|
|
2012-10-03 06:19:43 +00:00
|
|
|
private static final int[] EMPTY_CODEPOINTS = {};
|
|
|
|
|
2012-04-17 06:55:17 +00:00
|
|
|
public static int[] toCodePointArray(final String string) {
|
2012-10-03 06:19:43 +00:00
|
|
|
final int length = string.length();
|
2012-06-25 18:17:48 +00:00
|
|
|
if (length <= 0) {
|
2012-10-03 06:19:43 +00:00
|
|
|
return EMPTY_CODEPOINTS;
|
2012-06-25 18:17:48 +00:00
|
|
|
}
|
2012-10-03 06:19:43 +00:00
|
|
|
final int[] codePoints = new int[string.codePointCount(0, length)];
|
|
|
|
int destIndex = 0;
|
|
|
|
for (int index = 0; index < length; index = string.offsetByCodePoints(index, 1)) {
|
|
|
|
codePoints[destIndex] = string.codePointAt(index);
|
|
|
|
destIndex++;
|
2012-04-17 06:55:17 +00:00
|
|
|
}
|
|
|
|
return codePoints;
|
|
|
|
}
|
2012-09-10 10:27:45 +00:00
|
|
|
|
2013-02-12 07:00:28 +00:00
|
|
|
public static String[] parseCsvString(final String text) {
|
|
|
|
final int size = text.length();
|
|
|
|
if (size == 0) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (codePointCount(text) == 1) {
|
|
|
|
return text.codePointAt(0) == Constants.CSV_SEPARATOR ? null : new String[] { text };
|
|
|
|
}
|
|
|
|
|
|
|
|
ArrayList<String> list = null;
|
|
|
|
int start = 0;
|
|
|
|
for (int pos = 0; pos < size; pos++) {
|
|
|
|
final char c = text.charAt(pos);
|
|
|
|
if (c == Constants.CSV_SEPARATOR) {
|
|
|
|
// Skip empty entry.
|
|
|
|
if (pos - start > 0) {
|
|
|
|
if (list == null) {
|
|
|
|
list = CollectionUtils.newArrayList();
|
|
|
|
}
|
|
|
|
list.add(text.substring(start, pos));
|
|
|
|
}
|
|
|
|
// Skip comma
|
|
|
|
start = pos + 1;
|
|
|
|
} else if (c == Constants.CSV_ESCAPE) {
|
|
|
|
// Skip escape character and escaped character.
|
|
|
|
pos++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
final String remain = (size - start > 0) ? text.substring(start) : null;
|
|
|
|
if (list == null) {
|
|
|
|
return remain != null ? new String[] { remain } : null;
|
|
|
|
}
|
|
|
|
if (remain != null) {
|
|
|
|
list.add(remain);
|
|
|
|
}
|
|
|
|
return list.toArray(new String[list.size()]);
|
|
|
|
}
|
2013-04-04 12:14:37 +00:00
|
|
|
|
2013-04-05 03:07:16 +00:00
|
|
|
// This method assumes the text is not null. For the empty string, it returns CAPITALIZE_NONE.
|
2013-04-04 12:14:37 +00:00
|
|
|
public static int getCapitalizationType(final String text) {
|
|
|
|
// If the first char is not uppercase, then the word is either all lower case or
|
|
|
|
// camel case, and in either case we return CAPITALIZE_NONE.
|
|
|
|
final int len = text.length();
|
2013-04-05 03:07:16 +00:00
|
|
|
int index = 0;
|
|
|
|
for (; index < len; index = text.offsetByCodePoints(index, 1)) {
|
|
|
|
if (Character.isLetter(text.codePointAt(index))) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (index == len) return CAPITALIZE_NONE;
|
|
|
|
if (!Character.isUpperCase(text.codePointAt(index))) {
|
|
|
|
return CAPITALIZE_NONE;
|
|
|
|
}
|
2013-04-04 12:14:37 +00:00
|
|
|
int capsCount = 1;
|
|
|
|
int letterCount = 1;
|
2013-04-05 03:07:16 +00:00
|
|
|
for (index = text.offsetByCodePoints(index, 1); index < len;
|
|
|
|
index = text.offsetByCodePoints(index, 1)) {
|
2013-04-04 12:14:37 +00:00
|
|
|
if (1 != capsCount && letterCount != capsCount) break;
|
2013-04-05 03:07:16 +00:00
|
|
|
final int codePoint = text.codePointAt(index);
|
2013-04-04 12:14:37 +00:00
|
|
|
if (Character.isUpperCase(codePoint)) {
|
|
|
|
++capsCount;
|
|
|
|
++letterCount;
|
|
|
|
} else if (Character.isLetter(codePoint)) {
|
|
|
|
// We need to discount non-letters since they may not be upper-case, but may
|
|
|
|
// still be part of a word (e.g. single quote or dash, as in "IT'S" or "FULL-TIME")
|
|
|
|
++letterCount;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// We know the first char is upper case. So we want to test if either every letter other
|
|
|
|
// than the first is lower case, or if they are all upper case. If the string is exactly
|
|
|
|
// one char long, then we will arrive here with letterCount 1, and this is correct, too.
|
|
|
|
if (1 == capsCount) return CAPITALIZE_FIRST;
|
|
|
|
return (letterCount == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE);
|
|
|
|
}
|
2013-04-05 06:52:41 +00:00
|
|
|
|
|
|
|
public static boolean isIdenticalAfterUpcase(final String text) {
|
|
|
|
final int len = text.length();
|
|
|
|
for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
|
|
|
|
final int codePoint = text.codePointAt(i);
|
|
|
|
if (Character.isLetter(codePoint) && !Character.isUpperCase(codePoint)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean isIdenticalAfterDowncase(final String text) {
|
|
|
|
final int len = text.length();
|
|
|
|
for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
|
|
|
|
final int codePoint = text.codePointAt(i);
|
|
|
|
if (Character.isLetter(codePoint) && !Character.isLowerCase(codePoint)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean isIdenticalAfterCapitalizeEachWord(final String text,
|
|
|
|
final String separators) {
|
|
|
|
boolean needCapsNext = true;
|
|
|
|
final int len = text.length();
|
|
|
|
for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
|
|
|
|
final int codePoint = text.codePointAt(i);
|
|
|
|
if (Character.isLetter(codePoint)) {
|
|
|
|
if ((needCapsNext && !Character.isUpperCase(codePoint))
|
|
|
|
|| (!needCapsNext && !Character.isLowerCase(codePoint))) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// We need a capital letter next if this is a separator.
|
|
|
|
needCapsNext = (-1 != separators.indexOf(codePoint));
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: like capitalizeFirst*, this does not work perfectly for Dutch because of the IJ digraph
|
|
|
|
// which should be capitalized together in *some* cases.
|
|
|
|
public static String capitalizeEachWord(final String text, final String separators,
|
|
|
|
final Locale locale) {
|
|
|
|
final StringBuilder builder = new StringBuilder();
|
|
|
|
boolean needCapsNext = true;
|
|
|
|
final int len = text.length();
|
|
|
|
for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
|
|
|
|
final String nextChar = text.substring(i, text.offsetByCodePoints(i, 1));
|
|
|
|
if (needCapsNext) {
|
|
|
|
builder.append(nextChar.toUpperCase(locale));
|
|
|
|
} else {
|
|
|
|
builder.append(nextChar.toLowerCase(locale));
|
|
|
|
}
|
|
|
|
// We need a capital letter next if this is a separator.
|
|
|
|
needCapsNext = (-1 != separators.indexOf(nextChar.codePointAt(0)));
|
|
|
|
}
|
|
|
|
return builder.toString();
|
|
|
|
}
|
2012-03-08 08:07:02 +00:00
|
|
|
}
|