1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.common.resources; 18 19 import com.android.ide.common.rendering.api.AttrResourceValue; 20 import com.android.ide.common.rendering.api.DeclareStyleableResourceValue; 21 import com.android.ide.common.rendering.api.ResourceValue; 22 import com.android.ide.common.rendering.api.StyleResourceValue; 23 import com.android.resources.ResourceType; 24 25 import org.xml.sax.Attributes; 26 import org.xml.sax.SAXException; 27 import org.xml.sax.helpers.DefaultHandler; 28 29 /** 30 * SAX handler to parser value resource files. 31 */ 32 public final class ValueResourceParser extends DefaultHandler { 33 34 // TODO: reuse definitions from somewhere else. 35 private final static String NODE_RESOURCES = "resources"; 36 private final static String NODE_ITEM = "item"; 37 private final static String ATTR_NAME = "name"; 38 private final static String ATTR_TYPE = "type"; 39 private final static String ATTR_PARENT = "parent"; 40 private final static String ATTR_VALUE = "value"; 41 42 private final static String DEFAULT_NS_PREFIX = "android:"; 43 private final static int DEFAULT_NS_PREFIX_LEN = DEFAULT_NS_PREFIX.length(); 44 45 public interface IValueResourceRepository { addResourceValue(ResourceValue value)46 void addResourceValue(ResourceValue value); 47 } 48 49 private boolean inResources = false; 50 private int mDepth = 0; 51 private ResourceValue mCurrentValue = null; 52 private StyleResourceValue mCurrentStyle = null; 53 private DeclareStyleableResourceValue mCurrentDeclareStyleable = null; 54 private AttrResourceValue mCurrentAttr; 55 private IValueResourceRepository mRepository; 56 private final boolean mIsFramework; 57 ValueResourceParser(IValueResourceRepository repository, boolean isFramework)58 public ValueResourceParser(IValueResourceRepository repository, boolean isFramework) { 59 mRepository = repository; 60 mIsFramework = isFramework; 61 } 62 63 @Override endElement(String uri, String localName, String qName)64 public void endElement(String uri, String localName, String qName) throws SAXException { 65 if (mCurrentValue != null) { 66 mCurrentValue.setValue(trimXmlWhitespaces(mCurrentValue.getValue())); 67 } 68 69 if (inResources && qName.equals(NODE_RESOURCES)) { 70 inResources = false; 71 } else if (mDepth == 2) { 72 mCurrentValue = null; 73 mCurrentStyle = null; 74 mCurrentDeclareStyleable = null; 75 mCurrentAttr = null; 76 } else if (mDepth == 3) { 77 mCurrentValue = null; 78 if (mCurrentDeclareStyleable != null) { 79 mCurrentAttr = null; 80 } 81 } 82 83 mDepth--; 84 super.endElement(uri, localName, qName); 85 } 86 87 @Override startElement(String uri, String localName, String qName, Attributes attributes)88 public void startElement(String uri, String localName, String qName, Attributes attributes) 89 throws SAXException { 90 try { 91 mDepth++; 92 if (inResources == false && mDepth == 1) { 93 if (qName.equals(NODE_RESOURCES)) { 94 inResources = true; 95 } 96 } else if (mDepth == 2 && inResources == true) { 97 ResourceType type = getType(qName, attributes); 98 99 if (type != null) { 100 // get the resource name 101 String name = attributes.getValue(ATTR_NAME); 102 if (name != null) { 103 switch (type) { 104 case STYLE: 105 String parent = attributes.getValue(ATTR_PARENT); 106 mCurrentStyle = new StyleResourceValue(type, name, parent, 107 mIsFramework); 108 mRepository.addResourceValue(mCurrentStyle); 109 break; 110 case DECLARE_STYLEABLE: 111 mCurrentDeclareStyleable = new DeclareStyleableResourceValue( 112 type, name, mIsFramework); 113 mRepository.addResourceValue(mCurrentDeclareStyleable); 114 break; 115 case ATTR: 116 mCurrentAttr = new AttrResourceValue(type, name, mIsFramework); 117 mRepository.addResourceValue(mCurrentAttr); 118 break; 119 default: 120 mCurrentValue = new ResourceValue(type, name, mIsFramework); 121 mRepository.addResourceValue(mCurrentValue); 122 break; 123 } 124 } 125 } 126 } else if (mDepth == 3) { 127 // get the resource name 128 String name = attributes.getValue(ATTR_NAME); 129 if (name != null) { 130 131 if (mCurrentStyle != null) { 132 // is the attribute in the android namespace? 133 boolean isFrameworkAttr = mIsFramework; 134 if (name.startsWith(DEFAULT_NS_PREFIX)) { 135 name = name.substring(DEFAULT_NS_PREFIX_LEN); 136 isFrameworkAttr = true; 137 } 138 139 mCurrentValue = new ResourceValue(null, name, mIsFramework); 140 mCurrentStyle.addValue(mCurrentValue, isFrameworkAttr); 141 } else if (mCurrentDeclareStyleable != null) { 142 // is the attribute in the android namespace? 143 boolean isFramework = mIsFramework; 144 if (name.startsWith(DEFAULT_NS_PREFIX)) { 145 name = name.substring(DEFAULT_NS_PREFIX_LEN); 146 isFramework = true; 147 } 148 149 mCurrentAttr = new AttrResourceValue(ResourceType.ATTR, name, isFramework); 150 mCurrentDeclareStyleable.addValue(mCurrentAttr); 151 152 // also add it to the repository. 153 mRepository.addResourceValue(mCurrentAttr); 154 155 } else if (mCurrentAttr != null) { 156 // get the enum/flag value 157 String value = attributes.getValue(ATTR_VALUE); 158 159 try { 160 // Integer.decode/parseInt can't deal with hex value > 0x7FFFFFFF so we 161 // use Long.decode instead. 162 mCurrentAttr.addValue(name, (int)(long)Long.decode(value)); 163 } catch (NumberFormatException e) { 164 // pass, we'll just ignore this value 165 } 166 167 } 168 } 169 } else if (mDepth == 4 && mCurrentAttr != null) { 170 // get the enum/flag name 171 String name = attributes.getValue(ATTR_NAME); 172 String value = attributes.getValue(ATTR_VALUE); 173 174 try { 175 // Integer.decode/parseInt can't deal with hex value > 0x7FFFFFFF so we 176 // use Long.decode instead. 177 mCurrentAttr.addValue(name, (int)(long)Long.decode(value)); 178 } catch (NumberFormatException e) { 179 // pass, we'll just ignore this value 180 } 181 } 182 } finally { 183 super.startElement(uri, localName, qName, attributes); 184 } 185 } 186 getType(String qName, Attributes attributes)187 private ResourceType getType(String qName, Attributes attributes) { 188 String typeValue; 189 190 // if the node is <item>, we get the type from the attribute "type" 191 if (NODE_ITEM.equals(qName)) { 192 typeValue = attributes.getValue(ATTR_TYPE); 193 } else { 194 // the type is the name of the node. 195 typeValue = qName; 196 } 197 198 ResourceType type = ResourceType.getEnum(typeValue); 199 return type; 200 } 201 202 203 @Override characters(char[] ch, int start, int length)204 public void characters(char[] ch, int start, int length) throws SAXException { 205 if (mCurrentValue != null) { 206 String value = mCurrentValue.getValue(); 207 if (value == null) { 208 mCurrentValue.setValue(new String(ch, start, length)); 209 } else { 210 mCurrentValue.setValue(value + new String(ch, start, length)); 211 } 212 } 213 } 214 trimXmlWhitespaces(String value)215 public static String trimXmlWhitespaces(String value) { 216 if (value == null) { 217 return null; 218 } 219 220 // look for carriage return and replace all whitespace around it by just 1 space. 221 int index; 222 223 while ((index = value.indexOf('\n')) != -1) { 224 // look for whitespace on each side 225 int left = index - 1; 226 while (left >= 0) { 227 if (Character.isWhitespace(value.charAt(left))) { 228 left--; 229 } else { 230 break; 231 } 232 } 233 234 int right = index + 1; 235 int count = value.length(); 236 while (right < count) { 237 if (Character.isWhitespace(value.charAt(right))) { 238 right++; 239 } else { 240 break; 241 } 242 } 243 244 // remove all between left and right (non inclusive) and replace by a single space. 245 String leftString = null; 246 if (left >= 0) { 247 leftString = value.substring(0, left + 1); 248 } 249 String rightString = null; 250 if (right < count) { 251 rightString = value.substring(right); 252 } 253 254 if (leftString != null) { 255 value = leftString; 256 if (rightString != null) { 257 value += " " + rightString; 258 } 259 } else { 260 value = rightString != null ? rightString : ""; 261 } 262 } 263 264 // now we un-escape the string 265 int length = value.length(); 266 char[] buffer = value.toCharArray(); 267 268 for (int i = 0 ; i < length ; i++) { 269 if (buffer[i] == '\\' && i + 1 < length) { 270 if (buffer[i+1] == 'u') { 271 if (i + 5 < length) { 272 // this is unicode char \u1234 273 int unicodeChar = Integer.parseInt(new String(buffer, i+2, 4), 16); 274 275 // put the unicode char at the location of the \ 276 buffer[i] = (char)unicodeChar; 277 278 // offset the rest of the buffer since we go from 6 to 1 char 279 if (i + 6 < buffer.length) { 280 System.arraycopy(buffer, i+6, buffer, i+1, length - i - 6); 281 } 282 length -= 5; 283 } 284 } else { 285 if (buffer[i+1] == 'n') { 286 // replace the 'n' char with \n 287 buffer[i+1] = '\n'; 288 } 289 290 // offset the buffer to erase the \ 291 System.arraycopy(buffer, i+1, buffer, i, length - i - 1); 292 length--; 293 } 294 } else if (buffer[i] == '"') { 295 // if the " was escaped it would have been processed above. 296 // offset the buffer to erase the " 297 System.arraycopy(buffer, i+1, buffer, i, length - i - 1); 298 length--; 299 300 // unlike when unescaping, we want to process the next char too 301 i--; 302 } 303 } 304 305 return new String(buffer, 0, length); 306 } 307 } 308