aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTor Norbye <tnorbye@google.com>2012-09-18 18:08:37 -0700
committerTor Norbye <tnorbye@google.com>2012-09-18 18:08:52 -0700
commit454f0e05d3e202320b0cd7bc176360458e88658e (patch)
tree0763e42a33ff136e89ed56a44fd38f51ec6a698b
parent005c9879219df96beee1d1e28b82b3b0e19bc2ed (diff)
downloadsdk-454f0e05d3e202320b0cd7bc176360458e88658e.zip
sdk-454f0e05d3e202320b0cd7bc176360458e88658e.tar.gz
sdk-454f0e05d3e202320b0cd7bc176360458e88658e.tar.bz2
37497: Templates should escape string literals in resource files
If the user enters an activity title like "Android's Tools" in the new template wizard, an invalid strings.xml file is generated, since the apostrophe is not properly escaped. To fix this, there's a new string conversion method in the template engine, "escapeXmlString", which will perform all the necessary conversions. It also adds two other XML escaping functions: one to escape text to be suitable for XML attribute values, and one to be suitable for XML text values. Finally, when verifying this, I discovered that if I inserted ampersands in the MasterDetail template, I ended up with errors in various places there a filename was derived from the input string. To help make this work better, there's also a new "extractLetters" method which pulls all the characters out of a string (effectively stripping whitespace and punctuation). In addition to the above 4 new string conversion methods, the templates have been updated to use them, and the template format documentation updated. Change-Id: I4d4e854ab78d63bc86b8eb0fb9d92246534615e7
-rw-r--r--common/src/com/android/utils/XmlUtils.java21
-rw-r--r--common/tests/src/com/android/utils/XmlUtilsTest.java5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethod.java40
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethod.java43
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethod.java40
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethod.java45
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethodTest.java50
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethodTest.java66
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethodTest.java46
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethodTest.java46
-rw-r--r--templates/activities/BlankActivity/root/res/values/strings.xml.ftl2
-rw-r--r--templates/activities/FullscreenActivity/root/res/values/strings.xml.ftl2
-rw-r--r--templates/activities/LoginActivity/root/res/values/strings.xml.ftl2
-rw-r--r--templates/activities/MasterDetailFlow/globals.xml.ftl8
-rw-r--r--templates/activities/MasterDetailFlow/recipe.xml.ftl2
-rw-r--r--templates/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl2
-rw-r--r--templates/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl2
-rw-r--r--templates/activities/MasterDetailFlow/root/res/values/strings.xml.ftl4
-rw-r--r--templates/activities/SettingsActivity/root/res/values/strings.xml.ftl2
-rw-r--r--templates/docs/index.html52
-rw-r--r--templates/projects/NewAndroidApplication/root/res/values/strings.xml.ftl2
22 files changed, 471 insertions, 15 deletions
diff --git a/common/src/com/android/utils/XmlUtils.java b/common/src/com/android/utils/XmlUtils.java
index 73e1d11..94b7405 100644
--- a/common/src/com/android/utils/XmlUtils.java
+++ b/common/src/com/android/utils/XmlUtils.java
@@ -182,6 +182,27 @@ public class XmlUtils {
}
/**
+ * Converts the given attribute value to an XML-text-safe value, meaning that
+ * less than and ampersand characters are escaped.
+ *
+ * @param textValue the text value to be escaped
+ * @return the escaped value
+ */
+ @NonNull
+ public static String toXmlTextValue(@NonNull String textValue) {
+ for (int i = 0, n = textValue.length(); i < n; i++) {
+ char c = textValue.charAt(i);
+ if (c == '<' || c == '&') {
+ StringBuilder sb = new StringBuilder(2 * textValue.length());
+ appendXmlTextValue(sb, textValue);
+ return sb.toString();
+ }
+ }
+
+ return textValue;
+ }
+
+ /**
* Appends text to the given {@link StringBuilder} and escapes it as required for a
* DOM attribute node.
*
diff --git a/common/tests/src/com/android/utils/XmlUtilsTest.java b/common/tests/src/com/android/utils/XmlUtilsTest.java
index 6c28451..ea33346 100644
--- a/common/tests/src/com/android/utils/XmlUtilsTest.java
+++ b/common/tests/src/com/android/utils/XmlUtilsTest.java
@@ -16,7 +16,6 @@
package com.android.utils;
import com.android.SdkConstants;
-import com.android.utils.XmlUtils;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
@@ -79,6 +78,10 @@ public class XmlUtilsTest extends TestCase {
assertEquals("&lt;&quot;&apos;>&amp;", sb.toString());
}
+ public void testToXmlTextValue() throws Exception {
+ assertEquals("&lt;\"'>&amp;", XmlUtils.toXmlTextValue("<\"'>&"));
+ }
+
public void testAppendXmlTextValue() throws Exception {
StringBuilder sb = new StringBuilder();
XmlUtils.appendXmlTextValue(sb, "<\"'>&");
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethod.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethod.java
new file mode 100644
index 0000000..21f33b8
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethod.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.eclipse.adt.internal.wizards.templates;
+
+import com.android.utils.XmlUtils;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateMethodModel;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+
+import java.util.List;
+
+/**
+ * Method invoked by FreeMarker to escape a string such that it can be used
+ * as an XML attribute (escaping ', ", & and <).
+ */
+public class FmEscapeXmlAttributeMethod implements TemplateMethodModel {
+ @Override
+ public TemplateModel exec(List args) throws TemplateModelException {
+ if (args.size() != 1) {
+ throw new TemplateModelException("Wrong arguments");
+ }
+ String string = args.get(0).toString();
+ return new SimpleScalar(XmlUtils.toXmlAttributeValue(string));
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethod.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethod.java
new file mode 100644
index 0000000..7e5866e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethod.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.eclipse.adt.internal.wizards.templates;
+
+import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateMethodModel;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+
+import java.util.List;
+
+/**
+ * Method invoked by FreeMarker to escape a string such that it can be placed
+ * as text in a string resource file.
+ * This is similar to {@link FmEscapeXmlTextMethod}, but in addition to escaping
+ * &lt; and &amp; it also escapes characters such as quotes necessary for Android
+ *{@code <string>} elements.
+ */
+public class FmEscapeXmlStringMethod implements TemplateMethodModel {
+ @Override
+ public TemplateModel exec(List args) throws TemplateModelException {
+ if (args.size() != 1) {
+ throw new TemplateModelException("Wrong arguments");
+ }
+ String string = args.get(0).toString();
+ return new SimpleScalar(ExtractStringRefactoring.escapeString(string));
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethod.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethod.java
new file mode 100644
index 0000000..55a4bc8
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethod.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.eclipse.adt.internal.wizards.templates;
+
+import com.android.utils.XmlUtils;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateMethodModel;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+
+import java.util.List;
+
+/**
+ * Method invoked by FreeMarker to escape a string such that it can be used
+ * as XML text (escaping < and &, but not ' and " etc).
+ */
+public class FmEscapeXmlTextMethod implements TemplateMethodModel {
+ @Override
+ public TemplateModel exec(List args) throws TemplateModelException {
+ if (args.size() != 1) {
+ throw new TemplateModelException("Wrong arguments");
+ }
+ String string = args.get(0).toString();
+ return new SimpleScalar(XmlUtils.toXmlTextValue(string));
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethod.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethod.java
new file mode 100644
index 0000000..09fa81c
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethod.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.eclipse.adt.internal.wizards.templates;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateMethodModel;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+
+import java.util.List;
+
+/**
+ * Method invoked by FreeMarker to extract letters from a string; this will remove
+ * any whitespace, punctuation and digits.
+ */
+public class FmExtractLettersMethod implements TemplateMethodModel {
+ @Override
+ public TemplateModel exec(List args) throws TemplateModelException {
+ if (args.size() != 1) {
+ throw new TemplateModelException("Wrong arguments");
+ }
+ String string = args.get(0).toString();
+ StringBuilder sb = new StringBuilder(string.length());
+ for (int i = 0, n = string.length(); i < n; i++) {
+ char c = string.charAt(i);
+ if (Character.isLetter(c)) {
+ sb.append(c);
+ }
+ }
+ return new SimpleScalar(sb.toString());
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java
index cb45522..f2c64ae 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java
@@ -315,6 +315,10 @@ class TemplateHandler {
paramMap.put("activityToLayout", new FmActivityToLayoutMethod()); //$NON-NLS-1$
paramMap.put("layoutToActivity", new FmLayoutToActivityMethod()); //$NON-NLS-1$
paramMap.put("classToResource", new FmClassNameToResourceMethod()); //$NON-NLS-1$
+ paramMap.put("escapeXmlAttribute", new FmEscapeXmlStringMethod()); //$NON-NLS-1$
+ paramMap.put("escapeXmlText", new FmEscapeXmlStringMethod()); //$NON-NLS-1$
+ paramMap.put("escapeXmlString", new FmEscapeXmlStringMethod()); //$NON-NLS-1$
+ paramMap.put("extractLetters", new FmExtractLettersMethod()); //$NON-NLS-1$
// This should be handled better: perhaps declared "required packages" as part of the
// inputs? (It would be better if we could conditionally disable template based
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethodTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethodTest.java
new file mode 100644
index 0000000..eb1e949
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethodTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.eclipse.adt.internal.wizards.templates;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateModelException;
+
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+@SuppressWarnings("javadoc")
+public class FmEscapeXmlAttributeMethodTest extends TestCase {
+ @SuppressWarnings("rawtypes")
+ private void check(String s, String expected) throws TemplateModelException {
+ FmEscapeXmlAttributeMethod method = new FmEscapeXmlAttributeMethod();
+ List list = Collections.singletonList(new SimpleScalar(s));
+ assertEquals(expected, ((SimpleScalar) method.exec(list)).getAsString());
+ }
+
+ public void test1() throws Exception {
+ check("", "");
+ }
+
+ public void test2() throws Exception {
+ check("foo", "foo");
+ }
+
+ public void test3() throws Exception {
+ check("<\"'>&", "&lt;&quot;&apos;>&amp;");
+ }
+
+ public void test4() throws Exception {
+ check("foo>bar", "foo>bar");
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethodTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethodTest.java
new file mode 100644
index 0000000..1a4289a
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethodTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.eclipse.adt.internal.wizards.templates;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateModelException;
+
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+@SuppressWarnings("javadoc")
+public class FmEscapeXmlStringMethodTest extends TestCase {
+ @SuppressWarnings("rawtypes")
+ private void check(String s, String expected) throws TemplateModelException {
+ FmEscapeXmlStringMethod method = new FmEscapeXmlStringMethod();
+ List list = Collections.singletonList(new SimpleScalar(s));
+ assertEquals(expected, ((SimpleScalar) method.exec(list)).getAsString());
+ }
+
+ public void test1() throws Exception {
+ check("", "");
+ }
+
+ public void test2() throws Exception {
+ check("foo", "foo");
+ }
+
+ public void test3() throws Exception {
+ check(" Foo Bar ", "\" Foo Bar \"");
+ }
+
+ public void test4() throws Exception {
+ check("@foo", "\\@foo");
+ }
+
+ public void test5() throws Exception {
+ check("Hello\nWorld", "Hello\\nWorld");
+ }
+
+ public void test6() throws Exception {
+ check("A & B", "A &amp; B");
+ }
+
+ public void test7() throws Exception {
+ check("Foo's Bar", "Foo\\'s Bar");
+ }
+
+ public void test8() throws Exception {
+ check("'\"\\", "\\'\\\"\\\\");
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethodTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethodTest.java
new file mode 100644
index 0000000..c08b834
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethodTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.eclipse.adt.internal.wizards.templates;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateModelException;
+
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+@SuppressWarnings("javadoc")
+public class FmEscapeXmlTextMethodTest extends TestCase {
+ @SuppressWarnings("rawtypes")
+ private void check(String s, String expected) throws TemplateModelException {
+ FmEscapeXmlTextMethod method = new FmEscapeXmlTextMethod();
+ List list = Collections.singletonList(new SimpleScalar(s));
+ assertEquals(expected, ((SimpleScalar) method.exec(list)).getAsString());
+ }
+
+ public void test1() throws Exception {
+ check("", "");
+ }
+
+ public void test2() throws Exception {
+ check("foo", "foo");
+ }
+
+ public void test3() throws Exception {
+ check("<\"'>&", "&lt;\"'>&amp;");
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethodTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethodTest.java
new file mode 100644
index 0000000..b1d3cee
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethodTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.eclipse.adt.internal.wizards.templates;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateModelException;
+
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+@SuppressWarnings("javadoc")
+public class FmExtractLettersMethodTest extends TestCase {
+ @SuppressWarnings("rawtypes")
+ private void check(String s, String expected) throws TemplateModelException {
+ FmExtractLettersMethod method = new FmExtractLettersMethod();
+ List list = Collections.singletonList(new SimpleScalar(s));
+ assertEquals(expected, ((SimpleScalar) method.exec(list)).getAsString());
+ }
+
+ public void test1() throws Exception {
+ check("", "");
+ }
+
+ public void test2() throws Exception {
+ check("foo", "foo");
+ }
+
+ public void test3() throws Exception {
+ check("<\"'>&foo ", "foo");
+ }
+}
diff --git a/templates/activities/BlankActivity/root/res/values/strings.xml.ftl b/templates/activities/BlankActivity/root/res/values/strings.xml.ftl
index 6c636d6..4ba950a 100644
--- a/templates/activities/BlankActivity/root/res/values/strings.xml.ftl
+++ b/templates/activities/BlankActivity/root/res/values/strings.xml.ftl
@@ -1,6 +1,6 @@
<resources>
<#if !isNewProject>
- <string name="title_${activityToLayout(activityClass)}">${activityTitle}</string>
+ <string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
</#if>
<string name="menu_settings">Settings</string>
diff --git a/templates/activities/FullscreenActivity/root/res/values/strings.xml.ftl b/templates/activities/FullscreenActivity/root/res/values/strings.xml.ftl
index 53ff7df..5a43acf 100644
--- a/templates/activities/FullscreenActivity/root/res/values/strings.xml.ftl
+++ b/templates/activities/FullscreenActivity/root/res/values/strings.xml.ftl
@@ -1,7 +1,7 @@
<resources>
<#if !isNewProject>
- <string name="title_${simpleName}">${activityTitle}</string>
+ <string name="title_${simpleName}">${escapeXmlString(activityTitle)}</string>
</#if>
<string name="dummy_button1">Button 1</string>
<string name="dummy_button2">Button 2</string>
diff --git a/templates/activities/LoginActivity/root/res/values/strings.xml.ftl b/templates/activities/LoginActivity/root/res/values/strings.xml.ftl
index c2ad046..18bf85f 100644
--- a/templates/activities/LoginActivity/root/res/values/strings.xml.ftl
+++ b/templates/activities/LoginActivity/root/res/values/strings.xml.ftl
@@ -1,6 +1,6 @@
<resources>
<#if !isNewProject>
- <string name="title_${simpleName}">${activityTitle}</string>
+ <string name="title_${simpleName}">${escapeXmlString(activityTitle)}</string>
</#if>
<!-- Strings related to login -->
diff --git a/templates/activities/MasterDetailFlow/globals.xml.ftl b/templates/activities/MasterDetailFlow/globals.xml.ftl
index 519c081..952e278 100644
--- a/templates/activities/MasterDetailFlow/globals.xml.ftl
+++ b/templates/activities/MasterDetailFlow/globals.xml.ftl
@@ -1,8 +1,8 @@
<?xml version="1.0"?>
<globals>
<global id="srcOut" value="src/${slashedPackageName(packageName)}" />
- <global id="CollectionName" value="${objectKind}List" />
- <global id="collection_name" value="${objectKind?lower_case}_list" />
- <global id="DetailName" value="${objectKind}Detail" />
- <global id="detail_name" value="${objectKind?lower_case}_detail" />
+ <global id="CollectionName" value="${extractLetters(objectKind)}List" />
+ <global id="collection_name" value="${extractLetters(objectKind?lower_case)}_list" />
+ <global id="DetailName" value="${extractLetters(objectKind)}Detail" />
+ <global id="detail_name" value="${extractLetters(objectKind?lower_case)}_detail" />
</globals>
diff --git a/templates/activities/MasterDetailFlow/recipe.xml.ftl b/templates/activities/MasterDetailFlow/recipe.xml.ftl
index 2c1f057..8b09c84 100644
--- a/templates/activities/MasterDetailFlow/recipe.xml.ftl
+++ b/templates/activities/MasterDetailFlow/recipe.xml.ftl
@@ -11,7 +11,7 @@
<instantiate from="res/layout/activity_content_list.xml.ftl"
to="res/layout/activity_${collection_name}.xml" />
<instantiate from="res/layout/activity_content_twopane.xml.ftl"
- to="res/layout/activity_${objectKind?lower_case}_twopane.xml" />
+ to="res/layout/activity_${extractLetters(objectKind?lower_case)}_twopane.xml" />
<instantiate from="res/layout/fragment_content_detail.xml.ftl"
to="res/layout/fragment_${detail_name}.xml" />
diff --git a/templates/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl b/templates/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl
index 3008e2e..97215c3 100644
--- a/templates/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl
+++ b/templates/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl
@@ -6,5 +6,5 @@
For more on layout aliases, see:
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
-->
- <item type="layout" name="activity_${collection_name}">@layout/activity_${objectKind?lower_case}_twopane</item>
+ <item type="layout" name="activity_${collection_name}">@layout/activity_${extractLetters(objectKind?lower_case)}_twopane</item>
</resources>
diff --git a/templates/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl b/templates/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl
index c698e6e..d592404 100644
--- a/templates/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl
+++ b/templates/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl
@@ -7,5 +7,5 @@
For more on layout aliases, see:
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
-->
- <item type="layout" name="activity_${collection_name}">@layout/activity_${objectKind?lower_case}_twopane</item>
+ <item type="layout" name="activity_${collection_name}">@layout/activity_${extractLetters(objectKind?lower_case)}_twopane</item>
</resources>
diff --git a/templates/activities/MasterDetailFlow/root/res/values/strings.xml.ftl b/templates/activities/MasterDetailFlow/root/res/values/strings.xml.ftl
index 8c555ae..ea882bc 100644
--- a/templates/activities/MasterDetailFlow/root/res/values/strings.xml.ftl
+++ b/templates/activities/MasterDetailFlow/root/res/values/strings.xml.ftl
@@ -1,6 +1,6 @@
<resources>
<#if !isNewProject>
- <string name="title_${collection_name}">${objectKindPlural}</string>
+ <string name="title_${collection_name}">${escapeXmlString(objectKindPlural)}</string>
</#if>
- <string name="title_${detail_name}">${objectKind} Detail</string>
+ <string name="title_${detail_name}">${escapeXmlString(objectKind)} Detail</string>
</resources>
diff --git a/templates/activities/SettingsActivity/root/res/values/strings.xml.ftl b/templates/activities/SettingsActivity/root/res/values/strings.xml.ftl
index bf881a3..8dc52ac 100644
--- a/templates/activities/SettingsActivity/root/res/values/strings.xml.ftl
+++ b/templates/activities/SettingsActivity/root/res/values/strings.xml.ftl
@@ -1,6 +1,6 @@
<resources>
<#if !isNewProject>
- <string name="title_${simpleName}">${activityTitle}</string>
+ <string name="title_${simpleName}">${escapeXmlString(activityTitle)}</string>
</#if>
<!-- Strings related to Settings -->
diff --git a/templates/docs/index.html b/templates/docs/index.html
index 0916157..f8e89eb 100644
--- a/templates/docs/index.html
+++ b/templates/docs/index.html
@@ -471,6 +471,58 @@
<h4>See also</h4>
<p><a href="#toc_underscoretocamelcase"><code>underscoreToCamelCase</code></a></p>
+<h3 data-toctitle="escapeXmlAttribute">string <em>escapeXmlAttribute</em>(string)</h3>
+
+<p>This function escapes a string, such as <code>Android's</code> such that it can be used as an XML attribute value: <code>Android&amp;apos;s</code>. In particular, it will escape ', ", &lt; and &amp;.</p>
+
+<h4>Arguments</h4>
+<dl>
+ <dt><code>str</code></dt>
+ <dd>The string to be escaped.</dd>
+</dl>
+
+<h4>See also</h4>
+<p><a href="#toc_escapexmltext"><code>escapeXmlText</code></a></p>
+<p><a href="#toc_escapexmlstring"><code>escapeXmlString</code></a></p>
+
+<h3 data-toctitle="escapeXmlText">string <em>escapeXmlText</em>(string)</h3>
+
+<p>This function escapes a string, such as <code>A &amp; B's</code> such that it can be used as XML text. This means it will escape &lt; and &gt;, but unlike <a href="#toc_escapexmlattribute"><code>escapeXmlAttribute</code></a> it will <b>not</b> escape ' and ". In the preceeding example, it will escape the string to <code>A &amp;amp; B\s</code>. Note that if you plan to use the XML text as the value for a &lt;string&gt; resource value, you should consider using <a href="#toc_escapexmlstring"><code>escapeXmlString</code></a> instead, since it performs additional escapes necessary for string resources.</p>
+
+<h4>Arguments</h4>
+<dl>
+ <dt><code>str</code></dt>
+ <dd>The string to escape to proper XML text.</dd>
+</dl>
+
+<h4>See also</h4>
+<p><a href="#toc_escapexmlattribute"><code>escapeXmlAttribute</code></a></p>
+<p><a href="#toc_escapexmlstring"><code>escapeXmlString</code></a></p>
+
+<h3 data-toctitle="escapeXmlString">string <em>escapeXmlString</em>(string)</h3>
+
+<p>This function escapes a string, such as <code>A &amp; B's</code> such that it is suitable to be inserted in a string resource file as XML text, such as <code>A &amp;amp; B\s</code>. In addition to escaping XML characters like &lt; and &amp;, it also performs additional Android specific escapes, such as escaping apostrophes with a backslash, and so on.</p>
+
+<h4>Arguments</h4>
+<dl>
+ <dt><code>str</code></dt>
+ <dd>The string, e.g. <code>Activity's Title</code> to escape to a proper resource XML value.</dd>
+</dl>
+
+<h4>See also</h4>
+<p><a href="#toc_escapexmlattribute"><code>escapeXmlAttribute</code></a></p>
+<p><a href="#toc_escapexmltext"><code>escapeXmlText</code></a></p>
+
+<h3 data-toctitle="extractLetters">string <em>extractLetters</em>(string)</h3>
+
+<p>This function extracts all the letters from a string, effectively removing any punctuation and whitespace characters.</p>
+
+<h4>Arguments</h4>
+<dl>
+ <dt><code>str</code></dt>
+ <dd>The string to extract letters from</dd>
+</dl>
+
<h3 data-toctitle="classToResource">string <em>classToResource</em>(string)</h3>
<p>This function converts an Android class name, such as <code>FooActivity</code> or <code>FooFragment</code>, to a corresponding resource-friendly identifier string, such as <code>foo</code>, stripping the 'Activity' or 'Fragment' suffix. Currently stripped suffixes are listed below.</p>
diff --git a/templates/projects/NewAndroidApplication/root/res/values/strings.xml.ftl b/templates/projects/NewAndroidApplication/root/res/values/strings.xml.ftl
index 557e5c2..ee03444 100644
--- a/templates/projects/NewAndroidApplication/root/res/values/strings.xml.ftl
+++ b/templates/projects/NewAndroidApplication/root/res/values/strings.xml.ftl
@@ -1,3 +1,3 @@
<resources>
- <string name="app_name">${appTitle}</string>
+ <string name="app_name">${escapeXmlString(appTitle)}</string>
</resources>