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