• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package com.android.inputmethod.keyboard.internal;
18 
19 import android.content.res.Resources;
20 import android.text.TextUtils;
21 import android.util.Log;
22 
23 import com.android.inputmethod.keyboard.Keyboard;
24 import com.android.inputmethod.latin.R;
25 
26 import java.util.ArrayList;
27 
28 /**
29  * String parser of moreKeys attribute of Key.
30  * The string is comma separated texts each of which represents one "more key".
31  * Each "more key" specification is one of the following:
32  * - A single letter (Letter)
33  * - Label optionally followed by keyOutputText or code (keyLabel|keyOutputText).
34  * - Icon followed by keyOutputText or code (@icon/icon_number|@integer/key_code)
35  * Special character, comma ',' backslash '\', and bar '|' can be escaped by '\'
36  * character.
37  * Note that the character '@' and '\' are also parsed by XML parser and CSV parser as well.
38  * See {@link KeyboardIconsSet} about icon_number.
39  */
40 public class MoreKeySpecParser {
41     private static final String TAG = MoreKeySpecParser.class.getSimpleName();
42 
43     private static final char ESCAPE = '\\';
44     private static final String LABEL_END = "|";
45     private static final String PREFIX_AT = "@";
46     private static final String PREFIX_ICON = PREFIX_AT + "icon/";
47     private static final String PREFIX_CODE = PREFIX_AT + "integer/";
48 
MoreKeySpecParser()49     private MoreKeySpecParser() {
50         // Intentional empty constructor for utility class.
51     }
52 
hasIcon(String moreKeySpec)53     private static boolean hasIcon(String moreKeySpec) {
54         if (moreKeySpec.startsWith(PREFIX_ICON)) {
55             final int end = indexOfLabelEnd(moreKeySpec, 0);
56             if (end > 0)
57                 return true;
58             throw new MoreKeySpecParserError("outputText or code not specified: " + moreKeySpec);
59         }
60         return false;
61     }
62 
hasCode(String moreKeySpec)63     private static boolean hasCode(String moreKeySpec) {
64         final int end = indexOfLabelEnd(moreKeySpec, 0);
65         if (end > 0 && end + 1 < moreKeySpec.length()
66                 && moreKeySpec.substring(end + 1).startsWith(PREFIX_CODE)) {
67             return true;
68         }
69         return false;
70     }
71 
parseEscape(String text)72     private static String parseEscape(String text) {
73         if (text.indexOf(ESCAPE) < 0)
74             return text;
75         final int length = text.length();
76         final StringBuilder sb = new StringBuilder();
77         for (int pos = 0; pos < length; pos++) {
78             final char c = text.charAt(pos);
79             if (c == ESCAPE && pos + 1 < length) {
80                 sb.append(text.charAt(++pos));
81             } else {
82                 sb.append(c);
83             }
84         }
85         return sb.toString();
86     }
87 
indexOfLabelEnd(String moreKeySpec, int start)88     private static int indexOfLabelEnd(String moreKeySpec, int start) {
89         if (moreKeySpec.indexOf(ESCAPE, start) < 0) {
90             final int end = moreKeySpec.indexOf(LABEL_END, start);
91             if (end == 0)
92                 throw new MoreKeySpecParserError(LABEL_END + " at " + start + ": " + moreKeySpec);
93             return end;
94         }
95         final int length = moreKeySpec.length();
96         for (int pos = start; pos < length; pos++) {
97             final char c = moreKeySpec.charAt(pos);
98             if (c == ESCAPE && pos + 1 < length) {
99                 pos++;
100             } else if (moreKeySpec.startsWith(LABEL_END, pos)) {
101                 return pos;
102             }
103         }
104         return -1;
105     }
106 
getLabel(String moreKeySpec)107     public static String getLabel(String moreKeySpec) {
108         if (hasIcon(moreKeySpec))
109             return null;
110         final int end = indexOfLabelEnd(moreKeySpec, 0);
111         final String label = (end > 0) ? parseEscape(moreKeySpec.substring(0, end))
112                 : parseEscape(moreKeySpec);
113         if (TextUtils.isEmpty(label))
114             throw new MoreKeySpecParserError("Empty label: " + moreKeySpec);
115         return label;
116     }
117 
getOutputText(String moreKeySpec)118     public static String getOutputText(String moreKeySpec) {
119         if (hasCode(moreKeySpec))
120             return null;
121         final int end = indexOfLabelEnd(moreKeySpec, 0);
122         if (end > 0) {
123             if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0)
124                     throw new MoreKeySpecParserError("Multiple " + LABEL_END + ": "
125                             + moreKeySpec);
126             final String outputText = parseEscape(moreKeySpec.substring(end + LABEL_END.length()));
127             if (!TextUtils.isEmpty(outputText))
128                 return outputText;
129             throw new MoreKeySpecParserError("Empty outputText: " + moreKeySpec);
130         }
131         final String label = getLabel(moreKeySpec);
132         if (label == null)
133             throw new MoreKeySpecParserError("Empty label: " + moreKeySpec);
134         // Code is automatically generated for one letter label. See {@link getCode()}.
135         if (label.length() == 1)
136             return null;
137         return label;
138     }
139 
getCode(Resources res, String moreKeySpec)140     public static int getCode(Resources res, String moreKeySpec) {
141         if (hasCode(moreKeySpec)) {
142             final int end = indexOfLabelEnd(moreKeySpec, 0);
143             if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0)
144                 throw new MoreKeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec);
145             final int resId = getResourceId(res,
146                     moreKeySpec.substring(end + LABEL_END.length() + PREFIX_AT.length()));
147             final int code = res.getInteger(resId);
148             return code;
149         }
150         if (indexOfLabelEnd(moreKeySpec, 0) > 0)
151             return Keyboard.CODE_DUMMY;
152         final String label = getLabel(moreKeySpec);
153         // Code is automatically generated for one letter label.
154         if (label != null && label.length() == 1)
155             return label.charAt(0);
156         return Keyboard.CODE_DUMMY;
157     }
158 
getIconId(String moreKeySpec)159     public static int getIconId(String moreKeySpec) {
160         if (hasIcon(moreKeySpec)) {
161             int end = moreKeySpec.indexOf(LABEL_END, PREFIX_ICON.length() + 1);
162             final String iconId = moreKeySpec.substring(PREFIX_ICON.length(), end);
163             try {
164                 return Integer.valueOf(iconId);
165             } catch (NumberFormatException e) {
166                 Log.w(TAG, "illegal icon id specified: " + iconId);
167                 return KeyboardIconsSet.ICON_UNDEFINED;
168             }
169         }
170         return KeyboardIconsSet.ICON_UNDEFINED;
171     }
172 
getResourceId(Resources res, String name)173     private static int getResourceId(Resources res, String name) {
174         String packageName = res.getResourcePackageName(R.string.english_ime_name);
175         int resId = res.getIdentifier(name, null, packageName);
176         if (resId == 0)
177             throw new MoreKeySpecParserError("Unknown resource: " + name);
178         return resId;
179     }
180 
181     @SuppressWarnings("serial")
182     public static class MoreKeySpecParserError extends RuntimeException {
MoreKeySpecParserError(String message)183         public MoreKeySpecParserError(String message) {
184             super(message);
185         }
186     }
187 
188     public interface CodeFilter {
shouldFilterOut(int code)189         public boolean shouldFilterOut(int code);
190     }
191 
192     public static final CodeFilter DIGIT_FILTER = new CodeFilter() {
193         @Override
194         public boolean shouldFilterOut(int code) {
195             return Character.isDigit(code);
196         }
197     };
198 
filterOut(Resources res, CharSequence[] moreKeys, CodeFilter filter)199     public static CharSequence[] filterOut(Resources res, CharSequence[] moreKeys,
200             CodeFilter filter) {
201         if (moreKeys == null || moreKeys.length < 1) {
202             return null;
203         }
204         if (moreKeys.length == 1
205                 && filter.shouldFilterOut(getCode(res, moreKeys[0].toString()))) {
206             return null;
207         }
208         ArrayList<CharSequence> filtered = null;
209         for (int i = 0; i < moreKeys.length; i++) {
210             final CharSequence moreKeySpec = moreKeys[i];
211             if (filter.shouldFilterOut(getCode(res, moreKeySpec.toString()))) {
212                 if (filtered == null) {
213                     filtered = new ArrayList<CharSequence>();
214                     for (int j = 0; j < i; j++) {
215                         filtered.add(moreKeys[j]);
216                     }
217                 }
218             } else if (filtered != null) {
219                 filtered.add(moreKeySpec);
220             }
221         }
222         if (filtered == null) {
223             return moreKeys;
224         }
225         if (filtered.size() == 0) {
226             return null;
227         }
228         return filtered.toArray(new CharSequence[filtered.size()]);
229     }
230 }
231