aboutsummaryrefslogtreecommitdiffstats
path: root/ide_common/src/com/android/ide/common/resources/IdResourceParser.java
blob: 1de664e0460fc3c5dc44ff0b9332d94360a1d213 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ide.common.resources;

import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.ValueResourceParser.IValueResourceRepository;
import com.android.resources.ResourceType;

import org.kxml2.io.KXmlParser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Parser for scanning an id-generating resource file such as a layout or a menu
 * file, which registers any ids it encounters with an
 * {@link IValueResourceRepository}, and which registers errors with a
 * {@link ScanningContext}.
 */
public class IdResourceParser {
    private final IValueResourceRepository mRepository;
    private final boolean mIsFramework;
    private ScanningContext mContext;

    /**
     * Creates a new {@link IdResourceParser}
     *
     * @param repository value repository for registering resource declaration
     * @param context a context object with state for the current update, such
     *            as a place to stash errors encountered
     * @param isFramework true if scanning a framework resource
     */
    public IdResourceParser(IValueResourceRepository repository, ScanningContext context,
            boolean isFramework) {
        mRepository = repository;
        mContext = context;
        mIsFramework = isFramework;
    }

    /**
     * Parse the given input and register ids with the given
     * {@link IValueResourceRepository}.
     *
     * @param type the type of resource being scanned
     * @param path the full OS path to the file being parsed
     * @param input the input stream of the XML to be parsed
     * @return true if parsing succeeds and false if it fails
     * @throws IOException if reading the contents fails
     */
    public boolean parse(ResourceType type, final String path, InputStream input)
            throws IOException {
        KXmlParser parser = new KXmlParser();
        try {
            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);

            if (input instanceof FileInputStream) {
                input = new BufferedInputStream(input);
            }
            parser.setInput(input, "UTF-8"); //$NON-NLS-1$

            return parse(type, path, parser);
        } catch (XmlPullParserException e) {
            String message = e.getMessage();

            // Strip off position description
            int index = message.indexOf("(position:"); //$NON-NLS-1$ (Hardcoded in KXml)
            if (index != -1) {
                message = message.substring(0, index);
            }

            String error = String.format("%1$s:%2$d: Error: %3$s", //$NON-NLS-1$
                    path, parser.getLineNumber(), message);
            mContext.addError(error);
            return false;
        } catch (RuntimeException e) {
            // Some exceptions are thrown by the KXmlParser that are not XmlPullParserExceptions,
            // such as this one:
            //    java.lang.RuntimeException: Undefined Prefix: w in org.kxml2.io.KXmlParser@...
            //        at org.kxml2.io.KXmlParser.adjustNsp(Unknown Source)
            //        at org.kxml2.io.KXmlParser.parseStartTag(Unknown Source)
            String message = e.getMessage();
            String error = String.format("%1$s:%2$d: Error: %3$s", //$NON-NLS-1$
                    path, parser.getLineNumber(), message);
            mContext.addError(error);
            return false;
        }
    }

    private boolean parse(ResourceType type, String path, KXmlParser parser)
            throws XmlPullParserException, IOException {
        boolean valid = true;
        ResourceRepository resources = mContext.getRepository();
        boolean checkForErrors = !mIsFramework && !mContext.needsFullAapt();

        while (true) {
            int event = parser.next();
            if (event == XmlPullParser.START_TAG) {
                for (int i = 0, n = parser.getAttributeCount(); i < n; i++) {
                    String attribute = parser.getAttributeName(i);
                    String value = parser.getAttributeValue(i);
                    assert value != null : attribute;

                    if (value.startsWith("@")) {       //$NON-NLS-1$
                        // Gather IDs
                        if (value.startsWith("@+")) {  //$NON-NLS-1$
                            // Strip out the @+id/ or @+android:id/ section
                            String id = value.substring(value.indexOf('/') + 1);
                            ResourceValue newId = new ResourceValue(ResourceType.ID, id,
                                    mIsFramework);
                            mRepository.addResourceValue(newId);
                        } else if (checkForErrors){
                            // Validate resource references (unless we're scanning a framework
                            // resource or if we've already scheduled a full aapt run)
                            boolean exists = resources.hasResourceItem(value);
                            if (!exists && !mRepository.hasResourceValue(ResourceType.ID,
                                    value.substring(value.indexOf('/') + 1))) {
                                String error = String.format(
                                    // Don't localize because the exact pattern matches AAPT's
                                    // output which has hardcoded regexp matching in
                                    // AaptParser.
                                    "%1$s:%2$d: Error: No resource found that matches " + //$NON-NLS-1$
                                    "the given name (at '%3$s' with value '%4$s')",       //$NON-NLS-1$
                                            path, parser.getLineNumber(),
                                            attribute, value);
                                mContext.addError(error);
                                valid = false;
                            }
                        }
                    }
                }
            } else if (event == XmlPullParser.END_DOCUMENT) {
                break;
            }
        }

        return valid;
    }
}