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 { 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 58 public ValueResourceParser(IValueResourceRepository repository, boolean isFramework) { 59 mRepository = repository; 60 mIsFramework = isFramework; 61 } 62 63 @Override 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 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 // the name can, in some cases, contain a prefix! we remove it. 133 if (name.startsWith(DEFAULT_NS_PREFIX)) { 134 name = name.substring(DEFAULT_NS_PREFIX_LEN); 135 } 136 137 mCurrentValue = new ResourceValue(null, name, mIsFramework); 138 mCurrentStyle.addValue(mCurrentValue); 139 } else if (mCurrentDeclareStyleable != null) { 140 mCurrentAttr = new AttrResourceValue(ResourceType.ATTR, name, mIsFramework); 141 mCurrentDeclareStyleable.addValue(mCurrentAttr); 142 } else if (mCurrentAttr != null) { 143 // get the enum/flag value 144 String value = attributes.getValue(ATTR_VALUE); 145 146 try { 147 // Integer.decode/parseInt can't deal with hex value > 0x7FFFFFFF so we 148 // use Long.decode instead. 149 mCurrentAttr.addValue(name, (int)(long)Long.decode(value)); 150 } catch (NumberFormatException e) { 151 // pass, we'll just ignore this value 152 } 153 154 } 155 } 156 } else if (mDepth == 4 && mCurrentAttr != null) { 157 // get the enum/flag name 158 String name = attributes.getValue(ATTR_NAME); 159 String value = attributes.getValue(ATTR_VALUE); 160 161 try { 162 // Integer.decode/parseInt can't deal with hex value > 0x7FFFFFFF so we 163 // use Long.decode instead. 164 mCurrentAttr.addValue(name, (int)(long)Long.decode(value)); 165 } catch (NumberFormatException e) { 166 // pass, we'll just ignore this value 167 } 168 } 169 } finally { 170 super.startElement(uri, localName, qName, attributes); 171 } 172 } 173 174 private ResourceType getType(String qName, Attributes attributes) { 175 String typeValue; 176 177 // if the node is <item>, we get the type from the attribute "type" 178 if (NODE_ITEM.equals(qName)) { 179 typeValue = attributes.getValue(ATTR_TYPE); 180 } else { 181 // the type is the name of the node. 182 typeValue = qName; 183 } 184 185 ResourceType type = ResourceType.getEnum(typeValue); 186 return type; 187 } 188 189 190 @Override 191 public void characters(char[] ch, int start, int length) throws SAXException { 192 if (mCurrentValue != null) { 193 String value = mCurrentValue.getValue(); 194 if (value == null) { 195 mCurrentValue.setValue(new String(ch, start, length)); 196 } else { 197 mCurrentValue.setValue(value + new String(ch, start, length)); 198 } 199 } 200 } 201 202 public static String trimXmlWhitespaces(String value) { 203 if (value == null) { 204 return null; 205 } 206 207 // look for carriage return and replace all whitespace around it by just 1 space. 208 int index; 209 210 while ((index = value.indexOf('\n')) != -1) { 211 // look for whitespace on each side 212 int left = index - 1; 213 while (left >= 0) { 214 if (Character.isWhitespace(value.charAt(left))) { 215 left--; 216 } else { 217 break; 218 } 219 } 220 221 int right = index + 1; 222 int count = value.length(); 223 while (right < count) { 224 if (Character.isWhitespace(value.charAt(right))) { 225 right++; 226 } else { 227 break; 228 } 229 } 230 231 // remove all between left and right (non inclusive) and replace by a single space. 232 String leftString = null; 233 if (left >= 0) { 234 leftString = value.substring(0, left + 1); 235 } 236 String rightString = null; 237 if (right < count) { 238 rightString = value.substring(right); 239 } 240 241 if (leftString != null) { 242 value = leftString; 243 if (rightString != null) { 244 value += " " + rightString; 245 } 246 } else { 247 value = rightString != null ? rightString : ""; 248 } 249 } 250 251 // now we un-escape the string 252 int length = value.length(); 253 char[] buffer = value.toCharArray(); 254 255 for (int i = 0 ; i < length ; i++) { 256 if (buffer[i] == '\\' && i + 1 < length) { 257 if (buffer[i+1] == 'u') { 258 if (i + 5 < length) { 259 // this is unicode char \u1234 260 int unicodeChar = Integer.parseInt(new String(buffer, i+2, 4), 16); 261 262 // put the unicode char at the location of the \ 263 buffer[i] = (char)unicodeChar; 264 265 // offset the rest of the buffer since we go from 6 to 1 char 266 if (i + 6 < buffer.length) { 267 System.arraycopy(buffer, i+6, buffer, i+1, length - i - 6); 268 } 269 length -= 5; 270 } 271 } else { 272 if (buffer[i+1] == 'n') { 273 // replace the 'n' char with \n 274 buffer[i+1] = '\n'; 275 } 276 277 // offset the buffer to erase the \ 278 System.arraycopy(buffer, i+1, buffer, i, length - i - 1); 279 length--; 280 } 281 } else if (buffer[i] == '"') { 282 // if the " was escaped it would have been processed above. 283 // offset the buffer to erase the " 284 System.arraycopy(buffer, i+1, buffer, i, length - i - 1); 285 length--; 286 287 // unlike when unescaping, we want to process the next char too 288 i--; 289 } 290 } 291 292 return new String(buffer, 0, length); 293 } 294 } 295