• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.ResourceValue;
20 import com.android.ide.common.resources.ValueResourceParser.IValueResourceRepository;
21 import com.android.resources.ResourceType;
22 
23 import org.kxml2.io.KXmlParser;
24 import org.xmlpull.v1.XmlPullParser;
25 import org.xmlpull.v1.XmlPullParserException;
26 
27 import java.io.BufferedInputStream;
28 import java.io.FileInputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 
32 /**
33  * Parser for scanning an id-generating resource file such as a layout or a menu
34  * file, which registers any ids it encounters with an
35  * {@link IValueResourceRepository}, and which registers errors with a
36  * {@link ScanningContext}.
37  */
38 public class IdResourceParser {
39     private final IValueResourceRepository mRepository;
40     private final boolean mIsFramework;
41     private ScanningContext mContext;
42 
43     /**
44      * Creates a new {@link IdResourceParser}
45      *
46      * @param repository value repository for registering resource declaration
47      * @param context a context object with state for the current update, such
48      *            as a place to stash errors encountered
49      * @param isFramework true if scanning a framework resource
50      */
IdResourceParser(IValueResourceRepository repository, ScanningContext context, boolean isFramework)51     public IdResourceParser(IValueResourceRepository repository, ScanningContext context,
52             boolean isFramework) {
53         mRepository = repository;
54         mContext = context;
55         mIsFramework = isFramework;
56     }
57 
58     /**
59      * Parse the given input and register ids with the given
60      * {@link IValueResourceRepository}.
61      *
62      * @param type the type of resource being scanned
63      * @param path the full OS path to the file being parsed
64      * @param input the input stream of the XML to be parsed
65      * @return true if parsing succeeds and false if it fails
66      * @throws IOException if reading the contents fails
67      */
parse(ResourceType type, final String path, InputStream input)68     public boolean parse(ResourceType type, final String path, InputStream input)
69             throws IOException {
70         KXmlParser parser = new KXmlParser();
71         try {
72             parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
73 
74             if (input instanceof FileInputStream) {
75                 input = new BufferedInputStream(input);
76             }
77             parser.setInput(input, "UTF-8"); //$NON-NLS-1$
78 
79             return parse(type, path, parser);
80         } catch (XmlPullParserException e) {
81             String message = e.getMessage();
82 
83             // Strip off position description
84             int index = message.indexOf("(position:"); //$NON-NLS-1$ (Hardcoded in KXml)
85             if (index != -1) {
86                 message = message.substring(0, index);
87             }
88 
89             String error = String.format("%1$s:%2$d: Error: %3$s", //$NON-NLS-1$
90                     path, parser.getLineNumber(), message);
91             mContext.addError(error);
92             return false;
93         } catch (RuntimeException e) {
94             // Some exceptions are thrown by the KXmlParser that are not XmlPullParserExceptions,
95             // such as this one:
96             //    java.lang.RuntimeException: Undefined Prefix: w in org.kxml2.io.KXmlParser@...
97             //        at org.kxml2.io.KXmlParser.adjustNsp(Unknown Source)
98             //        at org.kxml2.io.KXmlParser.parseStartTag(Unknown Source)
99             String message = e.getMessage();
100             String error = String.format("%1$s:%2$d: Error: %3$s", //$NON-NLS-1$
101                     path, parser.getLineNumber(), message);
102             mContext.addError(error);
103             return false;
104         }
105     }
106 
parse(ResourceType type, String path, KXmlParser parser)107     private boolean parse(ResourceType type, String path, KXmlParser parser)
108             throws XmlPullParserException, IOException {
109         boolean valid = true;
110         ResourceRepository resources = mContext.getRepository();
111         boolean checkForErrors = !mIsFramework && !mContext.needsFullAapt();
112 
113         while (true) {
114             int event = parser.next();
115             if (event == XmlPullParser.START_TAG) {
116                 for (int i = 0, n = parser.getAttributeCount(); i < n; i++) {
117                     String attribute = parser.getAttributeName(i);
118                     String value = parser.getAttributeValue(i);
119                     assert value != null : attribute;
120 
121                     if (value.startsWith("@")) {       //$NON-NLS-1$
122                         // Gather IDs
123                         if (value.startsWith("@+")) {  //$NON-NLS-1$
124                             // Strip out the @+id/ or @+android:id/ section
125                             String id = value.substring(value.indexOf('/') + 1);
126                             ResourceValue newId = new ResourceValue(ResourceType.ID, id,
127                                     mIsFramework);
128                             mRepository.addResourceValue(newId);
129                         } else if (checkForErrors){
130                             // Validate resource references (unless we're scanning a framework
131                             // resource or if we've already scheduled a full aapt run)
132                             boolean exists = resources.hasResourceItem(value);
133                             if (!exists) {
134                                 String error = String.format(
135                                     // Don't localize because the exact pattern matches AAPT's
136                                     // output which has hardcoded regexp matching in
137                                     // AaptParser.
138                                     "%1$s:%2$d: Error: No resource found that matches " + //$NON-NLS-1$
139                                     "the given name (at '%3$s' with value '%4$s')",       //$NON-NLS-1$
140                                             path, parser.getLineNumber(),
141                                             attribute, value);
142                                 mContext.addError(error);
143                                 valid = false;
144                             }
145                         }
146                     }
147                 }
148             } else if (event == XmlPullParser.END_DOCUMENT) {
149                 break;
150             }
151         }
152 
153         return valid;
154     }
155 }
156