/* * 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.Location; import com.android.tools.lint.detector.api.Location.Handle; import com.android.tools.lint.detector.api.XmlContext; import com.android.utils.PositionXmlParser; import org.w3c.dom.Document; 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 { private final PositionXmlParser mParser = new PositionXmlParser() { @NonNull @Override protected OffsetPosition createPosition(int line, int column, int offset) { return new OffsetPosition(line, column, offset); } }; @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 mParser.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) { OffsetPosition pos = (OffsetPosition) mParser.getPosition(node, -1, -1); if (pos != null) { return Location.create(context.file, pos, (OffsetPosition) pos.getEnd()); } return Location.create(context.file); } @NonNull @Override public Location getLocation(@NonNull XmlContext context, @NonNull Node node, int start, int end) { OffsetPosition pos = (OffsetPosition) mParser.getPosition(node, start, end); if (pos != null) { return Location.create(context.file, pos, (OffsetPosition) pos.getEnd()); } return Location.create(context.file); } @NonNull @Override public Handle createLocationHandle(@NonNull XmlContext context, @NonNull Node node) { return new LocationHandle(context.file, node); } private static class OffsetPosition extends com.android.tools.lint.detector.api.Position implements PositionXmlParser.Position { /** The line number (0-based where the first line is line 0) */ private final int mLine; /** * The column number (where the first character on the line is 0), or -1 if * unknown */ private final int mColumn; /** The character offset */ private final int mOffset; /** * Linked position: for a begin offset this will point to the end * offset, and for an end offset this will be null */ private PositionXmlParser.Position mEnd; /** * Creates a new {@link OffsetPosition} * * @param line the 0-based line number, or -1 if unknown * @param column the 0-based column number, or -1 if unknown * @param offset the offset, or -1 if unknown */ public OffsetPosition(int line, int column, int offset) { mLine = line; mColumn = column; mOffset = offset; } @Override public int getLine() { return mLine; } @Override public int getOffset() { return mOffset; } @Override public int getColumn() { return mColumn; } @Override public PositionXmlParser.Position getEnd() { return mEnd; } @Override public void setEnd(@NonNull PositionXmlParser.Position end) { mEnd = end; } @Override public String toString() { return "OffsetPosition [line=" + mLine + ", column=" + mColumn + ", offset=" + mOffset + ", end=" + mEnd + ']'; } } @Override public int getNodeStartOffset(@NonNull XmlContext context, @NonNull Node node) { OffsetPosition pos = (OffsetPosition) mParser.getPosition(node, -1, -1); if (pos != null) { return pos.getOffset(); } return -1; } @Override public int getNodeEndOffset(@NonNull XmlContext context, @NonNull Node node) { OffsetPosition pos = (OffsetPosition) mParser.getPosition(node, -1, -1); if (pos != null) { PositionXmlParser.Position end = pos.getEnd(); if (end != null) { return end.getOffset(); } } return -1; } /* 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() { OffsetPosition pos = (OffsetPosition) mParser.getPosition(mNode); if (pos != null) { return Location.create(mFile, pos, (OffsetPosition) pos.getEnd()); } return Location.create(mFile); } @Override public void setClientData(@Nullable Object clientData) { mClientData = clientData; } @Override @Nullable public Object getClientData() { return mClientData; } } }