package org.w3c.css.css; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.Velocity; import org.apache.velocity.exception.ParseErrorException; import org.apache.velocity.exception.ResourceNotFoundException; import org.apache.velocity.runtime.RuntimeConstants; import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; import org.apache.velocity.tools.ToolManager; import org.apache.velocity.tools.generic.EscapeTool; import org.w3c.css.error.ErrorReportHTML; import org.w3c.css.parser.CssError; import org.w3c.css.parser.CssErrorToken; import org.w3c.css.parser.CssParseException; import org.w3c.css.parser.Errors; import org.w3c.css.properties.PropertiesLoader; import org.w3c.css.util.ApplContext; import org.w3c.css.util.CssProfile; import org.w3c.css.util.CssVersion; import org.w3c.css.util.InvalidParamException; import org.w3c.css.util.Messages; import org.w3c.css.util.Utf8Properties; import org.w3c.css.util.Warnings; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.TimeZone; /** * @author Julien Grand-Mourcel * @author Maria Kaceriakova * @date 2007-06-29 * This class uses Velocity to output the validator results */ public class StyleSheetGenerator extends StyleReport { private PrintWriter out; private Template template; private String template_file; private String default_lang = "en"; private ApplContext ac; private StyleSheet style; private String title; private VelocityContext context; private Warnings warnings; private Errors errors; private static final Utf8Properties availableFormat; private static final Utf8Properties availablePropertiesURL; private static ToolManager velocityToolManager; static { availableFormat = new Utf8Properties(); try { java.io.InputStream f; f = StyleSheetGenerator.class.getResourceAsStream( "format.properties"); availableFormat.load(f); f.close(); } catch (Exception e) { System.err.println("org.w3c.css.css.StyleSheetGeneratorHTML: " + "couldn't load format properties "); System.err.println(" " + e.toString()); } availablePropertiesURL = new Utf8Properties(); try { java.io.InputStream f; f = StyleSheetGenerator.class.getResourceAsStream( "urls.properties"); availablePropertiesURL.load(f); f.close(); } catch (Exception e) { System.err.println("org.w3c.css.css.StyleSheetGeneratorHTML: " + "couldn't load URLs properties "); System.err.println(" " + e.toString()); } try { Velocity.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath"); Velocity.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName()); Velocity.init(); velocityToolManager = new ToolManager(); } catch (Exception e) { System.err.println("Failed to initialize Velocity. " + "Validator might not work as expected."); } } public StyleSheetGenerator(String title, StyleSheet style, String document, int warningLevel) { this(null, title, style, document, warningLevel); } public StyleSheetGenerator(ApplContext ac, String title, StyleSheet style, String document, int warningLevel) { ArrayList items; this.ac = ac; this.style = style; this.title = title; this.template_file = availableFormat.getProperty(document); context = new VelocityContext(velocityToolManager.createContext()); context.put("esc", new EscapeTool()); // add a static ref for templates that needs to do some escaping context.put("Messages", Messages.class); // adjust the source name if needed if (ac.isInputFake()) { title = title.substring(title.lastIndexOf('/') + 1); } context.put("file_title", queryReplace(title)); // W3C_validator_result warnings = style.getWarnings(); errors = style.getErrors(); items = style.newGetRules(); // Setting all the variables of the velocity context ApplContext ac_default = new ApplContext(default_lang); String k, v; if (ac.getLang() == null || ac.getLang().equals(default_lang)) { for (Object s : ac_default.getMsg().properties.keySet()) { k = String.valueOf(s); context.put(k, ac.getMsg().getString(k)); } } else { for (Object s : ac_default.getMsg().properties.keySet()) { k = String.valueOf(s); v = ac.getMsg().getString(k); context.put(k, (v == null) ? ac_default.getMsg().getString(k) : v); } } if (ac.getLink() != null) { int arraysize = Messages.languages_name.size(); ArrayList> languages; languages = new ArrayList>(arraysize); String name; for (int i = 0; i < arraysize; ++i) { name = String.valueOf(Messages.languages_name.get(i)); HashMap l = new HashMap(); l.put("name", name); l.put("real", ((Utf8Properties) Messages.languages.get(name)).getProperty("language_name")); languages.add(i, l); } context.put("languages", languages.toArray()); String link = ac.getLink().replaceAll("&lang=.*&", "&"); link = link.replaceAll("&lang=.*$", ""); context.put("link", "?" + link.replaceAll("&", "&")); } // generated values SimpleDateFormat formatter; formatter = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'"); formatter.setTimeZone(TimeZone.getTimeZone("UTC")); Date now = new Date(); context.put("currentdate", formatter.format(now)); context.put("currentdatelong", now.getTime()); context.put("lang", ac.getContentLanguage()); context.put("errors_count", Integer.valueOf(errors.getErrorCount())); context.put("warnings_count", Integer.valueOf(warnings.getWarningCount())); context.put("ignored-warnings_count", Integer.valueOf(warnings.getIgnoredWarningCount())); context.put("warning_level", Integer.valueOf(warningLevel)); context.put("rules_count", Integer.valueOf(items.size())); context.put("no_errors_report", Boolean.FALSE); context.put("charset", ac.getContentEncoding()); context.put("cssversion", ac.getCssVersionString()); context.put("css_profile", ac.getProfileString()); // we are currently not using profile in the templates // so we compute here the version + profile in the $css variable. // TODO evaluate which is better. if (ac.getCssProfile() == CssProfile.EMPTY) { context.put("css", ac.getMsg().getString(ac.getCssVersionString())); } else { context.put("css", ac.getMsg().getString(ac.getCssVersionString()) + " + " + ac.getMsg().getString(ac.getCssProfile().toString())); } context.put("css_link", getURLProperty("@url-base_" + ac.getCssVersionString())); context.put("is_valid", (errors.getErrorCount() == 0) ? "true" : "false"); context.put("fake_input", Boolean.valueOf(ac.isInputFake())); context.put("author", "www-validator-css"); context.put("author-email", "Email.html"); if (style.charset != null) context.put("style_charset", style.charset); produceError(); produceWarning(); produceStyleSheet(); try { String _template_dir = "org/w3c/css/css/"; template = Velocity.getTemplate(_template_dir + template_file); template.setEncoding("utf-8"); } catch (ResourceNotFoundException rnfe) { System.err.println(rnfe.getMessage()); rnfe.printStackTrace(); } catch (ParseErrorException pee) { System.err.println(pee.getMessage()); pee.printStackTrace(); } catch (Exception e) { System.err.println(e.getMessage()); e.printStackTrace(); } } private final static String getURLProperty(String name) { return availablePropertiesURL.getProperty(name); } public final static void printAvailableFormat(PrintWriter out) { Enumeration e = availableFormat.propertyNames(); out.println(" -- listing available output format --"); while (e.hasMoreElements()) { String key = ((String) e.nextElement()).toLowerCase(); out.println("Format : " + key); out.println(" File : " + getDocumentName(key)); } out.flush(); } public static String printAvailableFormatList(String def) { Enumeration e = availableFormat.propertyNames(); StringBuilder sb = new StringBuilder(); ArrayList t = (ArrayList) Collections.list(e); Collections.sort(t); boolean isFirst = true; for (String s : t) { if (!isFirst) { sb.append(", "); } isFirst = false; sb.append(s); if (s.equals(def)) { sb.append(" (default)"); } } return sb.toString(); } /** * Test if document is an available output * * @param document, the desired output * @return true if the desired output is available */ public static boolean isAvailableFormat(String document) { return availableFormat.containsKey(document); } private final static String getDocumentName(String documentName) { String document = availableFormat.getProperty(documentName .toLowerCase()); if (document == null) { System.err.println("Unable to find " + documentName.toLowerCase() + " output format"); return documentName; } else { return document; } } /** * Add the style information to the context */ private void produceStyleSheet() { context.put("at_rules_list", style.newGetRules()); } /** * Add the errors information to the context * For each error, *
    *
  • the error type and message, and *
  • the context type and value *
* are set. */ private void produceError() { Hashtable[] errors_content = new Hashtable[errors.getErrorCount()]; try { if (errors.getErrorCount() != 0) { CssError[] error = errors.getErrors(); int nbError = error.length; for (int i = 0; i < nbError; i++) { CssError csserror = error[i]; Throwable ex = csserror.getException(); Hashtable h = new Hashtable(); errors_content[i] = h; h.put("Error", csserror); h.put("CtxName", "nocontext"); h.put("CtxMsg", ""); h.put("ErrorMsg", ((ex.getMessage() == null) ? "" : ex.getMessage())); h.put("ClassName", "unkownerror"); h.put("Type", csserror.getType()); if (ex instanceof FileNotFoundException) { h.put("ClassName", "notfound"); h.put("ErrorMsg", ac.getMsg().getGeneratorString("not-found") + ": " + ex.getMessage()); } else if (ex instanceof CssParseException) { produceParseException((CssParseException) ex, h); } else if (ex instanceof InvalidParamException) { h.put("ClassName", "invalidparam"); h.put("ErrorMsg", queryReplace((String) h.get("ErrorMsg"))); } else if (ex instanceof IOException) { String stringError = ex.toString(); // int index = stringError.indexOf(':'); // The Exception name 'StringError' was previously // displayed // between and // tag // because we would need a new variable to put it there // for // just one rare case // TODO: why not using ex.toString()? h.put("CtxMsg", stringError);// .substring(0, // index)); h.put("ClassName", "io"); } else if (csserror instanceof CssErrorToken) { CssErrorToken terror = (CssErrorToken) csserror; h.put("ClassName", "errortoken"); String str = ac.getMsg().getErrorString("errortoken"); if (str == null) { str = "Parse Error %s"; } String[] params = new String[4]; params[0] = terror.getErrorToken(); params[1] = Integer.toString(terror.getLine()); StringBuilder sb = new StringBuilder(); boolean notfirst = false; for (String t : terror.getExpected()) { if (notfirst) { sb.append(", "); } sb.append(t); } params[2] = sb.toString(); params[3] = terror.getSkippedString(); // replace all parameters String[] msg_parts = str.split("%s", -1); int j = 0; sb.setLength(0); sb.append(msg_parts[0]); for (int k = 1; k < msg_parts.length; k++) { if (j < params.length) { sb.append(params[j++]); } sb.append(msg_parts[k]); } h.put("ErrorMsg", queryReplace(sb.toString())); } else { h.put("ClassName", "unkownerror"); h.put("ErrorMsg", ac.getMsg().getErrorString("unknown") + " " + ex); if (ex instanceof NullPointerException) { // ohoh, a bug ex.printStackTrace(); } } } } context.put("errors_content", errors_content); } catch (Exception e) { context.put("errors_content", errors_content); context.put("request", ac.getMsg().getGeneratorString("request")); System.err.println(e.getMessage()); e.printStackTrace(); } } /** * Add an array of warnings to the context so it c an be displayed */ private void produceWarning() { try { if (warnings.getWarningCount() != 0) { warnings.sort(); context.put("warnings_list", warnings.getWarnings()); } } catch (Exception e) { out.println(ac.getMsg().getGeneratorString("request")); e.printStackTrace(); } } /** * Some error need more details * We can add a link or information of this kind * * @param error, the error to check * @param ht_error, the Hastable with information about this error */ private void produceParseException(CssParseException error, Hashtable ht_error) { if (error.getContexts() != null && error.getContexts().size() != 0) { ht_error.put("CtxName", "codeContext"); StringBuilder buf = new StringBuilder(); // Loop on the list of contexts for errors Enumeration e; Iterator li = error.getContexts().iterator(); while (li.hasNext()) { Object t = li.next(); // if the list is not null, add a comma if (t != null) { buf.append(t); if (li.hasNext()) { buf.append(", "); } } } if (buf.length() != 0) { ht_error.put("CtxMsg", String.valueOf(buf)); } } else { ht_error.put("CtxName", "nocontext"); } ht_error.put("ClassName", "parse-error"); String name = error.getProperty(); String ret; if ((name != null) && (getURLProperty(name) != null) && PropertiesLoader.getProfile(ac.getPropertyKey()).containsKey(name)) { //we add a link information // we check if the property doesn't exist in this css version ht_error.put("link_before_parse_error", ac.getMsg().getGeneratorString("property")); // Since CSS3 is only a working draft, the links don't exist yet // in CSS3... // And this is the same with CSS1 because the links are not working // the same way... // This can be removed as soon as the CSS3 specifications are made // and CSS1 use the links // and the link is changed in urls.properties String lnk; CssVersion v = ac.getCssVersion(); if (v == CssVersion.CSS3) { lnk = getURLProperty("@url-base_css2.1"); } else if (v == CssVersion.CSS1) { lnk = getURLProperty("@url-base_css2"); } else { Object _lnk = context.get("cs_link"); lnk = (_lnk == null) ? "[error]" : _lnk.toString(); } // this would be replaced by : // ht_error.put("link_value_parse_error", // context.get("css_link") + getURLProperty(name)); ht_error.put("link_value_parse_error", lnk + getURLProperty(name)); ht_error.put("link_name_parse_error", name); } if ((error.getException() != null) && (error.getMessage() != null)) { if (error.isParseException()) { ret = queryReplace(error.getMessage()); } else { Exception ex = error.getException(); if (ex instanceof NumberFormatException) { ret = ac.getMsg().getGeneratorString("invalid-number"); } else { ret = queryReplace(ex.getMessage()); } } if (error.getSkippedString() != null) { ht_error.put("span_class_parse_error", "skippedString"); ht_error.put("span_value_parse_error", queryReplace(error.getSkippedString())); } else if (error.getExp() != null) { ret += " : ";// + queryReplace(error.getExp().toStringFromStart()); ht_error.put("span_class_parse_error", "exp"); ht_error.put("span_value_parse_error", queryReplace(error.getExp().toString())); } } else { ret = ac.getMsg().getGeneratorString("unrecognize"); ht_error.put("span_class_parse_error", "unrecognized"); if (error.getSkippedString() != null) { ht_error.put("span_value_parse_error", queryReplace(error.getSkippedString())); } else if (error.getExp() != null) { ht_error.put("span_value_parse_error", queryReplace(error.getExp().toStringFromStart())); } else { // nothing useful to add } } ht_error.put("ErrorMsg", ret); } /** * @param s, the string to convert * @return the string s with html character escaped */ private String queryReplace(String s) { if (!"xhtml.properties".equals(this.template_file)) { return s; } if (s != null) { int len = s.length(); StringBuilder ret = new StringBuilder(len + 16); char c; for (int i = 0; i < len; i++) { switch (c = s.charAt(i)) { case '&': ret.append("&"); break; case '\'': ret.append("'"); break; case '"': ret.append("""); break; case '<': ret.append("<"); break; case '>': ret.append(">"); break; case '\u201C': ret.append(""); break; case '\u201D': ret.append(""); break; default: ret.append(c); } } return ret.toString(); } return "[empty string]"; } /** * Display the output * * @require VelocityContext to be set and init() */ public void print(PrintWriter out) { this.out = out; try { // Velocity context must have been setup in IndexGenerator template.merge(context, out); } catch (Exception e) { new ErrorReportHTML(ac, title, "", e).print(out); } out.flush(); } /** * The user doesn't want to see the error report when this function * is called */ public void desactivateError() { context.put("no_errors_report", Boolean.TRUE); // activate the no errors report } }