1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tools.lint; 18 19 import com.android.tools.lint.api.IDomParser; 20 import com.android.tools.lint.detector.api.Context; 21 import com.android.tools.lint.detector.api.Position; 22 23 import org.w3c.dom.Attr; 24 import org.w3c.dom.Document; 25 import org.w3c.dom.Element; 26 import org.w3c.dom.Node; 27 import org.xml.sax.Attributes; 28 import org.xml.sax.InputSource; 29 import org.xml.sax.Locator; 30 import org.xml.sax.SAXException; 31 import org.xml.sax.XMLReader; 32 import org.xml.sax.helpers.AttributesImpl; 33 import org.xml.sax.helpers.XMLFilterImpl; 34 import org.xml.sax.helpers.XMLReaderFactory; 35 36 import java.io.StringReader; 37 38 import javax.xml.transform.Transformer; 39 import javax.xml.transform.TransformerConfigurationException; 40 import javax.xml.transform.TransformerException; 41 import javax.xml.transform.TransformerFactory; 42 import javax.xml.transform.dom.DOMResult; 43 import javax.xml.transform.sax.SAXSource; 44 45 /** A simple XML parser which can store and retrieve line:column information for the nodes */ 46 public class PositionXmlParser implements IDomParser { 47 private static final String ATTR_LOCATION = "location"; //$NON-NLS-1$ 48 private static final String PRIVATE_NAMESPACE = "http://tools.android.com"; //$NON-NLS-1$ 49 private static final String PRIVATE_PREFIX = "temp"; //$NON-NLS-1$ 50 51 public Document parse(Context context) { 52 InputSource input = new InputSource(new StringReader(context.getContents())); 53 try { 54 Filter filter = new Filter(XMLReaderFactory.createXMLReader()); 55 Transformer transformer = TransformerFactory.newInstance().newTransformer(); 56 DOMResult result = new DOMResult(); 57 transformer.transform(new SAXSource(filter, input), result); 58 return (Document) result.getNode(); 59 } catch (SAXException e) { 60 // The file doesn't parse: not an exception. Infrastructure will log a warning 61 // that this file was not analyzed. 62 return null; 63 } catch (TransformerConfigurationException e) { 64 context.toolContext.log(e, null); 65 } catch (TransformerException e) { 66 context.toolContext.log(e, null); 67 } 68 69 return null; 70 } 71 72 private static class Filter extends XMLFilterImpl { 73 private Locator mLocator; 74 75 Filter(XMLReader reader) { 76 super(reader); 77 } 78 79 @Override 80 public void setDocumentLocator(Locator locator) { 81 super.setDocumentLocator(locator); 82 this.mLocator = locator; 83 } 84 85 @Override 86 public void startElement(String uri, String localName, String qualifiedName, 87 Attributes attributes) throws SAXException { 88 int lineno = mLocator.getLineNumber(); 89 int column = mLocator.getColumnNumber(); 90 String location = Integer.toString(lineno) + ':' + Integer.toString(column); 91 92 // Modify attributes parameter to a copy that includes our private attribute 93 AttributesImpl wrapper = new AttributesImpl(attributes); 94 wrapper.addAttribute(PRIVATE_NAMESPACE, ATTR_LOCATION, 95 PRIVATE_PREFIX + ':' + ATTR_LOCATION, "CDATA", location); //$NON-NLS-1$ 96 97 super.startElement(uri, localName, qualifiedName, wrapper); 98 } 99 } 100 101 public Position getStartPosition(Context context, Node node) { 102 if (node instanceof Attr) { 103 Attr attr = (Attr) node; 104 node = attr.getOwnerElement(); 105 } 106 if (node instanceof Element) { 107 Attr attribute = ((Element) node).getAttributeNodeNS(PRIVATE_NAMESPACE, ATTR_LOCATION); 108 if (attribute != null) { 109 String position = attribute.getValue(); 110 int separator = position.indexOf(':'); 111 int line = Integer.parseInt(position.substring(0, separator)); 112 int column = Integer.parseInt(position.substring(separator + 1)); 113 return new OffsetPosition(line, column, -1); 114 } 115 } 116 117 return null; 118 } 119 120 public Position getEndPosition(Context context, Node node) { 121 // TODO: Currently unused 122 return null; 123 } 124 125 private static class OffsetPosition extends Position { 126 /** The line number (0-based where the first line is line 0) */ 127 private final int mLine; 128 129 /** 130 * The column number (where the first character on the line is 0), or -1 if 131 * unknown 132 */ 133 private final int mColumn; 134 135 /** The character offset */ 136 private final int mOffset; 137 138 /** 139 * Creates a new {@link Position} 140 * 141 * @param line the 0-based line number, or -1 if unknown 142 * @param column the 0-based column number, or -1 if unknown 143 * @param offset the offset, or -1 if unknown 144 */ 145 public OffsetPosition(int line, int column, int offset) { 146 this.mLine = line; 147 this.mColumn = column; 148 this.mOffset = offset; 149 } 150 151 @Override 152 public int getLine() { 153 return mLine; 154 } 155 156 @Override 157 public int getOffset() { 158 return mOffset; 159 } 160 161 @Override 162 public int getColumn() { 163 return mColumn; 164 } 165 } 166 } 167