From e54a4005d569cddbf8610dfd3e9afaec540fa060 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Fri, 20 Jan 2012 16:19:51 +0900 Subject: [PATCH] Support @string reference in moreKeys attribute Change-Id: If0056d0601149d2ddd0e231a81e7b2409b37fc06 --- .../keyboard/internal/KeyStyles.java | 37 +++-- .../keyboard/internal/MoreKeySpecParser.java | 25 ++- .../com/android/inputmethod/latin/Utils.java | 55 +++++++ tests/res/values/strings.xml | 50 ++++++ .../keyboard/internal/KeyStylesTests.java | 145 +++++++++++++++++- 5 files changed, 281 insertions(+), 31 deletions(-) create mode 100644 tests/res/values/strings.xml diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java index 0f84c4563..faea38941 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java @@ -16,11 +16,13 @@ package com.android.inputmethod.keyboard.internal; +import android.content.res.Resources; import android.content.res.TypedArray; import android.util.Log; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.Utils; import com.android.inputmethod.latin.XmlParseUtils; import org.xmlpull.v1.XmlPullParser; @@ -30,15 +32,13 @@ import java.util.ArrayList; import java.util.HashMap; public class KeyStyles { - private static final String TAG = "KeyStyles"; + private static final String TAG = KeyStyles.class.getSimpleName(); private static final boolean DEBUG = false; private final HashMap mStyles = new HashMap(); private static final KeyStyle EMPTY_KEY_STYLE = new EmptyKeyStyle(); - private static final char ESCAPE_CHAR = '\\'; - public interface KeyStyle { public String[] getTextArray(TypedArray a, int index); public CharSequence getText(TypedArray a, int index); @@ -75,23 +75,30 @@ public class KeyStyles { if (!a.hasValue(index)) return null; final CharSequence text = a.getText(index); - return parseCsvText(text.toString()); + return parseCsvText(text.toString(), a.getResources(), R.string.english_ime_name); } - } /* package for test */ - static String[] parseCsvText(String text) { + static String[] parseCsvText(String rawText, Resources res, int packageNameResId) { + final String text = Utils.resolveStringResource(rawText, res, packageNameResId); final int size = text.length(); - if (size == 0) return null; - if (size == 1) return new String[] { text }; + if (size == 0) { + return null; + } + if (size == 1) { + return new String[] { text }; + } + final StringBuilder sb = new StringBuilder(); ArrayList list = null; int start = 0; for (int pos = 0; pos < size; pos++) { final char c = text.charAt(pos); if (c == ',') { - if (list == null) list = new ArrayList(); + if (list == null) { + list = new ArrayList(); + } if (sb.length() == 0) { list.add(text.substring(start, pos)); } else { @@ -100,24 +107,28 @@ public class KeyStyles { } start = pos + 1; continue; - } else if (c == ESCAPE_CHAR) { + } else if (c == Utils.ESCAPE_CHAR) { if (start == pos) { // Skip escape character at the beginning of the value. start++; pos++; } else { - if (start < pos && sb.length() == 0) + if (start < pos && sb.length() == 0) { sb.append(text.subSequence(start, pos)); + } pos++; - if (pos < size) + if (pos < size) { sb.append(text.charAt(pos)); + } } } else if (sb.length() > 0) { sb.append(c); } } if (list == null) { - return new String[] { sb.length() > 0 ? sb.toString() : text.substring(start) }; + return new String[] { + sb.length() > 0 ? sb.toString() : text.substring(start) + }; } else { list.add(sb.length() > 0 ? sb.toString() : text.substring(start)); return list.toArray(new String[list.size()]); diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParser.java index d4c85c33d..2facf8439 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParser.java +++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParser.java @@ -41,11 +41,9 @@ import java.util.ArrayList; public class MoreKeySpecParser { private static final String TAG = MoreKeySpecParser.class.getSimpleName(); - private static final char ESCAPE_CHAR = '\\'; - private static final String LABEL_END = "|"; - private static final String PREFIX_AT = "@"; - private static final String PREFIX_ICON = PREFIX_AT + "icon/"; - private static final String PREFIX_CODE = PREFIX_AT + "integer/"; + private static final char LABEL_END = '|'; + private static final String PREFIX_ICON = Utils.PREFIX_AT + "icon" + Utils.SUFFIX_SLASH; + private static final String PREFIX_CODE = Utils.PREFIX_AT + "integer" + Utils.SUFFIX_SLASH; private MoreKeySpecParser() { // Intentional empty constructor for utility class. @@ -72,14 +70,14 @@ public class MoreKeySpecParser { } private static String parseEscape(String text) { - if (text.indexOf(ESCAPE_CHAR) < 0) { + if (text.indexOf(Utils.ESCAPE_CHAR) < 0) { return text; } final int length = text.length(); final StringBuilder sb = new StringBuilder(); for (int pos = 0; pos < length; pos++) { final char c = text.charAt(pos); - if (c == ESCAPE_CHAR && pos + 1 < length) { + if (c == Utils.ESCAPE_CHAR && pos + 1 < length) { sb.append(text.charAt(++pos)); } else { sb.append(c); @@ -89,7 +87,7 @@ public class MoreKeySpecParser { } private static int indexOfLabelEnd(String moreKeySpec, int start) { - if (moreKeySpec.indexOf(ESCAPE_CHAR, start) < 0) { + if (moreKeySpec.indexOf(Utils.ESCAPE_CHAR, start) < 0) { final int end = moreKeySpec.indexOf(LABEL_END, start); if (end == 0) { throw new MoreKeySpecParserError(LABEL_END + " at " + start + ": " + moreKeySpec); @@ -99,9 +97,9 @@ public class MoreKeySpecParser { final int length = moreKeySpec.length(); for (int pos = start; pos < length; pos++) { final char c = moreKeySpec.charAt(pos); - if (c == ESCAPE_CHAR && pos + 1 < length) { + if (c == Utils.ESCAPE_CHAR && pos + 1 < length) { pos++; - } else if (moreKeySpec.startsWith(LABEL_END, pos)) { + } else if (c == LABEL_END) { return pos; } } @@ -131,7 +129,8 @@ public class MoreKeySpecParser { throw new MoreKeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec); } - final String outputText = parseEscape(moreKeySpec.substring(end + LABEL_END.length())); + final String outputText = parseEscape( + moreKeySpec.substring(end + /* LABEL_END */1)); if (!TextUtils.isEmpty(outputText)) { return outputText; } @@ -152,7 +151,7 @@ public class MoreKeySpecParser { throw new MoreKeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec); } final int resId = Utils.getResourceId(res, - moreKeySpec.substring(end + LABEL_END.length() + PREFIX_AT.length()), + moreKeySpec.substring(end + /* LABEL_END */1 + /* PREFIX_AT */1), R.string.english_ime_name); final int code = res.getInteger(resId); return code; @@ -170,7 +169,7 @@ public class MoreKeySpecParser { public static int getIconId(String moreKeySpec) { if (hasIcon(moreKeySpec)) { - int end = moreKeySpec.indexOf(LABEL_END, PREFIX_ICON.length() + 1); + final int end = moreKeySpec.indexOf(LABEL_END, PREFIX_ICON.length() + 1); final String iconId = moreKeySpec.substring(PREFIX_ICON.length(), end); try { return Integer.valueOf(iconId); diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index d8ca409c7..bc8a1301c 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -62,6 +62,12 @@ public class Utils { private static boolean DBG = LatinImeLogger.sDBG; private static boolean DBG_EDIT_DISTANCE = false; + // Constants for resource name parsing. + public static final char ESCAPE_CHAR = '\\'; + public static final char PREFIX_AT = '@'; + public static final char SUFFIX_SLASH = '/'; + private static final String PREFIX_STRING = PREFIX_AT + "string"; + private Utils() { // Intentional empty constructor for utility class. } @@ -802,4 +808,53 @@ public class Utils { } return resId; } + + public static String resolveStringResource(String text, Resources res, int packageNameResId) { + final int size = text.length(); + if (size < PREFIX_STRING.length()) { + return text; + } + + StringBuilder sb = null; + for (int pos = 0; pos < size; pos++) { + final char c = text.charAt(pos); + if (c == PREFIX_AT && text.startsWith(PREFIX_STRING, pos)) { + if (sb == null) { + sb = new StringBuilder(text.substring(0, pos)); + } + final int end = Utils.searchResourceNameEnd(text, pos + PREFIX_STRING.length()); + final String resName = text.substring(pos + 1, end); + final int resId = getResourceId(res, resName, packageNameResId); + sb.append(res.getString(resId)); + pos = end - 1; + } else if (c == ESCAPE_CHAR) { + pos++; + if (sb != null) { + sb.append(c); + if (pos < size) { + sb.append(text.charAt(pos)); + } + } + } else if (sb != null) { + sb.append(c); + } + } + return (sb == null) ? text : sb.toString(); + } + + private static int searchResourceNameEnd(String text, int start) { + final int size = text.length(); + if (start >= size || text.charAt(start) != SUFFIX_SLASH) { + throw new RuntimeException("Resource name not specified"); + } + for (int pos = start + 1; pos < size; pos++) { + final char c = text.charAt(pos); + // String resource name should be consisted of [a-z_0-9]. + if ((c >= 'a' && c <= 'z') || c == '_' || (c >= '0' && c <= '9')) { + continue; + } + return pos; + } + return size; + } } diff --git a/tests/res/values/strings.xml b/tests/res/values/strings.xml new file mode 100644 index 000000000..bfd1c1716 --- /dev/null +++ b/tests/res/values/strings.xml @@ -0,0 +1,50 @@ + + + + + "" + "a" + " " + "abc" + " " + "a b c" + " abc" + "abc " + " abc " + "\\a" + "\\," + "\\\\" + "a\\bc" + "\\abc" + "a\\,c" + "\\,bc" + "\\,\\\\bc" + "a\\\\c" + "a,b,c" + " a , b , c " + "abc,def,ghi" + " abc , def , ghi " + "a,\\,,c" + " a , \\, , c " + "\\abc,d\\ef,gh\\i" + " \\abc , d\\ef , gh\\i " + "ab\\\\,d\\\\\\,,g\\,i" + " ab\\\\ , d\\\\\\, , g\\,i " + diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyStylesTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyStylesTests.java index cebe40958..29881d91c 100644 --- a/tests/src/com/android/inputmethod/keyboard/internal/KeyStylesTests.java +++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyStylesTests.java @@ -16,27 +16,53 @@ package com.android.inputmethod.keyboard.internal; +import android.content.res.Resources; import android.test.AndroidTestCase; import android.text.TextUtils; +import com.android.inputmethod.latin.tests.R; + +import java.util.Arrays; + public class KeyStylesTests extends AndroidTestCase { + private Resources mTestResources; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mTestResources = getTestContext().getResources(); + } + private static String format(String message, Object expected, Object actual) { return message + " expected:<" + expected + "> but was:<" + actual + ">"; } - private static void assertTextArray(String message, String value, String ... expected) { - final String actual[] = KeyStyles.parseCsvText(value); + private void assertTextArray(String message, String value, String ... expected) { + final String actual[] = KeyStyles.parseCsvText(value, mTestResources, + R.string.empty_string); if (expected.length == 0) { assertNull(message, actual); return; } - assertSame(message + ": result length", expected.length, actual.length); + assertEquals(message + ": expected=" + Arrays.toString(expected) + + " actual=" + Arrays.toString(actual) + + ": result length", expected.length, actual.length); for (int i = 0; i < actual.length; i++) { final boolean equals = TextUtils.equals(expected[i], actual[i]); assertTrue(format(message + ": result at " + i + ":", expected[i], actual[i]), equals); } } + private void assertError(String message, String value, String ... expected) { + try { + assertTextArray(message, value, expected); + fail(message); + } catch (Exception pcpe) { + // success. + } + } + public void testParseCsvTextZero() { assertTextArray("Empty string", ""); } @@ -49,7 +75,10 @@ public class KeyStylesTests extends AndroidTestCase { assertTextArray("Spaces in label", "a b c", "a b c"); assertTextArray("Spaces at beginning of label", " abc", " abc"); assertTextArray("Spaces at end of label", "abc ", "abc "); - assertTextArray("label surrounded by spaces", " abc ", " abc "); + assertTextArray("Label surrounded by spaces", " abc ", " abc "); + + assertTextArray("Incomplete resource reference 1", "string", "string"); + assertTextArray("Incomplete resource reference 2", "@strin", "@strin"); } public void testParseCsvTextSingleEscaped() { @@ -57,11 +86,13 @@ public class KeyStylesTests extends AndroidTestCase { assertTextArray("Escaped comma", "\\,", ","); assertTextArray("Escaped escape", "\\\\", "\\"); assertTextArray("Escaped label", "a\\bc", "abc"); - assertTextArray("Escaped label at begininng", "\\abc", "abc"); + assertTextArray("Escaped label at beginning", "\\abc", "abc"); assertTextArray("Escaped label with comma", "a\\,c", "a,c"); assertTextArray("Escaped label with comma at beginning", "\\,bc", ",bc"); assertTextArray("Escaped label with successive", "\\,\\\\bc", ",\\bc"); assertTextArray("Escaped label with escape", "a\\\\c", "a\\c"); + + assertTextArray("Escaped @string", "\\@string/empty_string", "@string/empty_string"); } public void testParseCsvTextMulti() { @@ -83,5 +114,109 @@ public class KeyStylesTests extends AndroidTestCase { "ab\\\\,d\\\\\\,,g\\,i", "ab\\", "d\\,", "g,i"); assertTextArray("Multiple labels with comma and escape surrounded by spaces", " ab\\\\ , d\\\\\\, , g\\,i ", " ab\\ ", " d\\, ", " g,i "); + + assertTextArray("Multiple escaped @string", "\\@,\\@string/empty_string", + "@", "@string/empty_string"); + } + + public void testParseCsvResourceError() { + assertError("Incomplete resource name 1", "@string", "@string"); + assertError("Incomplete resource name 2", "@string/", "@string/"); + assertError("Non existing resource", "@string/non_existing"); + } + + public void testParseCsvResourceZero() { + assertTextArray("Empty string", + "@string/empty_string"); + } + + public void testParseCsvResourceSingle() { + assertTextArray("Single char", + "@string/single_char", "a"); + assertTextArray("Space", + "@string/space", " "); + assertTextArray("Single label", + "@string/single_label", "abc"); + assertTextArray("Spaces", + "@string/spaces", " "); + assertTextArray("Spaces in label", + "@string/spaces_in_label", "a b c"); + assertTextArray("Spaces at beginning of label", + "@string/spaces_at_beginning_of_label", " abc"); + assertTextArray("Spaces at end of label", + "@string/spaces_at_end_of_label", "abc "); + assertTextArray("label surrounded by spaces", + "@string/label_surrounded_by_spaces", " abc "); + } + + public void testParseCsvResourceSingleEscaped() { + assertTextArray("Escaped char", + "@string/escaped_char", "a"); + assertTextArray("Escaped comma", + "@string/escaped_comma", ","); + assertTextArray("Escaped escape", + "@string/escaped_escape", "\\"); + assertTextArray("Escaped label", + "@string/escaped_label", "abc"); + assertTextArray("Escaped label at beginning", + "@string/escaped_label_at_beginning", "abc"); + assertTextArray("Escaped label with comma", + "@string/escaped_label_with_comma", "a,c"); + assertTextArray("Escaped label with comma at beginning", + "@string/escaped_label_with_comma_at_beginning", ",bc"); + assertTextArray("Escaped label with successive", + "@string/escaped_label_with_successive", ",\\bc"); + assertTextArray("Escaped label with escape", + "@string/escaped_label_with_escape", "a\\c"); + } + + public void testParseCsvResourceMulti() { + assertTextArray("Multiple chars", + "@string/multiple_chars", "a", "b", "c"); + assertTextArray("Multiple chars surrounded by spaces", + "@string/multiple_chars_surrounded_by_spaces", + " a ", " b ", " c "); + assertTextArray("Multiple labels", + "@string/multiple_labels", "abc", "def", "ghi"); + assertTextArray("Multiple labels surrounded by spaces", + "@string/multiple_labels_surrounded_by_spaces", " abc ", " def ", " ghi "); + } + + public void testParseCsvResourcetMultiEscaped() { + assertTextArray("Multiple chars with comma", + "@string/multiple_chars_with_comma", + "a", ",", "c"); + assertTextArray("Multiple chars with comma surrounded by spaces", + "@string/multiple_chars_with_comma_surrounded_by_spaces", + " a ", " , ", " c "); + assertTextArray("Multiple labels with escape", + "@string/multiple_labels_with_escape", + "abc", "def", "ghi"); + assertTextArray("Multiple labels with escape surrounded by spaces", + "@string/multiple_labels_with_escape_surrounded_by_spaces", + " abc ", " def ", " ghi "); + assertTextArray("Multiple labels with comma and escape", + "@string/multiple_labels_with_comma_and_escape", + "ab\\", "d\\,", "g,i"); + assertTextArray("Multiple labels with comma and escape surrounded by spaces", + "@string/multiple_labels_with_comma_and_escape_surrounded_by_spaces", + " ab\\ ", " d\\, ", " g,i "); + } + + public void testParseMultipleResources() { + assertTextArray("Literals and resources", + "1,@string/multiple_chars,z", "1", "a", "b", "c", "z"); + assertTextArray("Multiple single resource chars and labels", + "@string/single_char,@string/single_label,@string/escaped_comma", + "a", "abc", ","); + assertTextArray("Multiple multiple resource chars and labels", + "@string/multiple_chars,@string/multiple_labels,@string/multiple_chars_with_comma", + "a", "b", "c", "abc", "def", "ghi", "a", ",", "c"); + assertTextArray("Concatenated resources", + "@string/multiple_chars@string/multiple_labels@string/multiple_chars_with_comma", + "a", "b", "cabc", "def", "ghia", ",", "c"); + assertTextArray("Concatenated resource and literal", + "abc@string/multiple_labels", + "abcabc", "def", "ghi"); } }