package pt.utl.ist.fenix.tools.html; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Stack; import java.util.Map.Entry; import org.apache.commons.lang.StringUtils; import pt.utl.ist.fenix.tools.util.StringAppender; public class JspHtmlValidator { private static int numFiles = 0; private static int numDirs = 0; private static int jspFiles = 0; private static int htmlFiles = 0; private static int cssFiles = 0; private static int imageFiles = 0; private static int jsFiles = 0; private static int textFiles = 0; private static int soundFiles = 0; private static int otherFiles = 0; private static int writtenFiles = 0; private static int fixedTags = 0; private static int skippedTags = 0; private static int alreadyCorrectTags = 0; private static Map altKeys = new HashMap(); public static void main(String[] args) { final String projectDir = args[0]; ///home/marvin/workspace/fenix_head"; try { long start = System.currentTimeMillis(); validate(new File(projectDir + "/jsp")); long end = System.currentTimeMillis(); generateAltKeyProperties(projectDir + "/config/resources", "HtmlAltResources"); System.out.println("Process took: " + (end - start) + " ms."); } catch (Throwable t) { t.printStackTrace(); } finally { System.out.println("\nProcessed " + numDirs + " dirs."); System.out.println("Processed " + numFiles + " files."); System.out.println(" jsp: " + jspFiles); System.out.println(" html: " + htmlFiles); System.out.println(" css: " + cssFiles); System.out.println(" image: " + imageFiles); System.out.println(" javascript: " + jsFiles); System.out.println(" text: " + textFiles); System.out.println(" sound: " + soundFiles); System.out.println(" other: " + otherFiles); System.out.println("Initially " + alreadyCorrectTags + " correct tags."); System.out.println("Wrote " + writtenFiles + " files."); System.out.println("Created " + altKeys.size() + " distinct keys"); System.out.println("Fixed " + fixedTags + " tags."); System.out.println("Skipped " + skippedTags + " tags."); System.exit(0); } } private static void generateAltKeyProperties(final String destinationDirPath, final String bundleName) throws IOException { generateAltKeyProperties(destinationDirPath, bundleName, "pt"); generateAltKeyProperties(destinationDirPath, bundleName, "en"); } private static void generateAltKeyProperties(final String destinationDirPath, final String bundleName, final String language) throws IOException { final String filename = StringAppender.append(destinationDirPath, "/", bundleName, "_", language, ".properties"); final File file = new File(filename); final Properties properties; if (file.exists()) { properties = PropertyBeutifier.loadProperties(file); } else { properties = new Properties(); file.createNewFile(); } for (final Entry altKeyEntry : altKeys.entrySet()) { final String key = altKeyEntry.getKey(); if (!properties.containsKey(key)) { final String value = altKeyEntry.getValue(); properties.put(key, value); } } final FileOutputStream fileOutputStream = new FileOutputStream(file, false); properties.store(fileOutputStream, null); fileOutputStream.close(); } private static void validate(final File file) throws IOException { if (file != null && file.exists()) { if (file.isDirectory()) { numDirs++; for (final File subFile : file.listFiles()) { validate(subFile); } } else if (file.isFile()) { numFiles++; try { validateFile(file); } catch (StringIndexOutOfBoundsException ex) { throw ex; } } else { throw new IllegalArgumentException("unknown.file.type: " + file); } } } private static void validateFile(final File file) throws IOException { final String filename = file.getName(); final String extension = StringUtils.substringAfterLast(filename, "."); if (extension == null || extension.length() == 0) { otherFiles++; } else if (extension.equalsIgnoreCase("jsp")) { jspFiles++; validateJSPFile(file); } else if (extension.equalsIgnoreCase("css")) { cssFiles++; } else if (extension.equalsIgnoreCase("html") || extension.equalsIgnoreCase("htm")) { htmlFiles++; } else if (extension.equalsIgnoreCase("gif") || extension.equalsIgnoreCase("jpg") || extension.equalsIgnoreCase("png") || extension.equalsIgnoreCase("bmp")) { imageFiles++; } else if (extension.equalsIgnoreCase("js")) { jsFiles++; } else if (extension.equalsIgnoreCase("txt") || extension.equalsIgnoreCase("properties")) { textFiles++; } else if (extension.equalsIgnoreCase("mid")) { soundFiles++; } else { otherFiles++; } } private static void validateJSPFile(final File file) throws IOException { final String originalFileContents = readFile(file); final StringBuilder stringBuilder = new StringBuilder(); final int offset = validateStrutsHeader(stringBuilder, originalFileContents); validateJSPFile(stringBuilder, originalFileContents, offset); final String processedFileContents = repaceTDWithTH(stringBuilder.toString(), file.getAbsolutePath()); if (!processedFileContents.equals(originalFileContents)) { writtenFiles++; final String contentsToWrite = injectJSFLoadBundles(processedFileContents); writeFile(file, contentsToWrite); } } private static String repaceTDWithTH(final String contents, final String filename) { int offset = contents.indexOf(" tags."); return contents; } buffer.append(contents.substring(previousOffset, offset)); final int cssClassIndex = contents.indexOf("listClasses-header", offset + 1); final int tdCloseIndex = findTagTermination(contents, offset + 1, '>'); final int tdEndIndex = contents.indexOf(" 0 && indexOfHtmlTrueAttribute1 == -1 && indexOfHtmlTrueAttribute2 == -1 && indexOfXHtmlTag == -1) { final int indexOfNewLine = contents.indexOf('\n', indexOfStrutsHtmlTld); final int indexOfReturn = contents.indexOf('\r', indexOfStrutsHtmlTld); final int indexOfEndOfLine = indexOfNewLine != -1 && indexOfReturn != -1 ? Math.min(indexOfNewLine, indexOfReturn) : indexOfNewLine != -1 ? indexOfNewLine : indexOfReturn; if (indexOfEndOfLine > indexOfStrutsHtmlTld) { buffer.append(contents.substring(0, indexOfEndOfLine + 1)); buffer.append(""); return indexOfEndOfLine; } } return 0; } private static String injectJSFLoadBundles(final String contents) { final int indexOfJsfsHtmlTld = contents.indexOf("html_basic.tld"); if (0 <= indexOfJsfsHtmlTld) { final int indexOfFTViewTag = contents.indexOf(""); buffer.append(contents.substring(nextNewLine)); return buffer.toString(); } } } return contents; } private static int findNextNewLine(String contents, int offset) { final int newLinePos = contents.indexOf('\n', offset); final int returnPos = contents.indexOf('\r', offset); if (0 <= newLinePos && 0 <= returnPos) { return Math.min(newLinePos, returnPos); } else if (0 <= newLinePos) { return newLinePos; } else { return returnPos; } } private final static String[] TAGS = new String[] { "html:button", "html:cancel", "html:checkbox", "html:file", "html:hidden", "html:image", "html:img", "html:multibox", "html:password", "html:radio", "html:reset", // "html:select", "html:submit", "html:text", "html:textarea", "input", "h:commandButton", //"h:commandLink", //"h:inputHidden", "h:inputSecret", "h:inputText" //"h:inputTextarea", //"h:selectBooleanCheckbox", //"h:selectManyCheckbox", //"h:selectManyListbox", //"h:selectManyMenu", //"h:selectOneListbox", //"h:selectOneMenu", //"h:selectOneRadio" }; private static int findToken(final String contents, final int offset) { final int[] tagPositions = new int[TAGS.length]; for (int i = 0; i < TAGS.length; i++) { tagPositions[i] = findIndexOf(contents, offset, "<" + TAGS[i]); } return min(tagPositions); } private static String determineTag(final String contents, final int offset) { for (final String tag : TAGS) { if (isTag(contents, offset, tag)) { return tag; } } return null; } private static void validateJSPFile(final StringBuilder buffer, final String contents, final int offset) { final int tokenIndex = findToken(contents, offset); if (tokenIndex >= 0) { buffer.append(contents.substring(offset, tokenIndex + 1)); final int nextOffSet; final String tag = determineTag(contents, tokenIndex + 1); if (tag == null) { nextOffSet = tokenIndex + 2; buffer.append(contents.substring(tokenIndex + 1, nextOffSet)); } else { nextOffSet = processTag(buffer, contents, tokenIndex + 1, tag); } validateJSPFile(buffer, contents, nextOffSet); } else { buffer.append(contents.substring(offset)); } } private static boolean isTag(final String contents, final int offset, final String prefix) { final int nextCharPos = offset + prefix.length(); if (contents.length() >= nextCharPos + 1) { char nextChar = contents.charAt(nextCharPos); return contents.startsWith(prefix, offset) && (nextChar == ' ' || nextChar == '\t' || nextChar == '\n'); } return false; } private static int processTag(final StringBuilder buffer, final String contents, final int offset, final String tag) { final int tagTerminationIndex = findTagTermination(contents, offset + tag.length(), '>'); final int nextIndex; if (tagTerminationIndex > offset) { nextIndex = tagTerminationIndex; if (containsAlternative(contents, offset, tagTerminationIndex)) { alreadyCorrectTags++; buffer.append(contents.substring(offset, tagTerminationIndex)); } else { final String whereToLookForLabel; if (tag.startsWith("html:")) { whereToLookForLabel = "property="; } else if (tag.startsWith("h:")) { whereToLookForLabel = "value="; } else { whereToLookForLabel = "name="; } final String propertyName = findPropertyName(contents, offset, whereToLookForLabel, tagTerminationIndex); final String tagPropertyNamePrefix = getTagPropertyNamePrefix(tag); final String normalizedPropertyValue = getPropertyName(propertyName, tag); final char quoteSymbol = findQuoteSymbol(contents, offset); final char antiQuoteSymbol = findAntiQuoteSymbol(quoteSymbol); buffer.append(tag); if (isCalculatedValue(normalizedPropertyValue)) { buffer.append(" alt="); buffer.append(quoteSymbol); buffer.append(normalizedPropertyValue); } else { if (tag.startsWith("html:")) { buffer.append(" bundle=\"HTMLALT_RESOURCES\" altKey="); } else if (tag.startsWith("h:")) { buffer.append(" alt="); } else { buffer.append(" alt="); } buffer.append(quoteSymbol); final String efectivePropertyName = StringAppender.append(tagPropertyNamePrefix, ".", normalizedPropertyValue); altKeys.put(efectivePropertyName, normalizedPropertyValue); if (tag.startsWith("h:")) { buffer.append("#{htmlAltBundle["); buffer.append(antiQuoteSymbol); } buffer.append(efectivePropertyName); if (tag.startsWith("h:")) { buffer.append(antiQuoteSymbol); buffer.append("]}"); } } buffer.append(quoteSymbol); buffer.append(contents.substring(offset + tag.length(), tagTerminationIndex)); fixedTags++; } } else { nextIndex = offset + 1; buffer.append(contents.substring(offset, nextIndex)); } return nextIndex; } private static char findAntiQuoteSymbol(final char quote) { if (quote == '"') { return '\''; } else if (quote == '\'') { return '"'; } else { throw new Error("Unknown quote: " + quote); } } private static char findQuoteSymbol(final String contents, final int offset) { for (int i = offset; i < contents.length(); i++) { final char c = contents.charAt(i); if (c == '"' || c == '\'') { return c; } } throw new Error("No quotes found!"); } private static boolean isCalculatedValue(final String normalizedPropertyValue) { return normalizedPropertyValue.indexOf("<%=") >= 0; } private static String getPropertyName(final String propertyName, final String tag) { if (propertyName == null) { return getTagPropertyNamePrefix(tag); } else { if (0 <= propertyName.indexOf("#")) { final int dotIndex = propertyName.lastIndexOf('.'); if (0 <= dotIndex) { final int braceIndex = posativeVal(propertyName.lastIndexOf('}')); final int lastQuote = posativeVal(propertyName.lastIndexOf('\'')); final int breakPoint = min(braceIndex, lastQuote); if (dotIndex < breakPoint && breakPoint < propertyName.length()) { return propertyName.substring(dotIndex + 1, breakPoint); } else { throw new Error("No closing brace found!"); } } else { final int quotePos1 = propertyName.indexOf('\''); final int quotePos2 = propertyName.lastIndexOf('\''); if (0 <= quotePos1 && quotePos1 < quotePos2) { return propertyName.substring(quotePos1 + 1, quotePos2); } else { return propertyName; } } } else { return propertyName; } } } private static int posativeVal(final int i) { return i < 0 ? Integer.MAX_VALUE : i; } private static String getTagPropertyNamePrefix(final String tag) { final int colinPos = tag.indexOf(':'); return colinPos > 0 ? tag.substring(colinPos + 1) : tag; } private static String findPropertyName(final String contents, final int offset, final String whereToLookForLabel, final int tagTerminationIndex) { final int propertyPos = contents.indexOf(whereToLookForLabel, offset); if (propertyPos >= 0 && propertyPos < tagTerminationIndex) { final int startIndex = propertyPos + whereToLookForLabel.length() + 1; final int propertyTerminationIndex = findTagTermination(contents, startIndex, contents.charAt(propertyPos + whereToLookForLabel.length())); if (propertyPos < propertyTerminationIndex && propertyTerminationIndex < tagTerminationIndex) { return contents.substring(startIndex, propertyTerminationIndex - 1); } } return null; } private static boolean containsAlternative(final String contents, final int offset, final int tagTerminationIndex) { final int altPos = findIndexOf(contents, offset, "alt="); final int altKeyPos = findIndexOf(contents, offset, "altKey="); final int pos = min(altPos, altKeyPos); return pos >= 0 && pos < tagTerminationIndex; } private static int findTagTermination(final String contents, final int offset, final char terminationChar) { Stack charStack = new Stack(); for (int i = offset; i < contents.length(); i++) { char c = contents.charAt(i); if (isControleCharacter(c)) { if (charStack.isEmpty() && c == terminationChar) { return i + 1; } else if (charStack.isEmpty()) { charStack.push(Character.valueOf(c)); } else { char firstElement = charStack.peek().charValue(); if (matchingControlCharacters(firstElement, c)) { charStack.pop(); } else { charStack.push(Character.valueOf(c)); } } } } return -1; } private static boolean matchingControlCharacters(final char fc, final char lc) { return (fc == '"' && lc == '"') || (fc == '\'' && lc == '\'') || (fc == '<' && lc == '>'); } private static boolean isControleCharacter(final char c) { return c == '"' || c == '\'' || c == '<' || c == '>'; } private static int findIndexOf(final String contents, final int offset, final String token) { final int index = contents.indexOf(token, offset); return index == -1 ? Integer.MAX_VALUE : index; } private static int min(int... is) { if (is.length == 0) { throw new IllegalArgumentException(""); } else if (is.length == 1) { return normalize(is[0]); } else { int min = Integer.MAX_VALUE; for (int i = 0; i < is.length; i++) { min = Math.min(min, is[i]); } return normalize(min); } } private static int normalize(final int min) { return min == Integer.MAX_VALUE ? -1 : min; } public static String readFile(final File file) throws IOException { final FileReader fileReader = new FileReader(file); char[] buffer = new char[4096]; for (int i = 0; i < buffer.length; buffer[i++] = 0); final StringBuilder fileContents = new StringBuilder(); for (int n = 0; (n = fileReader.read(buffer)) != -1; ) { fileContents.append(buffer, 0, n); for (int i = 0; i < buffer.length; buffer[i++] = 0); } fileReader.close(); return fileContents.toString(); } private static void writeFile(final File file, final String content) throws IOException { final FileWriter fileWriter = new FileWriter(file, false); fileWriter.write(content); fileWriter.close(); } }