• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.res;
2 
3 import static com.google.common.base.CharMatcher.whitespace;
4 
5 import com.google.common.annotations.VisibleForTesting;
6 import org.robolectric.util.Logger;
7 
8 public class StringResources {
9 
10   private static final int CODE_POINT_LENGTH = 4;
11 
12   /**
13    * Processes String resource values in the same way real Android does, namely:-
14    * 1) Trim leading and trailing whitespace.
15    * 2) Converts code points.
16    * 3) Escapes
17    */
processStringResources(String inputValue)18   public static String processStringResources(String inputValue) {
19     return escape(whitespace().collapseFrom(inputValue.trim(), ' '));
20   }
21 
22   /**
23    * Provides escaping of String resources.
24    *
25    * @see <a
26    *     href="http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling">String
27    *     resource formatting</a>.
28    * @param text Text to escape.
29    * @return Escaped text.
30    */
31   @VisibleForTesting
escape(String text)32   static String escape(String text) {
33     // unwrap double quotes
34     if (text.length() > 1 && text.charAt(0) == '"' && text.charAt(text.length() - 1) == '"') {
35       text = text.substring(1, text.length() - 1);
36     }
37     int i = 0;
38     int length = text.length();
39     StringBuilder result = new StringBuilder(text.length());
40     while (true) {
41       int j = text.indexOf('\\', i);
42       if (j == -1) {
43         result.append(removeUnescapedDoubleQuotes(text.substring(i)));
44         break;
45       }
46       result.append(removeUnescapedDoubleQuotes(text.substring(i, j)));
47       if (j == length - 1) {
48         // dangling backslash
49         break;
50       }
51       boolean isUnicodeEscape = false;
52       char escapeCode = text.charAt(j + 1);
53       switch (escapeCode) {
54         case '\'':
55         case '"':
56         case '\\':
57         case '?':
58         case '@':
59         case '#':
60           result.append(escapeCode);
61           break;
62         case 'n':
63           result.append('\n');
64           break;
65         case 't':
66           result.append('\t');
67           break;
68         case 'u':
69           isUnicodeEscape = true;
70           break;
71         default:
72           Logger.strict("Unsupported string resource escape code '%s'", escapeCode);
73       }
74       if (!isUnicodeEscape) {
75         i = j + 2;
76       } else {
77         j += 2;
78         if (length - j < CODE_POINT_LENGTH) {
79           throw new IllegalArgumentException("Too short code point: \\u" + text.substring(j));
80         }
81         String codePoint = text.substring(j, j + CODE_POINT_LENGTH);
82         result.append(extractCodePoint(codePoint));
83         i = j + CODE_POINT_LENGTH;
84       }
85     }
86     return result.toString();
87   }
88 
89   /**
90    * Converts code points in a given string to actual characters. This method doesn't handle code
91    * points whose char counts are 2. In other words, this method doesn't handle U+10XXXX.
92    */
extractCodePoint(String codePoint)93   private static char[] extractCodePoint(String codePoint) {
94     try {
95       return Character.toChars(Integer.valueOf(codePoint, 16));
96     } catch (IllegalArgumentException e) {
97       // This may be caused by NumberFormatException of Integer.valueOf() or
98       // IllegalArgumentException of Character.toChars().
99       throw new IllegalArgumentException("Invalid code point: \\u" + codePoint, e);
100     }
101   }
102 
removeUnescapedDoubleQuotes(String input)103   private static String removeUnescapedDoubleQuotes(String input) {
104     return input.replaceAll("\"", "");
105   }
106 }
107