/* * 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.tools.lint; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.tools.lint.client.api.IssueRegistry; import com.android.tools.lint.client.api.XmlParser; import com.android.tools.lint.detector.api.DefaultPosition; import com.android.tools.lint.detector.api.Location; import com.android.tools.lint.detector.api.Location.Handle; import com.android.tools.lint.detector.api.Position; import com.android.tools.lint.detector.api.XmlContext; import com.android.utils.PositionXmlParser; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.SAXException; import java.io.File; import java.io.UnsupportedEncodingException; /** * A customization of the {@link PositionXmlParser} which creates position * objects that directly extend the lint * {@link com.android.tools.lint.detector.api.Position} class. * <p> * It also catches and reports parser errors as lint errors. */ public class LintCliXmlParser extends XmlParser { @Override public Document parseXml(@NonNull XmlContext context) { String xml = null; try { // Do we need to provide an input stream for encoding? xml = context.getContents(); if (xml != null) { return PositionXmlParser.parse(xml); } } catch (UnsupportedEncodingException e) { context.report( // Must provide an issue since API guarantees that the issue parameter // is valid IssueRegistry.PARSER_ERROR, Location.create(context.file), e.getCause() != null ? e.getCause().getLocalizedMessage() : e.getLocalizedMessage() ); } catch (SAXException e) { Location location = Location.create(context.file); String message = e.getCause() != null ? e.getCause().getLocalizedMessage() : e.getLocalizedMessage(); if (message.startsWith("The processing instruction target matching " + "\"[xX][mM][lL]\" is not allowed.")) { int prologue = xml.indexOf("<?xml "); int comment = xml.indexOf("<!--"); if (prologue != -1 && comment != -1 && comment < prologue) { message = "The XML prologue should appear before, not after, the first XML " + "header/copyright comment. " + message; } } context.report( // Must provide an issue since API guarantees that the issue parameter // is valid IssueRegistry.PARSER_ERROR, location, message ); } catch (Throwable t) { context.log(t, null); } return null; } @NonNull @Override public Location getLocation(@NonNull XmlContext context, @NonNull Node node) { return Location.create(context.file, PositionXmlParser.getPosition(node)); } @NonNull @Override public Location getLocation(@NonNull XmlContext context, @NonNull Node node, int start, int end) { return Location.create(context.file, PositionXmlParser.getPosition(node, start, end)); } @Override @NonNull public Location getNameLocation(@NonNull XmlContext context, @NonNull Node node) { Location location = getLocation(context, node); Position start = location.getStart(); Position end = location.getEnd(); if (start == null || end == null) { return location; } int delta = node instanceof Element ? 1 : 0; // Elements: skip "<" int length = node.getNodeName().length(); int startOffset = start.getOffset() + delta; int startColumn = start.getColumn() + delta; return Location.create(location.getFile(), new DefaultPosition(start.getLine(), startColumn, startOffset), new DefaultPosition(end.getLine(), startColumn + length, startOffset + length)); } @Override @NonNull public Location getValueLocation(@NonNull XmlContext context, @NonNull Attr node) { Location location = getLocation(context, node); Position start = location.getStart(); Position end = location.getEnd(); if (start == null || end == null) { return location; } int totalLength = end.getOffset() - start.getOffset(); int length = node.getValue().length(); int delta = totalLength - 1 - length; int startOffset = start.getOffset() + delta; int startColumn = start.getColumn() + delta; return Location.create(location.getFile(), new DefaultPosition(start.getLine(), startColumn, startOffset), new DefaultPosition(end.getLine(), startColumn + length, startOffset + length)); } @NonNull @Override public Handle createLocationHandle(@NonNull XmlContext context, @NonNull Node node) { return new LocationHandle(context.file, node); } @Override public int getNodeStartOffset(@NonNull XmlContext context, @NonNull Node node) { return PositionXmlParser.getPosition(node).getStartOffset(); } @Override public int getNodeEndOffset(@NonNull XmlContext context, @NonNull Node node) { return PositionXmlParser.getPosition(node).getEndOffset(); } /* Handle for creating DOM positions cheaply and returning full fledged locations later */ private class LocationHandle implements Handle { private final File mFile; private final Node mNode; private Object mClientData; public LocationHandle(File file, Node node) { mFile = file; mNode = node; } @NonNull @Override public Location resolve() { return Location.create(mFile, PositionXmlParser.getPosition(mNode)); } @Override public void setClientData(@Nullable Object clientData) { mClientData = clientData; } @Override @Nullable public Object getClientData() { return mClientData; } } }