Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 54c2dd67 authored by Tadashi G. Takaoka's avatar Tadashi G. Takaoka Committed by Android (Google) Code Review
Browse files

Merge "Support @string reference in moreKeys attribute"

parents 7c177e82 e54a4005
Loading
Loading
Loading
Loading
+24 −13
Original line number Diff line number Diff line
@@ -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<String, DeclaredKeyStyle> mStyles =
            new HashMap<String, DeclaredKeyStyle>();
    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<String> 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<String>();
                if (list == null) {
                    list = new ArrayList<String>();
                }
                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()]);
+12 −13
Original line number Diff line number Diff line
@@ -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);
+55 −0
Original line number Diff line number Diff line
@@ -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;
    }
}
+50 −0
Original line number 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>
+140 −5
Original line number Diff line number Diff line
@@ -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");
    }
}