blob: 61d3a377d210acf85c8db380c2887d9b82146129 [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.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;
}
}
}