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.annotations.NonNull; 20 21 import org.kxml2.io.KXmlParser; 22 import org.xmlpull.v1.XmlPullParser; 23 import org.xmlpull.v1.XmlPullParserException; 24 25 import java.io.BufferedInputStream; 26 import java.io.FileInputStream; 27 import java.io.IOException; 28 import java.io.InputStream; 29 30 /** 31 * Parser for scanning an XML resource file and validating all framework 32 * attribute references in it. If an error is found, the associated context 33 * is marked as needing a full AAPT run. 34 */ 35 public class ValidatingResourceParser { 36 private final boolean mIsFramework; 37 private ScanningContext mContext; 38 39 /** 40 * Creates a new {@link ValidatingResourceParser} 41 * 42 * @param context a context object with state for the current update, such 43 * as a place to stash errors encountered 44 * @param isFramework true if scanning a framework resource 45 */ ValidatingResourceParser( @onNull ScanningContext context, boolean isFramework)46 public ValidatingResourceParser( 47 @NonNull ScanningContext context, 48 boolean isFramework) { 49 mContext = context; 50 mIsFramework = isFramework; 51 } 52 53 /** 54 * Parse the given input and return false if it contains errors, <b>or</b> if 55 * the context is already tagged as needing a full aapt run. 56 * 57 * @param path the full OS path to the file being parsed 58 * @param input the input stream of the XML to be parsed 59 * @return true if parsing succeeds and false if it fails 60 * @throws IOException if reading the contents fails 61 */ parse(final String path, InputStream input)62 public boolean parse(final String path, InputStream input) 63 throws IOException { 64 // No need to validate framework files 65 if (mIsFramework) { 66 return true; 67 } 68 if (mContext.needsFullAapt()) { 69 return false; 70 } 71 72 KXmlParser parser = new KXmlParser(); 73 try { 74 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); 75 76 if (input instanceof FileInputStream) { 77 input = new BufferedInputStream(input); 78 } 79 parser.setInput(input, "UTF-8"); //$NON-NLS-1$ 80 81 return parse(path, parser); 82 } catch (XmlPullParserException e) { 83 String message = e.getMessage(); 84 85 // Strip off position description 86 int index = message.indexOf("(position:"); //$NON-NLS-1$ (Hardcoded in KXml) 87 if (index != -1) { 88 message = message.substring(0, index); 89 } 90 91 String error = String.format("%1$s:%2$d: Error: %3$s", //$NON-NLS-1$ 92 path, parser.getLineNumber(), message); 93 mContext.addError(error); 94 return false; 95 } catch (RuntimeException e) { 96 // Some exceptions are thrown by the KXmlParser that are not XmlPullParserExceptions, 97 // such as this one: 98 // java.lang.RuntimeException: Undefined Prefix: w in org.kxml2.io.KXmlParser@... 99 // at org.kxml2.io.KXmlParser.adjustNsp(Unknown Source) 100 // at org.kxml2.io.KXmlParser.parseStartTag(Unknown Source) 101 String message = e.getMessage(); 102 String error = String.format("%1$s:%2$d: Error: %3$s", //$NON-NLS-1$ 103 path, parser.getLineNumber(), message); 104 mContext.addError(error); 105 return false; 106 } 107 } 108 parse(String path, KXmlParser parser)109 private boolean parse(String path, KXmlParser parser) 110 throws XmlPullParserException, IOException { 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 (checkForErrors) { 122 String uri = parser.getAttributeNamespace(i); 123 if (!mContext.checkValue(uri, attribute, value)) { 124 mContext.requestFullAapt(); 125 return false; 126 } 127 } 128 } 129 } else if (event == XmlPullParser.END_DOCUMENT) { 130 break; 131 } 132 } 133 134 return true; 135 } 136 } 137