/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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.google.dart.tools.core.internal.util;
import java.util.ArrayList;
import java.util.List;
/**
* Instances of the class <code>LibraryReferenceFinder</code> scan a file for potential references
* to one or more Dart libraries.
*
* @coverage dart.tools.core
*/
public class LibraryReferenceFinder {
private static final String COMMENT_START = "<!--";
private static final int COMMENT_START_LENGTH = COMMENT_START.length();
private static final String COMMENT_END = "-->";
private static final int COMMENT_END_LENGTH = COMMENT_END.length();
private static final String TAG_NAME_SCRIPT = "script";
private static final int TAG_NAME_SCRIPT_LENGTH = TAG_NAME_SCRIPT.length();
private static final String TAG_END_SCRIPT = "</script>";
private static final int TAG_END_SCRIPT_LENGTH = TAG_END_SCRIPT.length();
/**
* Find all of the potential library references that are contained in the given HTML source. Note
* that there is no validation done to ensure that the name of a library actually references a
* library within the workspace.
*
* @param htmlSource the HTML source to be searched
* @return the potential library references that were found
*/
public static List<String> findInHTML(String htmlSource) {
LibraryReferenceFinder finder = new LibraryReferenceFinder();
finder.processHTML(htmlSource);
return finder.getLibraryList();
}
/**
* A list containing the paths to the potential libraries that were found.
*/
public List<String> libraryList = new ArrayList<String>();
/**
* Initialize a newly created library reference finder.
*/
public LibraryReferenceFinder() {
super();
}
/**
* Return a list containing the paths to the potential libraries that were found.
*
* @return a list containing the paths to the library names that were found
*/
public List<String> getLibraryList() {
return libraryList;
}
/**
* Process the given HTML source, adding any potential library references that are found to the
* list returned by {@link #getLibraryList()}.
*
* @param htmlSource the HTML source to be searched
*/
public void processHTML(String htmlSource) {
int index = htmlSource.indexOf('<');
while (index >= 0) {
if (htmlSource.startsWith(COMMENT_START, index)) {
index = htmlSource.indexOf(COMMENT_END, index + COMMENT_START_LENGTH);
if (index < 0) {
return;
}
index += COMMENT_END_LENGTH;
} else {
index = skipWhitespace(htmlSource, index + 1);
if (htmlSource.startsWith(TAG_NAME_SCRIPT, index)) {
index += TAG_NAME_SCRIPT_LENGTH;
int endIndex = htmlSource.indexOf('>', index);
if (endIndex < 0) {
return;
}
boolean isDartScript = processScriptTagAttributes(htmlSource.substring(index, endIndex));
if (isDartScript && htmlSource.charAt(endIndex - 1) != '/') {
// If the script tag is well-formed, then it has a body that we also need to process.
endIndex++;
index = htmlSource.indexOf(TAG_END_SCRIPT, endIndex);
if (index < 0) {
return;
}
processScriptBody(htmlSource.substring(endIndex, index));
index += TAG_END_SCRIPT_LENGTH;
} else {
index = endIndex + 1;
}
}
}
index = htmlSource.indexOf('<', index);
}
}
/**
* Extract the name of the file being referenced from the given src location string.
*
* @param srcLocation the value of the src attribute in a script tag
* @return the name of the file being referenced
*/
private String extractFileNameFromSrc(String srcLocation) {
int index = srcLocation.indexOf('/');
if (index >= 0) {
return srcLocation.substring(index + 1);
}
index = srcLocation.indexOf(':');
if (index >= 0) {
return srcLocation.substring(index + 1);
}
return srcLocation;
}
/**
* Return the value of the attribute in the given tag body that starts at the given index and is
* terminated by the given character.
*
* @param tagBody the body of the tag being scanned
* @param startIndex the index of the first character of the attribute value
* @param closingQuote the quote character used to terminate the attribute value
* @return the value of the given attribute
*/
private String getAttributeValue(String tagBody, int startIndex, char closingQuote) {
int endIndex = tagBody.indexOf(closingQuote, startIndex);
if (endIndex < 0) {
return null;
}
return tagBody.substring(startIndex, endIndex);
}
/**
* Return the value of the attribute in the given tag body with the given name, or
* <code>null</code> if there is no attribute with the given name.
*
* @param tagBody the body of the tag being scanned
* @param attributeName the name of the attribute whose value is to be returned
* @return the value of the given attribute
*/
private String getAttributeValue(String tagBody, String attributeName) {
int index = tagBody.indexOf(attributeName);
if (index < 0) {
return null;
}
int length = tagBody.length();
index = skipWhitespace(tagBody, index + attributeName.length());
if (index >= length || tagBody.charAt(index) != '=') {
return null;
}
index = skipWhitespace(tagBody, index + 1);
if (index >= length) {
return null;
}
if (tagBody.charAt(index) == '"') {
return getAttributeValue(tagBody, index + 1, '"');
} else if (tagBody.charAt(index) == '\'') {
return getAttributeValue(tagBody, index + 1, '\'');
}
int endIndex = skipToWhitespace(tagBody, index + 1);
return tagBody.substring(index, endIndex);
}
/**
* Examine the content of a script tag (the portion between the start tag and the end tag) to
* locate any libraries that are being imported, and add them to the list of libraries.
*
* @param scriptBody the content of the script tag to be processed
*/
private void processScriptBody(String scriptBody) {
// TODO(brianwilkerson) Implement this to look for #import directives (once the format is more
// settled).
}
/**
* Examine the attributes from a script tag (the portion between the "<code><script</code>" and
* "<code>></code>") to locate the library being referenced, and add it to the list of
* libraries.
*
* @param scriptTag the script tag to be processed
* @return <code>true</code> if the script is a dart script and the body needs to be examined
*/
private boolean processScriptTagAttributes(String scriptTag) {
String srcLocation = getAttributeValue(scriptTag, "src");
if (srcLocation != null) {
String scriptType = getAttributeValue(scriptTag, "type");
if (scriptType == null || scriptType.equals("text/javascript")
|| scriptType.equals("application/javascript")) {
// Match the "Generate Optimized Javascript" default extension
if (srcLocation.endsWith(".dart.js")) {
String fileName = extractFileNameFromSrc(srcLocation);
fileName = fileName.substring(0, fileName.length() - 3);
libraryList.add(fileName);
}
} else if (scriptType.equals("application/dart")) {
libraryList.add(srcLocation);
return true;
}
}
return false;
}
/**
* Return the index in the given string of the first whitespace character at or after the given
* start index, or the length of the string if there is no whitespace character.
*
* @param string the string being scanned
* @param start the first character to be examined
* @return the index of the first whitespace character
*/
private int skipToWhitespace(String string, int startIndex) {
int length = string.length();
int index = startIndex;
while (index < length && !Character.isWhitespace(string.charAt(index))) {
index++;
}
return index;
}
/**
* Return the index in the given string of the first non-whitespace character at or after the
* given start index, or the length of the string if there is no non-whitespace character.
*
* @param string the string being scanned
* @param startIndex the first character to be examined
* @return the index of the first non-whitespace character
*/
private int skipWhitespace(String string, int startIndex) {
int length = string.length();
int index = startIndex;
while (index < length && Character.isWhitespace(string.charAt(index))) {
index++;
}
return index;
}
}