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