• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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