aboutsummaryrefslogtreecommitdiffstats
path: root/common/src/com
diff options
context:
space:
mode:
Diffstat (limited to 'common/src/com')
-rw-r--r--common/src/com/android/util/XmlUtils.java253
1 files changed, 253 insertions, 0 deletions
diff --git a/common/src/com/android/util/XmlUtils.java b/common/src/com/android/util/XmlUtils.java
new file mode 100644
index 0000000..11db031
--- /dev/null
+++ b/common/src/com/android/util/XmlUtils.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2012 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.util;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+import java.util.HashSet;
+
+/** XML Utilities */
+public class XmlUtils {
+ /** Namespace used in XML files for Android attributes */
+ public static final String ANDROID_URI =
+ "http://schemas.android.com/apk/res/android"; //$NON-NLS-1$
+ /** Namespace used in XML files for Android Tooling attributes */
+ public static final String TOOLS_URI =
+ "http://schemas.android.com/tools"; //$NON-NLS-1$
+ /** URI of the reserved "xmlns" prefix */
+ public final static String XMLNS_URI = "http://www.w3.org/2000/xmlns/"; //$NON-NLS-1$
+ /** The "xmlns" attribute name */
+ public static final String XMLNS = "xmlns"; //$NON-NLS-1$
+ /** The default prefix used for the {@link #XMLNS_URI} */
+ public static final String XMLNS_PREFIX = "xmlns:"; //$NON-NLS-1$
+ /** Qualified name of the xmlns android declaration element */
+ public static final String XMLNS_ANDROID = "xmlns:android"; //$NON-NLS-1$
+ /** The default prefix used for the {@link #ANDROID_URI} name space */
+ public static final String ANDROID_NS_NAME = "android"; //$NON-NLS-1$
+ /** The default prefix used for the {@link #ANDROID_URI} name space including the colon */
+ public static final String ANDROID_NS_NAME_PREFIX = "android:"; //$NON-NLS-1$
+ /** The default prefix used for the app */
+ private static final String APP_PREFIX = "app"; //$NON-NLS-1$
+ /** The "xmlns:" attribute prefix used for namespace declarations */
+ public static final String XMLNS_COLON = "xmlns:"; //$NON-NLS-1$
+ /** The entity for the ampersand character */
+ public static final String AMP_ENTITY = "&"; //$NON-NLS-1$
+ /** The entity for the quote character */
+ public static final String QUOT_ENTITY = """; //$NON-NLS-1$
+ /** The entity for the apostrophe character */
+ public static final String APOS_ENTITY = "'"; //$NON-NLS-1$
+ /** The entity for the less than character */
+ public static final String LT_ENTITY = "<"; //$NON-NLS-1$
+ /** The entity for the greater than character */
+ public static final String GT_ENTITY = ">"; //$NON-NLS-1$
+
+ /**
+ * Returns the namespace prefix matching the requested namespace URI.
+ * If no such declaration is found, returns the default "android" prefix for
+ * the Android URI, and "app" for other URI's.
+ *
+ * @param node The current node. Must not be null.
+ * @param nsUri The namespace URI of which the prefix is to be found,
+ * e.g. SdkConstants.NS_RESOURCES
+ * @return The first prefix declared or the default "android" prefix
+ * (or "app" for non-Android URIs)
+ */
+ @NonNull
+ public static String lookupNamespacePrefix(@NonNull Node node, @NonNull String nsUri) {
+ String defaultPrefix = ANDROID_URI.equals(nsUri) ? ANDROID_NS_NAME : APP_PREFIX;
+ return lookupNamespacePrefix(node, nsUri, defaultPrefix);
+ }
+
+ /**
+ * Returns the namespace prefix matching the requested namespace URI.
+ * If no such declaration is found, returns the default "android" prefix.
+ *
+ * @param node The current node. Must not be null.
+ * @param nsUri The namespace URI of which the prefix is to be found,
+ * e.g. SdkConstants.NS_RESOURCES
+ * @param defaultPrefix The default prefix (root) to use if the namespace
+ * is not found. If null, do not create a new namespace
+ * if this URI is not defined for the document.
+ * @return The first prefix declared or the provided prefix (possibly with
+ * a number appended to avoid conflicts with existing prefixes.
+ */
+ public static String lookupNamespacePrefix(
+ @Nullable Node node, @Nullable String nsUri, @Nullable String defaultPrefix) {
+ // Note: Node.lookupPrefix is not implemented in wst/xml/core NodeImpl.java
+ // The following code emulates this simple call:
+ // String prefix = node.lookupPrefix(SdkConstants.NS_RESOURCES);
+
+ // if the requested URI is null, it denotes an attribute with no namespace.
+ if (nsUri == null) {
+ return null;
+ }
+
+ // per XML specification, the "xmlns" URI is reserved
+ if (XMLNS_URI.equals(nsUri)) {
+ return XMLNS;
+ }
+
+ HashSet<String> visited = new HashSet<String>();
+ Document doc = node == null ? null : node.getOwnerDocument();
+
+ // Ask the document about it. This method may not be implemented by the Document.
+ String nsPrefix = null;
+ try {
+ nsPrefix = doc != null ? doc.lookupPrefix(nsUri) : null;
+ if (nsPrefix != null) {
+ return nsPrefix;
+ }
+ } catch (Throwable t) {
+ // ignore
+ }
+
+ // If that failed, try to look it up manually.
+ // This also gathers prefixed in use in the case we want to generate a new one below.
+ for (; node != null && node.getNodeType() == Node.ELEMENT_NODE;
+ node = node.getParentNode()) {
+ NamedNodeMap attrs = node.getAttributes();
+ for (int n = attrs.getLength() - 1; n >= 0; --n) {
+ Node attr = attrs.item(n);
+ if (XMLNS.equals(attr.getPrefix())) {
+ String uri = attr.getNodeValue();
+ nsPrefix = attr.getLocalName();
+ // Is this the URI we are looking for? If yes, we found its prefix.
+ if (nsUri.equals(uri)) {
+ return nsPrefix;
+ }
+ visited.add(nsPrefix);
+ }
+ }
+ }
+
+ // Failed the find a prefix. Generate a new sensible default prefix, unless
+ // defaultPrefix was null in which case the caller does not want the document
+ // modified.
+ if (defaultPrefix == null) {
+ return null;
+ }
+
+ //
+ // We need to make sure the prefix is not one that was declared in the scope
+ // visited above. Pick a unique prefix from the provided default prefix.
+ String prefix = defaultPrefix;
+ String base = prefix;
+ for (int i = 1; visited.contains(prefix); i++) {
+ prefix = base + Integer.toString(i);
+ }
+ // Also create & define this prefix/URI in the XML document as an attribute in the
+ // first element of the document.
+ if (doc != null) {
+ node = doc.getFirstChild();
+ while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
+ node = node.getNextSibling();
+ }
+ if (node != null) {
+ // This doesn't work:
+ //Attr attr = doc.createAttributeNS(XMLNS_URI, prefix);
+ //attr.setPrefix(XMLNS);
+ //
+ // Xerces throws
+ //org.w3c.dom.DOMException: NAMESPACE_ERR: An attempt is made to create or
+ // change an object in a way which is incorrect with regard to namespaces.
+ //
+ // Instead pass in the concatenated prefix. (This is covered by
+ // the UiElementNodeTest#testCreateNameSpace() test.)
+ Attr attr = doc.createAttributeNS(XMLNS_URI, XMLNS_PREFIX + prefix);
+ attr.setValue(nsUri);
+ node.getAttributes().setNamedItemNS(attr);
+ }
+ }
+
+ return prefix;
+ }
+
+ /**
+ * Converts the given attribute value to an XML-attribute-safe value, meaning that
+ * single and double quotes are replaced with their corresponding XML entities.
+ *
+ * @param attrValue the value to be escaped
+ * @return the escaped value
+ */
+ @NonNull
+ public static String toXmlAttributeValue(@NonNull String attrValue) {
+ for (int i = 0, n = attrValue.length(); i < n; i++) {
+ char c = attrValue.charAt(i);
+ if (c == '"' || c == '\'' || c == '<' || c == '&') {
+ StringBuilder sb = new StringBuilder(2 * attrValue.length());
+ appendXmlAttributeValue(sb, attrValue);
+ return sb.toString();
+ }
+ }
+
+ return attrValue;
+ }
+
+ /**
+ * Appends text to the given {@link StringBuilder} and escapes it as required for a
+ * DOM attribute node.
+ *
+ * @param sb the string builder
+ * @param attrValue the attribute value to be appended and escaped
+ */
+ public static void appendXmlAttributeValue(@NonNull StringBuilder sb,
+ @NonNull String attrValue) {
+ int n = attrValue.length();
+ // &, ", ' and < are illegal in attributes; see http://www.w3.org/TR/REC-xml/#NT-AttValue
+ // (' legal in a " string and " is legal in a ' string but here we'll stay on the safe
+ // side)
+ for (int i = 0; i < n; i++) {
+ char c = attrValue.charAt(i);
+ if (c == '"') {
+ sb.append(QUOT_ENTITY);
+ } else if (c == '<') {
+ sb.append(LT_ENTITY);
+ } else if (c == '\'') {
+ sb.append(APOS_ENTITY);
+ } else if (c == '&') {
+ sb.append(AMP_ENTITY);
+ } else {
+ sb.append(c);
+ }
+ }
+ }
+
+ /**
+ * Appends text to the given {@link StringBuilder} and escapes it as required for a
+ * DOM text node.
+ *
+ * @param sb the string builder
+ * @param textValue the text value to be appended and escaped
+ */
+ public static void appendXmlTextValue(@NonNull StringBuilder sb, @NonNull String textValue) {
+ for (int i = 0, n = textValue.length(); i < n; i++) {
+ char c = textValue.charAt(i);
+ if (c == '<') {
+ sb.append(LT_ENTITY);
+ } else if (c == '&') {
+ sb.append(AMP_ENTITY);
+ } else {
+ sb.append(c);
+ }
+ }
+ }
+}