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