/* * 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.SdkConstants; import com.android.annotations.NonNull; import com.android.ide.common.rendering.api.ResourceValue; import com.android.ide.common.resources.ValueResourceParser.IValueResourceRepository; import com.android.resources.ResourceType; 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 id-generating resource file such as a layout or a menu * file, which registers any ids it encounters with an * {@link IValueResourceRepository}, and which registers errors with a * {@link ScanningContext}. */ public class IdResourceParser { private final IValueResourceRepository mRepository; private final boolean mIsFramework; private ScanningContext mContext; /** * Creates a new {@link IdResourceParser} * * @param repository value repository for registering resource declaration * @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 IdResourceParser( @NonNull IValueResourceRepository repository, @NonNull ScanningContext context, boolean isFramework) { mRepository = repository; mContext = context; mIsFramework = isFramework; } /** * Parse the given input and register ids with the given * {@link IValueResourceRepository}. * * @param type the type of resource being scanned * @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(ResourceType type, final String path, InputStream input) throws IOException { KXmlParser parser = new KXmlParser(); try { parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); if (input instanceof FileInputStream) { input = new BufferedInputStream(input); } parser.setInput(input, SdkConstants.UTF_8); return parse(type, 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 { try { Closeables.close(input, true /* swallowIOException */); } catch (IOException e) { // cannot happen } } } private boolean parse(ResourceType type, String path, KXmlParser parser) throws XmlPullParserException, IOException { boolean valid = true; 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(); checkForErrors = false; valid = false; } } if (value.startsWith("@+")) { //$NON-NLS-1$ // Strip out the @+id/ or @+android:id/ section String id = value.substring(value.indexOf('/') + 1); ResourceValue newId = new ResourceValue(ResourceType.ID, id, mIsFramework); mRepository.addResourceValue(newId); } } } else if (event == XmlPullParser.END_DOCUMENT) { break; } } return valid; } }