• 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 as described
24    * [here](http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling).
25    *
26    * @param text Text to escape.
27    * @return Escaped text.
28    */
29   @VisibleForTesting
escape(String text)30   static String escape(String text) {
31     // unwrap double quotes
32     if (text.length() > 1 && text.charAt(0) == '"' && text.charAt(text.length() - 1) == '"') {
33       text = text.substring(1, text.length() - 1);
34     }
35     int i = 0;
36     int length = text.length();
37     StringBuilder result = new StringBuilder(text.length());
38     while (true) {
39       int j = text.indexOf('\\', i);
40       if (j == -1) {
41         result.append(removeUnescapedDoubleQuotes(text.substring(i)));
42         break;
43       }
44       result.append(removeUnescapedDoubleQuotes(text.substring(i, j)));
45       if (j == length - 1) {
46         // dangling backslash
47         break;
48       }
49       boolean isUnicodeEscape = false;
50       char escapeCode = text.charAt(j + 1);
51       switch (escapeCode) {
52         case '\'':
53         case '"':
54         case '\\':
55         case '?':
56         case '@':
57         case '#':
58           result.append(escapeCode);
59           break;
60         case 'n':
61           result.append('\n');
62           break;
63         case 't':
64           result.append('\t');
65           break;
66         case 'u':
67           isUnicodeEscape = true;
68           break;
69         default:
70           Logger.strict("Unsupported string resource escape code '%s'", escapeCode);
71       }
72       if (!isUnicodeEscape) {
73         i = j + 2;
74       } else {
75         j += 2;
76         if (length - j < CODE_POINT_LENGTH) {
77           throw new IllegalArgumentException("Too short code point: \\u" + text.substring(j));
78         }
79         String codePoint = text.substring(j, j + CODE_POINT_LENGTH);
80         result.append(extractCodePoint(codePoint));
81         i = j + CODE_POINT_LENGTH;
82       }
83     }
84     return result.toString();
85   }
86 
87   /**
88    * Converts code points in a given string to actual characters. This method doesn't handle code
89    * points whose char counts are 2. In other words, this method doesn't handle U+10XXXX.
90    */
extractCodePoint(String codePoint)91   private static char[] extractCodePoint(String codePoint) {
92     try {
93       return Character.toChars(Integer.valueOf(codePoint, 16));
94     } catch (IllegalArgumentException e) {
95       // This may be caused by NumberFormatException of Integer.valueOf() or
96       // IllegalArgumentException of Character.toChars().
97       throw new IllegalArgumentException("Invalid code point: \\u" + codePoint, e);
98     }
99   }
100 
removeUnescapedDoubleQuotes(String input)101   private static String removeUnescapedDoubleQuotes(String input) {
102     return input.replaceAll("\"", "");
103   }
104 }
105