diff options
Diffstat (limited to 'sdk_common/src/com/android/ide/common/resources/ValueResourceParser.java')
-rw-r--r-- | sdk_common/src/com/android/ide/common/resources/ValueResourceParser.java | 480 |
1 files changed, 0 insertions, 480 deletions
diff --git a/sdk_common/src/com/android/ide/common/resources/ValueResourceParser.java b/sdk_common/src/com/android/ide/common/resources/ValueResourceParser.java deleted file mode 100644 index aa1f4b8..0000000 --- a/sdk_common/src/com/android/ide/common/resources/ValueResourceParser.java +++ /dev/null @@ -1,480 +0,0 @@ -/* - * Copyright (C) 2008 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.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; -import com.android.ide.common.rendering.api.StyleResourceValue; -import com.android.resources.ResourceType; - -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -/** - * SAX handler to parser value resource files. - */ -public final class ValueResourceParser extends DefaultHandler { - - // TODO: reuse definitions from somewhere else. - private final static String NODE_RESOURCES = "resources"; - private final static String NODE_ITEM = "item"; - private final static String ATTR_NAME = "name"; - private final static String ATTR_TYPE = "type"; - private final static String ATTR_PARENT = "parent"; - private final static String ATTR_VALUE = "value"; - - private final static String DEFAULT_NS_PREFIX = "android:"; - private final static int DEFAULT_NS_PREFIX_LEN = DEFAULT_NS_PREFIX.length(); - - public interface IValueResourceRepository { - void addResourceValue(ResourceValue value); - boolean hasResourceValue(ResourceType type, String name); - } - - private boolean inResources = false; - private int mDepth = 0; - private ResourceValue mCurrentValue = null; - private StyleResourceValue mCurrentStyle = null; - private DeclareStyleableResourceValue mCurrentDeclareStyleable = null; - private AttrResourceValue mCurrentAttr; - private IValueResourceRepository mRepository; - private final boolean mIsFramework; - - public ValueResourceParser(IValueResourceRepository repository, boolean isFramework) { - mRepository = repository; - mIsFramework = isFramework; - } - - @Override - public void endElement(String uri, String localName, String qName) throws SAXException { - if (mCurrentValue != null) { - mCurrentValue.setValue(unescapeResourceString(mCurrentValue.getValue(), false, true)); - } - - if (inResources && qName.equals(NODE_RESOURCES)) { - inResources = false; - } else if (mDepth == 2) { - mCurrentValue = null; - mCurrentStyle = null; - mCurrentDeclareStyleable = null; - mCurrentAttr = null; - } else if (mDepth == 3) { - mCurrentValue = null; - if (mCurrentDeclareStyleable != null) { - mCurrentAttr = null; - } - } - - mDepth--; - super.endElement(uri, localName, qName); - } - - @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) - throws SAXException { - try { - mDepth++; - if (inResources == false && mDepth == 1) { - if (qName.equals(NODE_RESOURCES)) { - inResources = true; - } - } else if (mDepth == 2 && inResources == true) { - ResourceType type = getType(qName, attributes); - - if (type != null) { - // get the resource name - String name = attributes.getValue(ATTR_NAME); - if (name != null) { - switch (type) { - case STYLE: - String parent = attributes.getValue(ATTR_PARENT); - mCurrentStyle = new StyleResourceValue(type, name, parent, - mIsFramework); - mRepository.addResourceValue(mCurrentStyle); - break; - case DECLARE_STYLEABLE: - mCurrentDeclareStyleable = new DeclareStyleableResourceValue( - type, name, mIsFramework); - mRepository.addResourceValue(mCurrentDeclareStyleable); - break; - case ATTR: - mCurrentAttr = new AttrResourceValue(type, name, mIsFramework); - mRepository.addResourceValue(mCurrentAttr); - break; - default: - mCurrentValue = new ResourceValue(type, name, mIsFramework); - mRepository.addResourceValue(mCurrentValue); - break; - } - } - } - } else if (mDepth == 3) { - // get the resource name - String name = attributes.getValue(ATTR_NAME); - if (name != null) { - - if (mCurrentStyle != null) { - // is the attribute in the android namespace? - boolean isFrameworkAttr = mIsFramework; - if (name.startsWith(DEFAULT_NS_PREFIX)) { - name = name.substring(DEFAULT_NS_PREFIX_LEN); - isFrameworkAttr = true; - } - - mCurrentValue = new ResourceValue(null, name, mIsFramework); - mCurrentStyle.addValue(mCurrentValue, isFrameworkAttr); - } else if (mCurrentDeclareStyleable != null) { - // is the attribute in the android namespace? - boolean isFramework = mIsFramework; - if (name.startsWith(DEFAULT_NS_PREFIX)) { - name = name.substring(DEFAULT_NS_PREFIX_LEN); - isFramework = true; - } - - mCurrentAttr = new AttrResourceValue(ResourceType.ATTR, name, isFramework); - mCurrentDeclareStyleable.addValue(mCurrentAttr); - - // also add it to the repository. - mRepository.addResourceValue(mCurrentAttr); - - } else if (mCurrentAttr != null) { - // get the enum/flag value - String value = attributes.getValue(ATTR_VALUE); - - try { - // Integer.decode/parseInt can't deal with hex value > 0x7FFFFFFF so we - // use Long.decode instead. - mCurrentAttr.addValue(name, (int)(long)Long.decode(value)); - } catch (NumberFormatException e) { - // pass, we'll just ignore this value - } - - } - } - } else if (mDepth == 4 && mCurrentAttr != null) { - // get the enum/flag name - String name = attributes.getValue(ATTR_NAME); - String value = attributes.getValue(ATTR_VALUE); - - try { - // Integer.decode/parseInt can't deal with hex value > 0x7FFFFFFF so we - // use Long.decode instead. - mCurrentAttr.addValue(name, (int)(long)Long.decode(value)); - } catch (NumberFormatException e) { - // pass, we'll just ignore this value - } - } - } finally { - super.startElement(uri, localName, qName, attributes); - } - } - - private ResourceType getType(String qName, Attributes attributes) { - String typeValue; - - // if the node is <item>, we get the type from the attribute "type" - if (NODE_ITEM.equals(qName)) { - typeValue = attributes.getValue(ATTR_TYPE); - } else { - // the type is the name of the node. - typeValue = qName; - } - - ResourceType type = ResourceType.getEnum(typeValue); - return type; - } - - - @Override - public void characters(char[] ch, int start, int length) throws SAXException { - if (mCurrentValue != null) { - String value = mCurrentValue.getValue(); - if (value == null) { - mCurrentValue.setValue(new String(ch, start, length)); - } else { - mCurrentValue.setValue(value + new String(ch, start, length)); - } - } - } - - /** - * 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; - } - - // 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--; - } - - // 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$ - } - - // 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--; - } - } - 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; - } - - 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 { - 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(); - - return s; - } - - /** - * 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; - } - - /** - * 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 ""; - } - - StringBuilder sb = new StringBuilder(s.length() * 2); - boolean hasSpace = s.charAt(0) == ' ' || s.charAt(n - 1) == ' '; - - 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; - } - } - - if (hasSpace) { - sb.append('"'); - } - - return sb.toString(); - } -} |