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