aboutsummaryrefslogtreecommitdiffstats
path: root/sdk_common
diff options
context:
space:
mode:
authorTor Norbye <tnorbye@google.com>2013-01-14 11:52:33 -0800
committerTor Norbye <tnorbye@google.com>2013-01-14 12:55:24 -0800
commit7c3a590edd14902d0cca68a435c020ca86331a6c (patch)
tree2526c81563de27a6bf220da65b8769a4748554b4 /sdk_common
parenta737845cdcf443843a3bc112a66f0d560b1e007a (diff)
downloadsdk-7c3a590edd14902d0cca68a435c020ca86331a6c.zip
sdk-7c3a590edd14902d0cca68a435c020ca86331a6c.tar.gz
sdk-7c3a590edd14902d0cca68a435c020ca86331a6c.tar.bz2
39612: Question Mark causes Eclipse Graphical Layout Editor to Freak Out
Handle string values starting with ? and @ even if they do not correspond to actual theme or resource URLs. Also fix the code which handles processing strings read from XML files; apply unescaping rules (for unicode, newlines and tabs, removing quotes, etc). Also make the style warning include the full resource URI (it was only logging the stripped URI). Change-Id: I9b9a87ac4841faeacd1d94a43fa091702e60f4d8
Diffstat (limited to 'sdk_common')
-rw-r--r--sdk_common/src/com/android/ide/common/resources/ResourceResolver.java43
-rw-r--r--sdk_common/src/com/android/ide/common/resources/ValueResourceParser.java304
-rw-r--r--sdk_common/tests/src/com/android/ide/common/resources/ValueResourceParserTest.java148
3 files changed, 421 insertions, 74 deletions
diff --git a/sdk_common/src/com/android/ide/common/resources/ResourceResolver.java b/sdk_common/src/com/android/ide/common/resources/ResourceResolver.java
index 756ea53..219c93f 100644
--- a/sdk_common/src/com/android/ide/common/resources/ResourceResolver.java
+++ b/sdk_common/src/com/android/ide/common/resources/ResourceResolver.java
@@ -189,7 +189,8 @@ public class ResourceResolver extends RenderResources {
if (reference == null) {
return null;
}
- if (reference.startsWith(PREFIX_THEME_REF)) {
+ if (reference.startsWith(PREFIX_THEME_REF)
+ && reference.length() > PREFIX_THEME_REF.length()) {
// no theme? no need to go further!
if (mTheme == null) {
return null;
@@ -198,6 +199,7 @@ public class ResourceResolver extends RenderResources {
boolean frameworkOnly = false;
// eliminate the prefix from the string
+ String originalReference = reference;
if (reference.startsWith(ANDROID_THEME_PREFIX)) {
frameworkOnly = true;
reference = reference.substring(ANDROID_THEME_PREFIX.length());
@@ -207,7 +209,7 @@ public class ResourceResolver extends RenderResources {
// at this point, value can contain type/name (drawable/foo for instance).
// split it to make sure.
- String[] segments = reference.split("\\/");
+ String[] segments = reference.split("/");
// we look for the referenced item name.
String referenceName = null;
@@ -224,6 +226,18 @@ public class ResourceResolver extends RenderResources {
} else {
// it's just an item name.
referenceName = segments[0];
+
+ // Make sure it looks like a resource name; if not, it could just be a string
+ // which starts with a ?
+ if (!Character.isJavaIdentifierStart(referenceName.charAt(0))) {
+ return null;
+ }
+ for (int i = 1, n = referenceName.length(); i < n; i++) {
+ char c = referenceName.charAt(i);
+ if (!Character.isJavaIdentifierPart(c) && c != '.') {
+ return null;
+ }
+ }
}
// now we look for android: in the referenceName in order to support format
@@ -241,7 +255,7 @@ public class ResourceResolver extends RenderResources {
mLogger.warning(LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR,
String.format("Couldn't find theme resource %1$s for the current theme",
reference),
- new ResourceValue(ResourceType.ATTR, referenceName, frameworkOnly));
+ new ResourceValue(ResourceType.ATTR, originalReference, frameworkOnly));
}
return item;
@@ -262,16 +276,17 @@ public class ResourceResolver extends RenderResources {
}
// at this point, value contains type/[android:]name (drawable/foo for instance)
- String[] segments = reference.split("\\/");
- if (segments.length <= 1) {
+ String[] segments = reference.split("/");
+ if (segments.length != 2) {
return null;
}
// now we look for android: in the resource name in order to support format
// such as: @drawable/android:name
- if (segments[1].startsWith(PREFIX_ANDROID)) {
+ String referenceName = segments[1];
+ if (referenceName.startsWith(PREFIX_ANDROID)) {
frameworkOnly = true;
- segments[1] = segments[1].substring(PREFIX_ANDROID.length());
+ referenceName = referenceName.substring(PREFIX_ANDROID.length());
}
ResourceType type = ResourceType.getEnum(segments[0]);
@@ -281,7 +296,19 @@ public class ResourceResolver extends RenderResources {
return null;
}
- return findResValue(type, segments[1],
+ // Make sure it looks like a resource name; if not, it could just be a string
+ // which starts with a ?
+ if (!Character.isJavaIdentifierStart(referenceName.charAt(0))) {
+ return null;
+ }
+ for (int i = 1, n = referenceName.length(); i < n; i++) {
+ char c = referenceName.charAt(i);
+ if (!Character.isJavaIdentifierPart(c) && c != '.') {
+ return null;
+ }
+ }
+
+ return findResValue(type, referenceName,
forceFrameworkOnly ? true :frameworkOnly);
}
diff --git a/sdk_common/src/com/android/ide/common/resources/ValueResourceParser.java b/sdk_common/src/com/android/ide/common/resources/ValueResourceParser.java
index aabfd35..aa1f4b8 100644
--- a/sdk_common/src/com/android/ide/common/resources/ValueResourceParser.java
+++ b/sdk_common/src/com/android/ide/common/resources/ValueResourceParser.java
@@ -16,6 +16,11 @@
package com.android.ide.common.resources;
+import static com.android.SdkConstants.AMP_ENTITY;
+import static com.android.SdkConstants.LT_ENTITY;
+
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
import com.android.ide.common.rendering.api.AttrResourceValue;
import com.android.ide.common.rendering.api.DeclareStyleableResourceValue;
import com.android.ide.common.rendering.api.ResourceValue;
@@ -64,7 +69,7 @@ public final class ValueResourceParser extends DefaultHandler {
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (mCurrentValue != null) {
- mCurrentValue.setValue(trimXmlWhitespaces(mCurrentValue.getValue()));
+ mCurrentValue.setValue(unescapeResourceString(mCurrentValue.getValue(), false, true));
}
if (inResources && qName.equals(NODE_RESOURCES)) {
@@ -213,96 +218,263 @@ public final class ValueResourceParser extends DefaultHandler {
}
}
- public static String trimXmlWhitespaces(String value) {
- if (value == null) {
+ /**
+ * Replaces escapes in an XML resource string with the actual characters,
+ * performing unicode substitutions (replacing any {@code \\uNNNN} references in the
+ * given string with the corresponding unicode characters), etc.
+ *
+ *
+ *
+ * @param s the string to unescape
+ * @param escapeEntities XML entities
+ * @param trim whether surrounding space and quotes should be trimmed
+ * @return the string with the escape characters removed and expanded
+ */
+ @Nullable
+ public static String unescapeResourceString(
+ @Nullable String s,
+ boolean escapeEntities, boolean trim) {
+ if (s == null) {
return null;
}
- // look for carriage return and replace all whitespace around it by just 1 space.
- int index;
-
- while ((index = value.indexOf('\n')) != -1) {
- // look for whitespace on each side
- int left = index - 1;
- while (left >= 0) {
- if (Character.isWhitespace(value.charAt(left))) {
- left--;
- } else {
+ // Trim space surrounding optional quotes
+ int i = 0;
+ int n = s.length();
+ if (trim) {
+ while (i < n) {
+ char c = s.charAt(i);
+ if (!Character.isWhitespace(c)) {
+ break;
+ }
+ i++;
+ }
+ while (n > i) {
+ char c = s.charAt(n - 1);
+ if (!Character.isWhitespace(c)) {
+ //See if this was a \, and if so, see whether it was escaped
+ if (n < s.length() && isEscaped(s, n)) {
+ n++;
+ }
break;
}
+ n--;
}
- int right = index + 1;
- int count = value.length();
- while (right < count) {
- if (Character.isWhitespace(value.charAt(right))) {
- right++;
- } else {
+ // Trim surrounding quotes. Note that there can be *any* number of these, and
+ // the left side and right side do not have to match; e.g. you can have
+ // """"f"" => f
+ int quoteStart = i;
+ int quoteEnd = n;
+ while (i < n) {
+ char c = s.charAt(i);
+ if (c != '"') {
+ break;
+ }
+ i++;
+ }
+ // Searching backwards is slightly more complicated; make sure we don't trim
+ // quotes that have been escaped.
+ while (n > i) {
+ char c = s.charAt(n - 1);
+ if (c != '"') {
+ if (n < s.length() && isEscaped(s, n)) {
+ n++;
+ }
break;
}
+ n--;
+ }
+ if (n == i) {
+ return ""; //$NON-NLS-1$
}
- // remove all between left and right (non inclusive) and replace by a single space.
- String leftString = null;
- if (left >= 0) {
- leftString = value.substring(0, left + 1);
+ // Only trim leading spaces if we didn't already process a leading quote:
+ if (i == quoteStart) {
+ while (i < n) {
+ char c = s.charAt(i);
+ if (!Character.isWhitespace(c)) {
+ break;
+ }
+ i++;
+ }
+ }
+ // Only trim trailing spaces if we didn't already process a trailing quote:
+ if (n == quoteEnd) {
+ while (n > i) {
+ char c = s.charAt(n - 1);
+ if (!Character.isWhitespace(c)) {
+ //See if this was a \, and if so, see whether it was escaped
+ if (n < s.length() && isEscaped(s, n)) {
+ n++;
+ }
+ break;
+ }
+ n--;
+ }
}
- String rightString = null;
- if (right < count) {
- rightString = value.substring(right);
+ if (n == i) {
+ return ""; //$NON-NLS-1$
}
+ }
+
+ // If no surrounding whitespace and no escape characters, no need to do any
+ // more work
+ if (i == 0 && n == s.length() && s.indexOf('\\') == -1
+ && (!escapeEntities || s.indexOf('&') == -1)) {
+ return s;
+ }
- if (leftString != null) {
- value = leftString;
- if (rightString != null) {
- value += " " + rightString;
+ StringBuilder sb = new StringBuilder(n - i);
+ for (; i < n; i++) {
+ char c = s.charAt(i);
+ if (c == '\\' && i < n - 1) {
+ char next = s.charAt(i + 1);
+ // Unicode escapes
+ if (next == 'u' && i < n - 5) { // case sensitive
+ String hex = s.substring(i + 2, i + 6);
+ try {
+ int unicodeValue = Integer.parseInt(hex, 16);
+ sb.append((char) unicodeValue);
+ i += 5;
+ continue;
+ } catch (NumberFormatException e) {
+ // Invalid escape: Just proceed to literally transcribe it
+ sb.append(c);
+ }
+ } else if (next == 'n') {
+ sb.append('\n');
+ i++;
+ } else if (next == 't') {
+ sb.append('\t');
+ i++;
+ } else {
+ sb.append(next);
+ i++;
+ continue;
}
} else {
- value = rightString != null ? rightString : "";
+ if (c == '&' && escapeEntities) {
+ if (s.regionMatches(true, i, LT_ENTITY, 0, LT_ENTITY.length())) {
+ sb.append('<');
+ i += LT_ENTITY.length() - 1;
+ continue;
+ } else if (s.regionMatches(true, i, AMP_ENTITY, 0, AMP_ENTITY.length())) {
+ sb.append('&');
+ i += AMP_ENTITY.length() - 1;
+ continue;
+ }
+ }
+ sb.append(c);
}
}
+ s = sb.toString();
- // now we un-escape the string
- int length = value.length();
- char[] buffer = value.toCharArray();
+ return s;
+ }
- for (int i = 0 ; i < length ; i++) {
- if (buffer[i] == '\\' && i + 1 < length) {
- if (buffer[i+1] == 'u') {
- if (i + 5 < length) {
- // this is unicode char \u1234
- int unicodeChar = Integer.parseInt(new String(buffer, i+2, 4), 16);
+ /**
+ * Returns true if the character at the given offset in the string is escaped
+ * (the previous character is a \, and that character isn't itself an escaped \)
+ *
+ * @param s the string
+ * @param index the index of the character in the string to check
+ * @return true if the character is escaped
+ */
+ @VisibleForTesting
+ static boolean isEscaped(String s, int index) {
+ if (index == 0 || index == s.length()) {
+ return false;
+ }
+ int prevPos = index - 1;
+ char prev = s.charAt(prevPos);
+ if (prev != '\\') {
+ return false;
+ }
+ // The character *may* be escaped; not sure if the \ we ran into is
+ // an escape character, or an escaped backslash; we have to search backwards
+ // to be certain.
+ int j = prevPos - 1;
+ while (j >= 0) {
+ if (s.charAt(j) != '\\') {
+ break;
+ }
+ j--;
+ }
+ // If we passed an odd number of \'s, the space is escaped
+ return (prevPos - j) % 2 == 1;
+ }
- // put the unicode char at the location of the \
- buffer[i] = (char)unicodeChar;
+ /**
+ * Escape a string value to be placed in a string resource file such that it complies with
+ * the escaping rules described here:
+ * http://developer.android.com/guide/topics/resources/string-resource.html
+ * More examples of the escaping rules can be found here:
+ * http://androidcookbook.com/Recipe.seam?recipeId=2219&recipeFrom=ViewTOC
+ * This method assumes that the String is not escaped already.
+ *
+ * Rules:
+ * <ul>
+ * <li>Double quotes are needed if string starts or ends with at least one space.
+ * <li>{@code @, ?} at beginning of string have to be escaped with a backslash.
+ * <li>{@code ', ", \} have to be escaped with a backslash.
+ * <li>{@code <, >, &} have to be replaced by their predefined xml entity.
+ * <li>{@code \n, \t} have to be replaced by a backslash and the appropriate character.
+ * </ul>
+ * @param s the string to be escaped
+ * @return the escaped string as it would appear in the XML text in a values file
+ */
+ public static String escapeResourceString(String s) {
+ int n = s.length();
+ if (n == 0) {
+ return "";
+ }
- // offset the rest of the buffer since we go from 6 to 1 char
- if (i + 6 < buffer.length) {
- System.arraycopy(buffer, i+6, buffer, i+1, length - i - 6);
- }
- length -= 5;
- }
- } else {
- if (buffer[i+1] == 'n') {
- // replace the 'n' char with \n
- buffer[i+1] = '\n';
- }
+ StringBuilder sb = new StringBuilder(s.length() * 2);
+ boolean hasSpace = s.charAt(0) == ' ' || s.charAt(n - 1) == ' ';
- // offset the buffer to erase the \
- System.arraycopy(buffer, i+1, buffer, i, length - i - 1);
- length--;
- }
- } else if (buffer[i] == '"') {
- // if the " was escaped it would have been processed above.
- // offset the buffer to erase the "
- System.arraycopy(buffer, i+1, buffer, i, length - i - 1);
- length--;
-
- // unlike when unescaping, we want to process the next char too
- i--;
+ if (hasSpace) {
+ sb.append('"');
+ } else if (s.charAt(0) == '@' || s.charAt(0) == '?') {
+ sb.append('\\');
+ }
+
+ for (int i = 0; i < n; ++i) {
+ char c = s.charAt(i);
+ switch (c) {
+ case '\'':
+ if (!hasSpace) {
+ sb.append('\\');
+ }
+ sb.append(c);
+ break;
+ case '"':
+ case '\\':
+ sb.append('\\');
+ sb.append(c);
+ break;
+ case '<':
+ sb.append(LT_ENTITY);
+ break;
+ case '&':
+ sb.append(AMP_ENTITY);
+ break;
+ case '\n':
+ sb.append("\\n"); //$NON-NLS-1$
+ break;
+ case '\t':
+ sb.append("\\t"); //$NON-NLS-1$
+ break;
+ default:
+ sb.append(c);
+ break;
}
}
- return new String(buffer, 0, length);
+ if (hasSpace) {
+ sb.append('"');
+ }
+
+ return sb.toString();
}
}
diff --git a/sdk_common/tests/src/com/android/ide/common/resources/ValueResourceParserTest.java b/sdk_common/tests/src/com/android/ide/common/resources/ValueResourceParserTest.java
new file mode 100644
index 0000000..aed6060
--- /dev/null
+++ b/sdk_common/tests/src/com/android/ide/common/resources/ValueResourceParserTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources;
+
+import static com.android.ide.common.resources.ValueResourceParser.escapeResourceString;
+import static com.android.ide.common.resources.ValueResourceParser.isEscaped;
+import static com.android.ide.common.resources.ValueResourceParser.unescapeResourceString;
+
+import junit.framework.TestCase;
+
+public class ValueResourceParserTest extends TestCase {
+
+ public void testEscapeStringShouldEscapeXmlSpecialCharacters() throws Exception {
+ assertEquals("&lt;", escapeResourceString("<"));
+ assertEquals("&amp;", escapeResourceString("&"));
+ }
+
+ public void testEscapeStringShouldEscapeQuotes() throws Exception {
+ assertEquals("\\'", escapeResourceString("'"));
+ assertEquals("\\\"", escapeResourceString("\""));
+ assertEquals("\" ' \"", escapeResourceString(" ' "));
+ }
+
+ public void testEscapeStringShouldPreserveWhitespace() throws Exception {
+ assertEquals("\"at end \"", escapeResourceString("at end "));
+ assertEquals("\" at begin\"", escapeResourceString(" at begin"));
+ }
+
+ public void testEscapeStringShouldEscapeAtSignAndQuestionMarkOnlyAtBeginning()
+ throws Exception {
+ assertEquals("\\@text", escapeResourceString("@text"));
+ assertEquals("a@text", escapeResourceString("a@text"));
+ assertEquals("\\?text", escapeResourceString("?text"));
+ assertEquals("a?text", escapeResourceString("a?text"));
+ assertEquals("\" ?text\"", escapeResourceString(" ?text"));
+ }
+
+ public void testEscapeStringShouldEscapeJavaEscapeSequences() throws Exception {
+ assertEquals("\\n", escapeResourceString("\n"));
+ assertEquals("\\t", escapeResourceString("\t"));
+ assertEquals("\\\\", escapeResourceString("\\"));
+ }
+
+ public void testTrim() throws Exception {
+ assertEquals("", unescapeResourceString("", false, true));
+ assertEquals("", unescapeResourceString(" \n ", false, true));
+ assertEquals("test", unescapeResourceString(" test ", false, true));
+ assertEquals(" test ", unescapeResourceString("\" test \"", false, true));
+ assertEquals("test", unescapeResourceString("\n\t test \t\n ", false, true));
+
+ assertEquals("test\n", unescapeResourceString(" test\\n ", false, true));
+ assertEquals(" test\n ", unescapeResourceString("\" test\\n \"", false, true));
+ assertEquals("te\\st", unescapeResourceString("\n\t te\\\\st \t\n ", false, true));
+ assertEquals("te\\st", unescapeResourceString(" te\\\\st ", false, true));
+ assertEquals("test", unescapeResourceString("\"\"\"test\"\" ", false, true));
+ assertEquals("\"test\"", unescapeResourceString("\"\"\\\"test\\\"\" ", false, true));
+ assertEquals("test ", unescapeResourceString("test\\ ", false, true));
+ assertEquals("\\\\\\", unescapeResourceString("\\\\\\\\\\\\ ", false, true));
+ assertEquals("\\\\\\ ", unescapeResourceString("\\\\\\\\\\\\\\ ", false, true));
+ }
+
+ public void testNoTrim() throws Exception {
+ assertEquals("", unescapeResourceString("", false, false));
+ assertEquals(" \n ", unescapeResourceString(" \n ", false, false));
+ assertEquals(" test ", unescapeResourceString(" test ", false, false));
+ assertEquals("\" test \"", unescapeResourceString("\" test \"", false, false));
+ assertEquals("\n\t test \t\n ", unescapeResourceString("\n\t test \t\n ", false, false));
+
+ assertEquals(" test\n ", unescapeResourceString(" test\\n ", false, false));
+ assertEquals("\" test\n \"", unescapeResourceString("\" test\\n \"", false, false));
+ assertEquals("\n\t te\\st \t\n ", unescapeResourceString("\n\t te\\\\st \t\n ", false, false));
+ assertEquals(" te\\st ", unescapeResourceString(" te\\\\st ", false, false));
+ assertEquals("\"\"\"test\"\" ", unescapeResourceString("\"\"\"test\"\" ", false, false));
+ assertEquals("\"\"\"test\"\" ", unescapeResourceString("\"\"\\\"test\\\"\" ", false, false));
+ assertEquals("test ", unescapeResourceString("test\\ ", false, false));
+ assertEquals("\\\\\\ ", unescapeResourceString("\\\\\\\\\\\\ ", false, false));
+ assertEquals("\\\\\\ ", unescapeResourceString("\\\\\\\\\\\\\\ ", false, false));
+ }
+
+ public void testUnescapeStringShouldUnescapeXmlSpecialCharacters() throws Exception {
+ assertEquals("&lt;", unescapeResourceString("&lt;", false, true));
+ assertEquals("<", unescapeResourceString("&lt;", true, true));
+ assertEquals("<", unescapeResourceString(" &lt; ", true, true));
+ assertEquals("&amp;", unescapeResourceString("&amp;", false, true));
+ assertEquals("&", unescapeResourceString("&amp;", true, true));
+ assertEquals("&", unescapeResourceString(" &amp; ", true, true));
+ assertEquals("!<", unescapeResourceString("!&lt;", true, true));
+ }
+
+ public void testUnescapeStringShouldUnescapeQuotes() throws Exception {
+ assertEquals("'", unescapeResourceString("\\'", false, true));
+ assertEquals("\"", unescapeResourceString("\\\"", false, true));
+ assertEquals(" ' ", unescapeResourceString("\" ' \"", false, true));
+ }
+
+ public void testUnescapeStringShouldPreserveWhitespace() throws Exception {
+ assertEquals("at end ", unescapeResourceString("\"at end \"", false, true));
+ assertEquals(" at begin", unescapeResourceString("\" at begin\"", false, true));
+ }
+
+ public void testUnescapeStringShouldUnescapeAtSignAndQuestionMarkOnlyAtBeginning()
+ throws Exception {
+ assertEquals("@text", unescapeResourceString("\\@text", false, true));
+ assertEquals("a@text", unescapeResourceString("a@text", false, true));
+ assertEquals("?text", unescapeResourceString("\\?text", false, true));
+ assertEquals("a?text", unescapeResourceString("a?text", false, true));
+ assertEquals(" ?text", unescapeResourceString("\" ?text\"", false, true));
+ }
+
+ public void testUnescapeStringShouldUnescapeJavaUnescapeSequences() throws Exception {
+ assertEquals("\n", unescapeResourceString("\\n", false, true));
+ assertEquals("\t", unescapeResourceString("\\t", false, true));
+ assertEquals("\\", unescapeResourceString("\\\\", false, true));
+ }
+
+ public void testIsEscaped() throws Exception {
+ assertFalse(isEscaped("", 0));
+ assertFalse(isEscaped(" ", 0));
+ assertFalse(isEscaped(" ", 1));
+ assertFalse(isEscaped("x\\y ", 0));
+ assertFalse(isEscaped("x\\y ", 1));
+ assertTrue(isEscaped("x\\y ", 2));
+ assertFalse(isEscaped("x\\y ", 3));
+ assertFalse(isEscaped("x\\\\y ", 0));
+ assertFalse(isEscaped("x\\\\y ", 1));
+ assertTrue(isEscaped("x\\\\y ", 2));
+ assertFalse(isEscaped("x\\\\y ", 3));
+ assertFalse(isEscaped("\\\\\\\\y ", 0));
+ assertTrue(isEscaped( "\\\\\\\\y ", 1));
+ assertFalse(isEscaped("\\\\\\\\y ", 2));
+ assertTrue(isEscaped( "\\\\\\\\y ", 3));
+ assertFalse(isEscaped("\\\\\\\\y ", 4));
+ }
+}