Home | History | Annotate | Download | only in resources
      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      */
     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      */
     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 (at) ...
     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 
    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