blob: b477b8fbb5afc791992ac308973b9ec8aac9e382 [file] [log] [blame]
/*
* 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.annotations.NonNull;
import com.google.common.io.Closeables;
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 XML resource file and validating all framework
* attribute references in it. If an error is found, the associated context
* is marked as needing a full AAPT run.
*/
public class ValidatingResourceParser {
private final boolean mIsFramework;
private ScanningContext mContext;
/**
* Creates a new {@link ValidatingResourceParser}
*
* @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 ValidatingResourceParser(
@NonNull ScanningContext context,
boolean isFramework) {
mContext = context;
mIsFramework = isFramework;
}
/**
* Parse the given input and return false if it contains errors, <b>or</b> if
* the context is already tagged as needing a full aapt run.
*
* @param path the full OS path to the file being parsed
* @param input the input stream of the XML to be parsed (will be closed by this method)
* @return true if parsing succeeds and false if it fails
* @throws IOException if reading the contents fails
*/
public boolean parse(final String path, InputStream input)
throws IOException {
// No need to validate framework files
if (mIsFramework) {
Closeables.closeQuietly(input);
return true;
}
if (mContext.needsFullAapt()) {
Closeables.closeQuietly(input);
return false;
}
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(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;
} finally {
Closeables.closeQuietly(input);
}
}
private boolean parse(String path, KXmlParser parser)
throws XmlPullParserException, IOException {
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 (checkForErrors) {
String uri = parser.getAttributeNamespace(i);
if (!mContext.checkValue(uri, attribute, value)) {
mContext.requestFullAapt();
return false;
}
}
}
} else if (event == XmlPullParser.END_DOCUMENT) {
break;
}
}
return true;
}
}