Loading java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java +24 −13 Original line number Original line Diff line number Diff line Loading @@ -16,11 +16,13 @@ package com.android.inputmethod.keyboard.internal; package com.android.inputmethod.keyboard.internal; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.TypedArray; import android.util.Log; import android.util.Log; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.Utils; import com.android.inputmethod.latin.XmlParseUtils; import com.android.inputmethod.latin.XmlParseUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParser; Loading @@ -30,15 +32,13 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashMap; public class KeyStyles { public class KeyStyles { private static final String TAG = "KeyStyles"; private static final String TAG = KeyStyles.class.getSimpleName(); private static final boolean DEBUG = false; private static final boolean DEBUG = false; private final HashMap<String, DeclaredKeyStyle> mStyles = private final HashMap<String, DeclaredKeyStyle> mStyles = new HashMap<String, DeclaredKeyStyle>(); new HashMap<String, DeclaredKeyStyle>(); private static final KeyStyle EMPTY_KEY_STYLE = new EmptyKeyStyle(); private static final KeyStyle EMPTY_KEY_STYLE = new EmptyKeyStyle(); private static final char ESCAPE_CHAR = '\\'; public interface KeyStyle { public interface KeyStyle { public String[] getTextArray(TypedArray a, int index); public String[] getTextArray(TypedArray a, int index); public CharSequence getText(TypedArray a, int index); public CharSequence getText(TypedArray a, int index); Loading Loading @@ -75,23 +75,30 @@ public class KeyStyles { if (!a.hasValue(index)) if (!a.hasValue(index)) return null; return null; final CharSequence text = a.getText(index); final CharSequence text = a.getText(index); return parseCsvText(text.toString()); return parseCsvText(text.toString(), a.getResources(), R.string.english_ime_name); } } } } /* package for test */ /* 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(); final int size = text.length(); if (size == 0) return null; if (size == 0) { if (size == 1) return new String[] { text }; return null; } if (size == 1) { return new String[] { text }; } final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder(); ArrayList<String> list = null; ArrayList<String> list = null; int start = 0; int start = 0; for (int pos = 0; pos < size; pos++) { for (int pos = 0; pos < size; pos++) { final char c = text.charAt(pos); final char c = text.charAt(pos); if (c == ',') { if (c == ',') { if (list == null) list = new ArrayList<String>(); if (list == null) { list = new ArrayList<String>(); } if (sb.length() == 0) { if (sb.length() == 0) { list.add(text.substring(start, pos)); list.add(text.substring(start, pos)); } else { } else { Loading @@ -100,24 +107,28 @@ public class KeyStyles { } } start = pos + 1; start = pos + 1; continue; continue; } else if (c == ESCAPE_CHAR) { } else if (c == Utils.ESCAPE_CHAR) { if (start == pos) { if (start == pos) { // Skip escape character at the beginning of the value. // Skip escape character at the beginning of the value. start++; start++; pos++; pos++; } else { } else { if (start < pos && sb.length() == 0) if (start < pos && sb.length() == 0) { sb.append(text.subSequence(start, pos)); sb.append(text.subSequence(start, pos)); } pos++; pos++; if (pos < size) if (pos < size) { sb.append(text.charAt(pos)); sb.append(text.charAt(pos)); } } } } else if (sb.length() > 0) { } else if (sb.length() > 0) { sb.append(c); sb.append(c); } } } } if (list == null) { 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 { } else { list.add(sb.length() > 0 ? sb.toString() : text.substring(start)); list.add(sb.length() > 0 ? sb.toString() : text.substring(start)); return list.toArray(new String[list.size()]); return list.toArray(new String[list.size()]); Loading java/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParser.java +12 −13 Original line number Original line Diff line number Diff line Loading @@ -41,11 +41,9 @@ import java.util.ArrayList; public class MoreKeySpecParser { public class MoreKeySpecParser { private static final String TAG = MoreKeySpecParser.class.getSimpleName(); private static final String TAG = MoreKeySpecParser.class.getSimpleName(); private static final char ESCAPE_CHAR = '\\'; private static final char LABEL_END = '|'; private static final String LABEL_END = "|"; private static final String PREFIX_ICON = Utils.PREFIX_AT + "icon" + Utils.SUFFIX_SLASH; private static final String PREFIX_AT = "@"; private static final String PREFIX_CODE = Utils.PREFIX_AT + "integer" + Utils.SUFFIX_SLASH; private static final String PREFIX_ICON = PREFIX_AT + "icon/"; private static final String PREFIX_CODE = PREFIX_AT + "integer/"; private MoreKeySpecParser() { private MoreKeySpecParser() { // Intentional empty constructor for utility class. // Intentional empty constructor for utility class. Loading @@ -72,14 +70,14 @@ public class MoreKeySpecParser { } } private static String parseEscape(String text) { private static String parseEscape(String text) { if (text.indexOf(ESCAPE_CHAR) < 0) { if (text.indexOf(Utils.ESCAPE_CHAR) < 0) { return text; return text; } } final int length = text.length(); final int length = text.length(); final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder(); for (int pos = 0; pos < length; pos++) { for (int pos = 0; pos < length; pos++) { final char c = text.charAt(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)); sb.append(text.charAt(++pos)); } else { } else { sb.append(c); sb.append(c); Loading @@ -89,7 +87,7 @@ public class MoreKeySpecParser { } } private static int indexOfLabelEnd(String moreKeySpec, int start) { 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); final int end = moreKeySpec.indexOf(LABEL_END, start); if (end == 0) { if (end == 0) { throw new MoreKeySpecParserError(LABEL_END + " at " + start + ": " + moreKeySpec); throw new MoreKeySpecParserError(LABEL_END + " at " + start + ": " + moreKeySpec); Loading @@ -99,9 +97,9 @@ public class MoreKeySpecParser { final int length = moreKeySpec.length(); final int length = moreKeySpec.length(); for (int pos = start; pos < length; pos++) { for (int pos = start; pos < length; pos++) { final char c = moreKeySpec.charAt(pos); final char c = moreKeySpec.charAt(pos); if (c == ESCAPE_CHAR && pos + 1 < length) { if (c == Utils.ESCAPE_CHAR && pos + 1 < length) { pos++; pos++; } else if (moreKeySpec.startsWith(LABEL_END, pos)) { } else if (c == LABEL_END) { return pos; return pos; } } } } Loading Loading @@ -131,7 +129,8 @@ public class MoreKeySpecParser { throw new MoreKeySpecParserError("Multiple " + LABEL_END + ": " throw new MoreKeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec); + 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)) { if (!TextUtils.isEmpty(outputText)) { return outputText; return outputText; } } Loading @@ -152,7 +151,7 @@ public class MoreKeySpecParser { throw new MoreKeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec); throw new MoreKeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec); } } final int resId = Utils.getResourceId(res, 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); R.string.english_ime_name); final int code = res.getInteger(resId); final int code = res.getInteger(resId); return code; return code; Loading @@ -170,7 +169,7 @@ public class MoreKeySpecParser { public static int getIconId(String moreKeySpec) { public static int getIconId(String moreKeySpec) { if (hasIcon(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); final String iconId = moreKeySpec.substring(PREFIX_ICON.length(), end); try { try { return Integer.valueOf(iconId); return Integer.valueOf(iconId); Loading java/src/com/android/inputmethod/latin/Utils.java +55 −0 Original line number Original line Diff line number Diff line Loading @@ -62,6 +62,12 @@ public class Utils { private static boolean DBG = LatinImeLogger.sDBG; private static boolean DBG = LatinImeLogger.sDBG; private static boolean DBG_EDIT_DISTANCE = false; 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() { private Utils() { // Intentional empty constructor for utility class. // Intentional empty constructor for utility class. } } Loading Loading @@ -802,4 +808,53 @@ public class Utils { } } return resId; 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; } } } tests/res/values/strings.xml 0 → 100644 +50 −0 Original line number Original line Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- /* ** ** Copyright 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. */ --> <resources> <string name="empty_string">""</string> <string name="single_char">"a"</string> <string name="space">" "</string> <string name="single_label">"abc"</string> <string name="spaces">" "</string> <string name="spaces_in_label">"a b c"</string> <string name="spaces_at_beginning_of_label">" abc"</string> <string name="spaces_at_end_of_label">"abc "</string> <string name="label_surrounded_by_spaces">" abc "</string> <string name="escaped_char">"\\a"</string> <string name="escaped_comma">"\\,"</string> <string name="escaped_escape">"\\\\"</string> <string name="escaped_label">"a\\bc"</string> <string name="escaped_label_at_beginning">"\\abc"</string> <string name="escaped_label_with_comma">"a\\,c"</string> <string name="escaped_label_with_comma_at_beginning">"\\,bc"</string> <string name="escaped_label_with_successive">"\\,\\\\bc"</string> <string name="escaped_label_with_escape">"a\\\\c"</string> <string name="multiple_chars">"a,b,c"</string> <string name="multiple_chars_surrounded_by_spaces">" a , b , c "</string> <string name="multiple_labels">"abc,def,ghi"</string> <string name="multiple_labels_surrounded_by_spaces">" abc , def , ghi "</string> <string name="multiple_chars_with_comma">"a,\\,,c"</string> <string name="multiple_chars_with_comma_surrounded_by_spaces">" a , \\, , c "</string> <string name="multiple_labels_with_escape">"\\abc,d\\ef,gh\\i"</string> <string name="multiple_labels_with_escape_surrounded_by_spaces">" \\abc , d\\ef , gh\\i "</string> <string name="multiple_labels_with_comma_and_escape">"ab\\\\,d\\\\\\,,g\\,i"</string> <string name="multiple_labels_with_comma_and_escape_surrounded_by_spaces">" ab\\\\ , d\\\\\\, , g\\,i "</string> </resources> tests/src/com/android/inputmethod/keyboard/internal/KeyStylesTests.java +140 −5 Original line number Original line Diff line number Diff line Loading @@ -16,27 +16,53 @@ package com.android.inputmethod.keyboard.internal; package com.android.inputmethod.keyboard.internal; import android.content.res.Resources; import android.test.AndroidTestCase; import android.test.AndroidTestCase; import android.text.TextUtils; import android.text.TextUtils; import com.android.inputmethod.latin.tests.R; import java.util.Arrays; public class KeyStylesTests extends AndroidTestCase { 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) { private static String format(String message, Object expected, Object actual) { return message + " expected:<" + expected + "> but was:<" + actual + ">"; return message + " expected:<" + expected + "> but was:<" + actual + ">"; } } private static void assertTextArray(String message, String value, String ... expected) { private void assertTextArray(String message, String value, String ... expected) { final String actual[] = KeyStyles.parseCsvText(value); final String actual[] = KeyStyles.parseCsvText(value, mTestResources, R.string.empty_string); if (expected.length == 0) { if (expected.length == 0) { assertNull(message, actual); assertNull(message, actual); return; 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++) { for (int i = 0; i < actual.length; i++) { final boolean equals = TextUtils.equals(expected[i], actual[i]); final boolean equals = TextUtils.equals(expected[i], actual[i]); assertTrue(format(message + ": result at " + i + ":", expected[i], actual[i]), equals); 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() { public void testParseCsvTextZero() { assertTextArray("Empty string", ""); assertTextArray("Empty string", ""); } } Loading @@ -49,7 +75,10 @@ public class KeyStylesTests extends AndroidTestCase { assertTextArray("Spaces in label", "a b c", "a b c"); assertTextArray("Spaces in label", "a b c", "a b c"); assertTextArray("Spaces at beginning of label", " abc", " abc"); assertTextArray("Spaces at beginning of label", " abc", " abc"); assertTextArray("Spaces at end 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() { public void testParseCsvTextSingleEscaped() { Loading @@ -57,11 +86,13 @@ public class KeyStylesTests extends AndroidTestCase { assertTextArray("Escaped comma", "\\,", ","); assertTextArray("Escaped comma", "\\,", ","); assertTextArray("Escaped escape", "\\\\", "\\"); assertTextArray("Escaped escape", "\\\\", "\\"); assertTextArray("Escaped label", "a\\bc", "abc"); 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", "a\\,c", "a,c"); assertTextArray("Escaped label with comma at beginning", "\\,bc", ",bc"); assertTextArray("Escaped label with comma at beginning", "\\,bc", ",bc"); assertTextArray("Escaped label with successive", "\\,\\\\bc", ",\\bc"); assertTextArray("Escaped label with successive", "\\,\\\\bc", ",\\bc"); assertTextArray("Escaped label with escape", "a\\\\c", "a\\c"); assertTextArray("Escaped label with escape", "a\\\\c", "a\\c"); assertTextArray("Escaped @string", "\\@string/empty_string", "@string/empty_string"); } } public void testParseCsvTextMulti() { public void testParseCsvTextMulti() { Loading @@ -83,5 +114,109 @@ public class KeyStylesTests extends AndroidTestCase { "ab\\\\,d\\\\\\,,g\\,i", "ab\\", "d\\,", "g,i"); "ab\\\\,d\\\\\\,,g\\,i", "ab\\", "d\\,", "g,i"); assertTextArray("Multiple labels with comma and escape surrounded by spaces", assertTextArray("Multiple labels with comma and escape surrounded by spaces", " ab\\\\ , d\\\\\\, , g\\,i ", " ab\\ ", " d\\, ", " g,i "); " 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"); } } } } Loading
java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java +24 −13 Original line number Original line Diff line number Diff line Loading @@ -16,11 +16,13 @@ package com.android.inputmethod.keyboard.internal; package com.android.inputmethod.keyboard.internal; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.TypedArray; import android.util.Log; import android.util.Log; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.Utils; import com.android.inputmethod.latin.XmlParseUtils; import com.android.inputmethod.latin.XmlParseUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParser; Loading @@ -30,15 +32,13 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashMap; public class KeyStyles { public class KeyStyles { private static final String TAG = "KeyStyles"; private static final String TAG = KeyStyles.class.getSimpleName(); private static final boolean DEBUG = false; private static final boolean DEBUG = false; private final HashMap<String, DeclaredKeyStyle> mStyles = private final HashMap<String, DeclaredKeyStyle> mStyles = new HashMap<String, DeclaredKeyStyle>(); new HashMap<String, DeclaredKeyStyle>(); private static final KeyStyle EMPTY_KEY_STYLE = new EmptyKeyStyle(); private static final KeyStyle EMPTY_KEY_STYLE = new EmptyKeyStyle(); private static final char ESCAPE_CHAR = '\\'; public interface KeyStyle { public interface KeyStyle { public String[] getTextArray(TypedArray a, int index); public String[] getTextArray(TypedArray a, int index); public CharSequence getText(TypedArray a, int index); public CharSequence getText(TypedArray a, int index); Loading Loading @@ -75,23 +75,30 @@ public class KeyStyles { if (!a.hasValue(index)) if (!a.hasValue(index)) return null; return null; final CharSequence text = a.getText(index); final CharSequence text = a.getText(index); return parseCsvText(text.toString()); return parseCsvText(text.toString(), a.getResources(), R.string.english_ime_name); } } } } /* package for test */ /* 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(); final int size = text.length(); if (size == 0) return null; if (size == 0) { if (size == 1) return new String[] { text }; return null; } if (size == 1) { return new String[] { text }; } final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder(); ArrayList<String> list = null; ArrayList<String> list = null; int start = 0; int start = 0; for (int pos = 0; pos < size; pos++) { for (int pos = 0; pos < size; pos++) { final char c = text.charAt(pos); final char c = text.charAt(pos); if (c == ',') { if (c == ',') { if (list == null) list = new ArrayList<String>(); if (list == null) { list = new ArrayList<String>(); } if (sb.length() == 0) { if (sb.length() == 0) { list.add(text.substring(start, pos)); list.add(text.substring(start, pos)); } else { } else { Loading @@ -100,24 +107,28 @@ public class KeyStyles { } } start = pos + 1; start = pos + 1; continue; continue; } else if (c == ESCAPE_CHAR) { } else if (c == Utils.ESCAPE_CHAR) { if (start == pos) { if (start == pos) { // Skip escape character at the beginning of the value. // Skip escape character at the beginning of the value. start++; start++; pos++; pos++; } else { } else { if (start < pos && sb.length() == 0) if (start < pos && sb.length() == 0) { sb.append(text.subSequence(start, pos)); sb.append(text.subSequence(start, pos)); } pos++; pos++; if (pos < size) if (pos < size) { sb.append(text.charAt(pos)); sb.append(text.charAt(pos)); } } } } else if (sb.length() > 0) { } else if (sb.length() > 0) { sb.append(c); sb.append(c); } } } } if (list == null) { 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 { } else { list.add(sb.length() > 0 ? sb.toString() : text.substring(start)); list.add(sb.length() > 0 ? sb.toString() : text.substring(start)); return list.toArray(new String[list.size()]); return list.toArray(new String[list.size()]); Loading
java/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParser.java +12 −13 Original line number Original line Diff line number Diff line Loading @@ -41,11 +41,9 @@ import java.util.ArrayList; public class MoreKeySpecParser { public class MoreKeySpecParser { private static final String TAG = MoreKeySpecParser.class.getSimpleName(); private static final String TAG = MoreKeySpecParser.class.getSimpleName(); private static final char ESCAPE_CHAR = '\\'; private static final char LABEL_END = '|'; private static final String LABEL_END = "|"; private static final String PREFIX_ICON = Utils.PREFIX_AT + "icon" + Utils.SUFFIX_SLASH; private static final String PREFIX_AT = "@"; private static final String PREFIX_CODE = Utils.PREFIX_AT + "integer" + Utils.SUFFIX_SLASH; private static final String PREFIX_ICON = PREFIX_AT + "icon/"; private static final String PREFIX_CODE = PREFIX_AT + "integer/"; private MoreKeySpecParser() { private MoreKeySpecParser() { // Intentional empty constructor for utility class. // Intentional empty constructor for utility class. Loading @@ -72,14 +70,14 @@ public class MoreKeySpecParser { } } private static String parseEscape(String text) { private static String parseEscape(String text) { if (text.indexOf(ESCAPE_CHAR) < 0) { if (text.indexOf(Utils.ESCAPE_CHAR) < 0) { return text; return text; } } final int length = text.length(); final int length = text.length(); final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder(); for (int pos = 0; pos < length; pos++) { for (int pos = 0; pos < length; pos++) { final char c = text.charAt(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)); sb.append(text.charAt(++pos)); } else { } else { sb.append(c); sb.append(c); Loading @@ -89,7 +87,7 @@ public class MoreKeySpecParser { } } private static int indexOfLabelEnd(String moreKeySpec, int start) { 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); final int end = moreKeySpec.indexOf(LABEL_END, start); if (end == 0) { if (end == 0) { throw new MoreKeySpecParserError(LABEL_END + " at " + start + ": " + moreKeySpec); throw new MoreKeySpecParserError(LABEL_END + " at " + start + ": " + moreKeySpec); Loading @@ -99,9 +97,9 @@ public class MoreKeySpecParser { final int length = moreKeySpec.length(); final int length = moreKeySpec.length(); for (int pos = start; pos < length; pos++) { for (int pos = start; pos < length; pos++) { final char c = moreKeySpec.charAt(pos); final char c = moreKeySpec.charAt(pos); if (c == ESCAPE_CHAR && pos + 1 < length) { if (c == Utils.ESCAPE_CHAR && pos + 1 < length) { pos++; pos++; } else if (moreKeySpec.startsWith(LABEL_END, pos)) { } else if (c == LABEL_END) { return pos; return pos; } } } } Loading Loading @@ -131,7 +129,8 @@ public class MoreKeySpecParser { throw new MoreKeySpecParserError("Multiple " + LABEL_END + ": " throw new MoreKeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec); + 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)) { if (!TextUtils.isEmpty(outputText)) { return outputText; return outputText; } } Loading @@ -152,7 +151,7 @@ public class MoreKeySpecParser { throw new MoreKeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec); throw new MoreKeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec); } } final int resId = Utils.getResourceId(res, 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); R.string.english_ime_name); final int code = res.getInteger(resId); final int code = res.getInteger(resId); return code; return code; Loading @@ -170,7 +169,7 @@ public class MoreKeySpecParser { public static int getIconId(String moreKeySpec) { public static int getIconId(String moreKeySpec) { if (hasIcon(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); final String iconId = moreKeySpec.substring(PREFIX_ICON.length(), end); try { try { return Integer.valueOf(iconId); return Integer.valueOf(iconId); Loading
java/src/com/android/inputmethod/latin/Utils.java +55 −0 Original line number Original line Diff line number Diff line Loading @@ -62,6 +62,12 @@ public class Utils { private static boolean DBG = LatinImeLogger.sDBG; private static boolean DBG = LatinImeLogger.sDBG; private static boolean DBG_EDIT_DISTANCE = false; 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() { private Utils() { // Intentional empty constructor for utility class. // Intentional empty constructor for utility class. } } Loading Loading @@ -802,4 +808,53 @@ public class Utils { } } return resId; 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; } } }
tests/res/values/strings.xml 0 → 100644 +50 −0 Original line number Original line Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- /* ** ** Copyright 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. */ --> <resources> <string name="empty_string">""</string> <string name="single_char">"a"</string> <string name="space">" "</string> <string name="single_label">"abc"</string> <string name="spaces">" "</string> <string name="spaces_in_label">"a b c"</string> <string name="spaces_at_beginning_of_label">" abc"</string> <string name="spaces_at_end_of_label">"abc "</string> <string name="label_surrounded_by_spaces">" abc "</string> <string name="escaped_char">"\\a"</string> <string name="escaped_comma">"\\,"</string> <string name="escaped_escape">"\\\\"</string> <string name="escaped_label">"a\\bc"</string> <string name="escaped_label_at_beginning">"\\abc"</string> <string name="escaped_label_with_comma">"a\\,c"</string> <string name="escaped_label_with_comma_at_beginning">"\\,bc"</string> <string name="escaped_label_with_successive">"\\,\\\\bc"</string> <string name="escaped_label_with_escape">"a\\\\c"</string> <string name="multiple_chars">"a,b,c"</string> <string name="multiple_chars_surrounded_by_spaces">" a , b , c "</string> <string name="multiple_labels">"abc,def,ghi"</string> <string name="multiple_labels_surrounded_by_spaces">" abc , def , ghi "</string> <string name="multiple_chars_with_comma">"a,\\,,c"</string> <string name="multiple_chars_with_comma_surrounded_by_spaces">" a , \\, , c "</string> <string name="multiple_labels_with_escape">"\\abc,d\\ef,gh\\i"</string> <string name="multiple_labels_with_escape_surrounded_by_spaces">" \\abc , d\\ef , gh\\i "</string> <string name="multiple_labels_with_comma_and_escape">"ab\\\\,d\\\\\\,,g\\,i"</string> <string name="multiple_labels_with_comma_and_escape_surrounded_by_spaces">" ab\\\\ , d\\\\\\, , g\\,i "</string> </resources>
tests/src/com/android/inputmethod/keyboard/internal/KeyStylesTests.java +140 −5 Original line number Original line Diff line number Diff line Loading @@ -16,27 +16,53 @@ package com.android.inputmethod.keyboard.internal; package com.android.inputmethod.keyboard.internal; import android.content.res.Resources; import android.test.AndroidTestCase; import android.test.AndroidTestCase; import android.text.TextUtils; import android.text.TextUtils; import com.android.inputmethod.latin.tests.R; import java.util.Arrays; public class KeyStylesTests extends AndroidTestCase { 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) { private static String format(String message, Object expected, Object actual) { return message + " expected:<" + expected + "> but was:<" + actual + ">"; return message + " expected:<" + expected + "> but was:<" + actual + ">"; } } private static void assertTextArray(String message, String value, String ... expected) { private void assertTextArray(String message, String value, String ... expected) { final String actual[] = KeyStyles.parseCsvText(value); final String actual[] = KeyStyles.parseCsvText(value, mTestResources, R.string.empty_string); if (expected.length == 0) { if (expected.length == 0) { assertNull(message, actual); assertNull(message, actual); return; 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++) { for (int i = 0; i < actual.length; i++) { final boolean equals = TextUtils.equals(expected[i], actual[i]); final boolean equals = TextUtils.equals(expected[i], actual[i]); assertTrue(format(message + ": result at " + i + ":", expected[i], actual[i]), equals); 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() { public void testParseCsvTextZero() { assertTextArray("Empty string", ""); assertTextArray("Empty string", ""); } } Loading @@ -49,7 +75,10 @@ public class KeyStylesTests extends AndroidTestCase { assertTextArray("Spaces in label", "a b c", "a b c"); assertTextArray("Spaces in label", "a b c", "a b c"); assertTextArray("Spaces at beginning of label", " abc", " abc"); assertTextArray("Spaces at beginning of label", " abc", " abc"); assertTextArray("Spaces at end 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() { public void testParseCsvTextSingleEscaped() { Loading @@ -57,11 +86,13 @@ public class KeyStylesTests extends AndroidTestCase { assertTextArray("Escaped comma", "\\,", ","); assertTextArray("Escaped comma", "\\,", ","); assertTextArray("Escaped escape", "\\\\", "\\"); assertTextArray("Escaped escape", "\\\\", "\\"); assertTextArray("Escaped label", "a\\bc", "abc"); 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", "a\\,c", "a,c"); assertTextArray("Escaped label with comma at beginning", "\\,bc", ",bc"); assertTextArray("Escaped label with comma at beginning", "\\,bc", ",bc"); assertTextArray("Escaped label with successive", "\\,\\\\bc", ",\\bc"); assertTextArray("Escaped label with successive", "\\,\\\\bc", ",\\bc"); assertTextArray("Escaped label with escape", "a\\\\c", "a\\c"); assertTextArray("Escaped label with escape", "a\\\\c", "a\\c"); assertTextArray("Escaped @string", "\\@string/empty_string", "@string/empty_string"); } } public void testParseCsvTextMulti() { public void testParseCsvTextMulti() { Loading @@ -83,5 +114,109 @@ public class KeyStylesTests extends AndroidTestCase { "ab\\\\,d\\\\\\,,g\\,i", "ab\\", "d\\,", "g,i"); "ab\\\\,d\\\\\\,,g\\,i", "ab\\", "d\\,", "g,i"); assertTextArray("Multiple labels with comma and escape surrounded by spaces", assertTextArray("Multiple labels with comma and escape surrounded by spaces", " ab\\\\ , d\\\\\\, , g\\,i ", " ab\\ ", " d\\, ", " g,i "); " 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"); } } } }