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 package com.android.layoutlib.bridge.android;
17 
18 import com.android.ide.common.rendering.api.ILayoutPullParser;
19 import com.android.ide.common.rendering.api.ResourceNamespace;
20 import com.android.ide.common.rendering.api.ResourceValue;
21 import com.android.layoutlib.bridge.impl.ParserFactory;
22 
23 import org.xmlpull.v1.XmlPullParser;
24 import org.xmlpull.v1.XmlPullParserException;
25 
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.content.res.XmlResourceParser;
29 import android.util.AttributeSet;
30 import android.util.BridgeXmlPullAttributes;
31 import android.util.ResolvingAttributeSet;
32 
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.io.Reader;
36 
37 /**
38  * {@link BridgeXmlBlockParser} reimplements most of android.xml.XmlBlock.Parser.
39  * It delegates to both an instance of {@link XmlPullParser} and an instance of
40  * XmlPullAttributes (for the {@link AttributeSet} part).
41  */
42 public class BridgeXmlBlockParser implements XmlResourceParser, ResolvingAttributeSet {
43     @NonNull private final XmlPullParser mParser;
44     @NonNull private final ResolvingAttributeSet mAttrib;
45     @Nullable private final BridgeContext mContext;
46     @NonNull private final ResourceNamespace mFileResourceNamespace;
47 
48     private boolean mStarted = false;
49     private int mEventType = START_DOCUMENT;
50 
51     private boolean mPopped = true; // default to true in case it's not pushed.
52 
53     /**
54      * Builds a {@link BridgeXmlBlockParser}.
55      * @param parser XmlPullParser to get the content from.
56      * @param context the Context.
57      * @param fileNamespace namespace of the file being parsed.
58      */
BridgeXmlBlockParser( @onNull XmlPullParser parser, @Nullable BridgeContext context, @NonNull ResourceNamespace fileNamespace)59     public BridgeXmlBlockParser(
60             @NonNull XmlPullParser parser,
61             @Nullable BridgeContext context,
62             @NonNull ResourceNamespace fileNamespace) {
63         if (ParserFactory.LOG_PARSER) {
64             System.out.println("CRTE " + parser.toString());
65         }
66 
67         mParser = parser;
68         mContext = context;
69         mFileResourceNamespace = fileNamespace;
70 
71         if (mContext != null) {
72             mAttrib = new BridgeXmlPullAttributes(parser, context, mFileResourceNamespace);
73             mContext.pushParser(this);
74             mPopped = false;
75         }
76         else {
77             mAttrib = new NopAttributeSet();
78         }
79     }
80 
getParser()81     public XmlPullParser getParser() {
82         return mParser;
83     }
84 
85     @NonNull
getFileResourceNamespace()86     public ResourceNamespace getFileResourceNamespace() {
87         return mFileResourceNamespace;
88     }
89 
getViewCookie()90     public Object getViewCookie() {
91         if (mParser instanceof ILayoutPullParser) {
92             return ((ILayoutPullParser)mParser).getViewCookie();
93         }
94 
95         return null;
96     }
97 
ensurePopped()98     public void ensurePopped() {
99         if (mContext != null && !mPopped) {
100             mContext.popParser();
101             mPopped = true;
102         }
103     }
104 
105     // ------- XmlResourceParser implementation
106 
107     @Override
setFeature(String name, boolean state)108     public void setFeature(String name, boolean state)
109             throws XmlPullParserException {
110         if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) {
111             return;
112         }
113         if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name) && state) {
114             return;
115         }
116         throw new XmlPullParserException("Unsupported feature: " + name);
117     }
118 
119     @Override
getFeature(String name)120     public boolean getFeature(String name) {
121         return FEATURE_PROCESS_NAMESPACES.equals(name) ||
122                 FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name);
123     }
124 
125     @Override
setProperty(String name, Object value)126     public void setProperty(String name, Object value) throws XmlPullParserException {
127         throw new XmlPullParserException("setProperty() not supported");
128     }
129 
130     @Override
getProperty(String name)131     public Object getProperty(String name) {
132         return null;
133     }
134 
135     @Override
setInput(Reader in)136     public void setInput(Reader in) throws XmlPullParserException {
137         mParser.setInput(in);
138     }
139 
140     @Override
setInput(InputStream inputStream, String inputEncoding)141     public void setInput(InputStream inputStream, String inputEncoding)
142             throws XmlPullParserException {
143         mParser.setInput(inputStream, inputEncoding);
144     }
145 
146     @Override
defineEntityReplacementText(String entityName, String replacementText)147     public void defineEntityReplacementText(String entityName,
148             String replacementText) throws XmlPullParserException {
149         throw new XmlPullParserException(
150                 "defineEntityReplacementText() not supported");
151     }
152 
153     @Override
getNamespacePrefix(int pos)154     public String getNamespacePrefix(int pos) throws XmlPullParserException {
155         throw new XmlPullParserException("getNamespacePrefix() not supported");
156     }
157 
158     @Override
getInputEncoding()159     public String getInputEncoding() {
160         return null;
161     }
162 
163     @Override
getNamespace(String prefix)164     public String getNamespace(String prefix) {
165         return mParser.getNamespace(prefix);
166     }
167 
168     @Override
getNamespaceCount(int depth)169     public int getNamespaceCount(int depth) throws XmlPullParserException {
170         throw new XmlPullParserException("getNamespaceCount() not supported");
171     }
172 
173     @Override
getPositionDescription()174     public String getPositionDescription() {
175         return "Binary XML file line #" + getLineNumber();
176     }
177 
178     @Override
getNamespaceUri(int pos)179     public String getNamespaceUri(int pos) throws XmlPullParserException {
180         throw new XmlPullParserException("getNamespaceUri() not supported");
181     }
182 
183     @Override
getColumnNumber()184     public int getColumnNumber() {
185         return -1;
186     }
187 
188     @Override
getDepth()189     public int getDepth() {
190         return mParser.getDepth();
191     }
192 
193     @Override
getText()194     public String getText() {
195         return mParser.getText();
196     }
197 
198     @Override
getLineNumber()199     public int getLineNumber() {
200         return mParser.getLineNumber();
201     }
202 
203     @Override
getEventType()204     public int getEventType() {
205         return mEventType;
206     }
207 
208     @Override
isWhitespace()209     public boolean isWhitespace() throws XmlPullParserException {
210         // Original comment: whitespace was stripped by aapt.
211         return mParser.isWhitespace();
212     }
213 
214     @Override
getPrefix()215     public String getPrefix() {
216         throw new RuntimeException("getPrefix not supported");
217     }
218 
219     @Override
getTextCharacters(int[] holderForStartAndLength)220     public char[] getTextCharacters(int[] holderForStartAndLength) {
221         String txt = getText();
222         char[] chars = null;
223         if (txt != null) {
224             holderForStartAndLength[0] = 0;
225             holderForStartAndLength[1] = txt.length();
226             chars = new char[txt.length()];
227             txt.getChars(0, txt.length(), chars, 0);
228         }
229         return chars;
230     }
231 
232     @Override
getNamespace()233     public String getNamespace() {
234         return mParser.getNamespace();
235     }
236 
237     @Override
getName()238     public String getName() {
239         return mParser.getName();
240     }
241 
242     @Override
getAttributeNamespace(int index)243     public String getAttributeNamespace(int index) {
244         return mParser.getAttributeNamespace(index);
245     }
246 
247     @Override
getAttributeName(int index)248     public String getAttributeName(int index) {
249         return mParser.getAttributeName(index);
250     }
251 
252     @Override
getAttributePrefix(int index)253     public String getAttributePrefix(int index) {
254         throw new RuntimeException("getAttributePrefix not supported");
255     }
256 
257     @Override
isEmptyElementTag()258     public boolean isEmptyElementTag() {
259         // XXX Need to detect this.
260         return false;
261     }
262 
263     @Override
getAttributeCount()264     public int getAttributeCount() {
265         return mParser.getAttributeCount();
266     }
267 
268     @Override
getAttributeValue(int index)269     public String getAttributeValue(int index) {
270         return mParser.getAttributeValue(index);
271     }
272 
273     @Override
getAttributeType(int index)274     public String getAttributeType(int index) {
275         return "CDATA";
276     }
277 
278     @Override
isAttributeDefault(int index)279     public boolean isAttributeDefault(int index) {
280         return false;
281     }
282 
283     @Override
nextToken()284     public int nextToken() throws XmlPullParserException, IOException {
285         return next();
286     }
287 
288     @Override
getAttributeValue(String namespace, String name)289     public String getAttributeValue(String namespace, String name) {
290         return mParser.getAttributeValue(namespace, name);
291     }
292 
293     @Override
next()294     public int next() throws XmlPullParserException, IOException {
295         if (!mStarted) {
296             mStarted = true;
297 
298             if (ParserFactory.LOG_PARSER) {
299                 System.out.println("STRT " + mParser.toString());
300             }
301 
302             return START_DOCUMENT;
303         }
304 
305         int ev = mParser.next();
306 
307         // AAPT treats resources so that XmlBlock$Parser never has TEXT events that are
308         // whitespace only. Ignore those events here as resources from Studio have not gone
309         // through AAPT compilation.
310         while (ev == TEXT && mParser.isWhitespace()) {
311             ev = mParser.next();
312         }
313 
314         if (ParserFactory.LOG_PARSER) {
315             System.out.println("NEXT " + mParser.toString() + " " +
316                     eventTypeToString(mEventType) + " -> " + eventTypeToString(ev));
317         }
318 
319         if (ev == END_TAG && mParser.getDepth() == 1) {
320             // done with parser remove it from the context stack.
321             ensurePopped();
322         }
323 
324         mEventType = ev;
325         return ev;
326     }
327 
eventTypeToString(int eventType)328     private static String eventTypeToString(int eventType) {
329         switch (eventType) {
330             case START_DOCUMENT:
331                 return "START_DOC";
332             case END_DOCUMENT:
333                 return "END_DOC";
334             case START_TAG:
335                 return "START_TAG";
336             case END_TAG:
337                 return "END_TAG";
338             case TEXT:
339                 return "TEXT";
340             case CDSECT:
341                 return "CDSECT";
342             case ENTITY_REF:
343                 return "ENTITY_REF";
344             case IGNORABLE_WHITESPACE:
345                 return "IGNORABLE_WHITESPACE";
346             case PROCESSING_INSTRUCTION:
347                 return "PROCESSING_INSTRUCTION";
348             case COMMENT:
349                 return "COMMENT";
350             case DOCDECL:
351                 return "DOCDECL";
352         }
353 
354         return "????";
355     }
356 
357     @Override
require(int type, String namespace, String name)358     public void require(int type, String namespace, String name)
359             throws XmlPullParserException {
360         if (type != getEventType()
361                 || (namespace != null && !namespace.equals(getNamespace()))
362                 || (name != null && !name.equals(getName())))
363             throw new XmlPullParserException("expected " + TYPES[type]
364                     + getPositionDescription());
365     }
366 
367     @Override
nextText()368     public String nextText() throws XmlPullParserException, IOException {
369         if (getEventType() != START_TAG) {
370             throw new XmlPullParserException(getPositionDescription()
371                     + ": parser must be on START_TAG to read next text", this,
372                     null);
373         }
374         int eventType = next();
375         if (eventType == TEXT) {
376             String result = getText();
377             eventType = next();
378             if (eventType != END_TAG) {
379                 throw new XmlPullParserException(
380                         getPositionDescription()
381                                 + ": event TEXT it must be immediately followed by END_TAG",
382                         this, null);
383             }
384             return result;
385         } else if (eventType == END_TAG) {
386             return "";
387         } else {
388             throw new XmlPullParserException(getPositionDescription()
389                     + ": parser must be on START_TAG or TEXT to read text",
390                     this, null);
391         }
392     }
393 
394     @Override
nextTag()395     public int nextTag() throws XmlPullParserException, IOException {
396         int eventType = next();
397         if (eventType == TEXT && isWhitespace()) { // skip whitespace
398             eventType = next();
399         }
400         if (eventType != START_TAG && eventType != END_TAG) {
401             throw new XmlPullParserException(getPositionDescription()
402                     + ": expected start or end tag", this, null);
403         }
404         return eventType;
405     }
406 
407     // AttributeSet implementation
408 
409 
410     @Override
close()411     public void close() {
412         // pass
413     }
414 
415     @Override
getAttributeBooleanValue(int index, boolean defaultValue)416     public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
417         return mAttrib.getAttributeBooleanValue(index, defaultValue);
418     }
419 
420     @Override
getAttributeBooleanValue(String namespace, String attribute, boolean defaultValue)421     public boolean getAttributeBooleanValue(String namespace, String attribute,
422             boolean defaultValue) {
423         return mAttrib.getAttributeBooleanValue(namespace, attribute, defaultValue);
424     }
425 
426     @Override
getAttributeFloatValue(int index, float defaultValue)427     public float getAttributeFloatValue(int index, float defaultValue) {
428         return mAttrib.getAttributeFloatValue(index, defaultValue);
429     }
430 
431     @Override
getAttributeFloatValue(String namespace, String attribute, float defaultValue)432     public float getAttributeFloatValue(String namespace, String attribute, float defaultValue) {
433         return mAttrib.getAttributeFloatValue(namespace, attribute, defaultValue);
434     }
435 
436     @Override
getAttributeIntValue(int index, int defaultValue)437     public int getAttributeIntValue(int index, int defaultValue) {
438         return mAttrib.getAttributeIntValue(index, defaultValue);
439     }
440 
441     @Override
getAttributeIntValue(String namespace, String attribute, int defaultValue)442     public int getAttributeIntValue(String namespace, String attribute, int defaultValue) {
443         return mAttrib.getAttributeIntValue(namespace, attribute, defaultValue);
444     }
445 
446     @Override
getAttributeListValue(int index, String[] options, int defaultValue)447     public int getAttributeListValue(int index, String[] options, int defaultValue) {
448         return mAttrib.getAttributeListValue(index, options, defaultValue);
449     }
450 
451     @Override
getAttributeListValue(String namespace, String attribute, String[] options, int defaultValue)452     public int getAttributeListValue(String namespace, String attribute,
453             String[] options, int defaultValue) {
454         return mAttrib.getAttributeListValue(namespace, attribute, options, defaultValue);
455     }
456 
457     @Override
getAttributeNameResource(int index)458     public int getAttributeNameResource(int index) {
459         return mAttrib.getAttributeNameResource(index);
460     }
461 
462     @Override
getAttributeResourceValue(int index, int defaultValue)463     public int getAttributeResourceValue(int index, int defaultValue) {
464         return mAttrib.getAttributeResourceValue(index, defaultValue);
465     }
466 
467     @Override
getAttributeResourceValue(String namespace, String attribute, int defaultValue)468     public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) {
469         return mAttrib.getAttributeResourceValue(namespace, attribute, defaultValue);
470     }
471 
472     @Override
getAttributeUnsignedIntValue(int index, int defaultValue)473     public int getAttributeUnsignedIntValue(int index, int defaultValue) {
474         return mAttrib.getAttributeUnsignedIntValue(index, defaultValue);
475     }
476 
477     @Override
getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue)478     public int getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue) {
479         return mAttrib.getAttributeUnsignedIntValue(namespace, attribute, defaultValue);
480     }
481 
482     @Override
getClassAttribute()483     public String getClassAttribute() {
484         return mAttrib.getClassAttribute();
485     }
486 
487     @Override
getIdAttribute()488     public String getIdAttribute() {
489         return mAttrib.getIdAttribute();
490     }
491 
492     @Override
getIdAttributeResourceValue(int defaultValue)493     public int getIdAttributeResourceValue(int defaultValue) {
494         return mAttrib.getIdAttributeResourceValue(defaultValue);
495     }
496 
497     @Override
getStyleAttribute()498     public int getStyleAttribute() {
499         return mAttrib.getStyleAttribute();
500     }
501 
502     @Override
503     @Nullable
getResolvedAttributeValue(@ullable String namespace, @NonNull String name)504     public ResourceValue getResolvedAttributeValue(@Nullable String namespace,
505             @NonNull String name) {
506         return mAttrib.getResolvedAttributeValue(namespace, name);
507     }
508 }
509