| /* |
| * 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.layoutlib.utils; |
| |
| 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"; |
| |
| // Resource type definition |
| private final static String RES_STYLE = "style"; |
| private final static String RES_ATTR = "attr"; |
| |
| 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(String resType, ResourceValue value); |
| } |
| |
| private boolean inResources = false; |
| private int mDepth = 0; |
| private StyleResourceValue mCurrentStyle = null; |
| private ResourceValue mCurrentValue = null; |
| 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(trimXmlWhitespaces(mCurrentValue.getValue())); |
| } |
| |
| if (inResources && qName.equals(NODE_RESOURCES)) { |
| inResources = false; |
| } else if (mDepth == 2) { |
| mCurrentValue = null; |
| mCurrentStyle = null; |
| } else if (mDepth == 3) { |
| mCurrentValue = 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) { |
| String type; |
| |
| // if the node is <item>, we get the type from the attribute "type" |
| if (NODE_ITEM.equals(qName)) { |
| type = attributes.getValue(ATTR_TYPE); |
| } else { |
| // the type is the name of the node. |
| type = qName; |
| } |
| |
| if (type != null) { |
| if (RES_ATTR.equals(type) == false) { |
| // get the resource name |
| String name = attributes.getValue(ATTR_NAME); |
| if (name != null) { |
| if (RES_STYLE.equals(type)) { |
| String parent = attributes.getValue(ATTR_PARENT); |
| mCurrentStyle = new StyleResourceValue(type, name, parent, mIsFramework); |
| mRepository.addResourceValue(type, mCurrentStyle); |
| } else { |
| mCurrentValue = new ResourceValue(type, name, mIsFramework); |
| mRepository.addResourceValue(type, mCurrentValue); |
| } |
| } |
| } |
| } |
| } else if (mDepth == 3 && mCurrentStyle != null) { |
| // get the resource name |
| String name = attributes.getValue(ATTR_NAME); |
| if (name != null) { |
| // the name can, in some cases, contain a prefix! we remove it. |
| if (name.startsWith(DEFAULT_NS_PREFIX)) { |
| name = name.substring(DEFAULT_NS_PREFIX_LEN); |
| } |
| |
| mCurrentValue = new ResourceValue(null, name, mIsFramework); |
| mCurrentStyle.addItem(mCurrentValue); |
| } |
| } |
| } finally { |
| super.startElement(uri, localName, qName, attributes); |
| } |
| } |
| |
| @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)); |
| } |
| } |
| } |
| |
| public static String trimXmlWhitespaces(String value) { |
| if (value == 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 { |
| break; |
| } |
| } |
| |
| int right = index + 1; |
| int count = value.length(); |
| while (right < count) { |
| if (Character.isWhitespace(value.charAt(right))) { |
| right++; |
| } else { |
| break; |
| } |
| } |
| |
| // 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); |
| } |
| String rightString = null; |
| if (right < count) { |
| rightString = value.substring(right); |
| } |
| |
| if (leftString != null) { |
| value = leftString; |
| if (rightString != null) { |
| value += " " + rightString; |
| } |
| } else { |
| value = rightString != null ? rightString : ""; |
| } |
| } |
| |
| // now we un-escape the string |
| int length = value.length(); |
| char[] buffer = value.toCharArray(); |
| |
| 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); |
| |
| // put the unicode char at the location of the \ |
| buffer[i] = (char)unicodeChar; |
| |
| // 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'; |
| } |
| |
| // offset the buffer to erase the \ |
| System.arraycopy(buffer, i+1, buffer, i, length - i - 1); |
| length--; |
| } |
| } |
| } |
| |
| return new String(buffer, 0, length); |
| } |
| } |