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