/* * Copyright 2004-2006 Geert Bevin <gbevin[remove] at uwyn dot com> * Distributed under the terms of either: * - the common development and distribution license (CDDL), v1.0; or * - the GNU Lesser General Public License, v2.1 or later * $Id: XhtmlRenderer.java 3108 2006-03-13 18:03:00Z gbevin $ */ package com.uwyn.jhighlight.renderer; import com.uwyn.jhighlight.highlighter.JavaHighlighter; import java.io.*; import com.uwyn.jhighlight.JHighlightVersion; import com.uwyn.jhighlight.highlighter.ExplicitStateHighlighter; import com.uwyn.jhighlight.tools.ExceptionUtils; import com.uwyn.jhighlight.tools.StringUtils; import java.net.URL; import java.net.URLConnection; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.logging.Logger; /** * Provides an abstract base class to perform source code to XHTML syntax * highlighting. * * @author Geert Bevin (gbevin[remove] at uwyn dot com) * @version $Revision: 3108 $ * @since 1.0 */ public abstract class XhtmlRenderer implements Renderer { /** * Transforms source code that's provided through an * <code>InputStream</code> to highlighted syntax in XHTML and writes it * back to an <code>OutputStream</code>. * <p>If the highlighting has to become a fragment, no CSS styles will be * generated. * <p>For complete documents, there's a collection of default styles that * will be included. It's possible to override these by changing the * provided <code>jhighlight.properties</code> file. It's best to look at * this file in the JHighlight archive and modify the styles that are * there already. * * @param name The name of the source file. * @param in The input stream that provides the source code that needs to * be transformed. * @param out The output stream to which to resulting XHTML should be * written. * @param encoding The encoding that will be used to read and write the * text. * @param fragment <code>true</code> if the generated XHTML should be a * fragment; or <code>false</code> if it should be a complete page * @see #highlight(String, String, String, boolean) * @since 1.0 */ public void highlight(String name, InputStream in, OutputStream out, String encoding, boolean fragment) throws IOException { ExplicitStateHighlighter highlighter = getHighlighter(); Reader isr; Writer osw; if (null == encoding) { isr = new InputStreamReader(in); osw = new OutputStreamWriter(out); } else { isr = new InputStreamReader(in, encoding); osw = new OutputStreamWriter(out, encoding); } BufferedReader r = new BufferedReader(isr); BufferedWriter w = new BufferedWriter(osw); // // if (fragment) { // w.write(getXhtmlHeaderFragment(name)); // } else { // w.write(getXhtmlHeader(name)); // } String line; String token; int length; int style; String css_class; int previous_style = 0; boolean newline = false; String open_tags = ""; String close_tags = ""; while ((line = r.readLine()) != null) { line += "\n"; line = StringUtils.convertTabsToSpaces(line, 4); // should be optimized by reusing a custom LineReader class Reader lineReader = new StringReader(line); highlighter.setReader(lineReader); int index = 0; while (index < line.length()) { style = highlighter.getNextToken(); length = highlighter.getTokenLength(); token = line.substring(index, index + length); if (style != previous_style || newline) { css_class = getCssClass(style); if (css_class != null) { if (previous_style != 0 && !newline) { //w.write("</span>"); w.write(close_tags); } switch (style) { case JavaHighlighter.PLAIN_STYLE: open_tags = ""; close_tags = ""; break; case JavaHighlighter.KEYWORD_STYLE: case JavaHighlighter.TYPE_STYLE: open_tags = "<font color=red><b>"; close_tags = "</b></font>"; break; case JavaHighlighter.JAVA_COMMENT_STYLE: case JavaHighlighter.JAVADOC_COMMENT_STYLE: case JavaHighlighter.JAVADOC_TAG_STYLE: open_tags = "<font color=green><i>"; close_tags = "</i></font>"; break; case JavaHighlighter.OPERATOR_STYLE: case JavaHighlighter.SEPARATOR_STYLE: //open_tags = "<font color=blue size=+1><b>"; open_tags = "<font color=blue><b>"; close_tags = "</b></font>"; break; case JavaHighlighter.LITERAL_STYLE: open_tags = "<font color=gray>"; close_tags = "</font>"; break; case JavaHighlighter.INQUISITION_TAG_STYLE: case JavaHighlighter.INQUISITION_INPUT_STYLE: open_tags = ""; close_tags = ""; break; } //w.write("<span class=\"" + css_class + "\">"); w.write(open_tags); previous_style = style; } } newline = false; //w.write(StringUtils.replace(StringUtils.encodeHtml(StringUtils.replace(token, "\n", "")), " ", " ")); if (style == JavaHighlighter.INQUISITION_INPUT_STYLE || style == JavaHighlighter.INQUISITION_TAG_STYLE) { w.write(token); } else { w.write(StringUtils.encodeHtml(StringUtils.replace(token, "\n", ""))); } index += length; } w.write(close_tags + "\n"); // w.write("</span><br />\n"); newline = true; } if (!fragment) w.write(getXhtmlFooter()); w.flush(); w.close(); } /** * Transforms source code that's provided through a * <code>String</code> to highlighted syntax in XHTML and returns it * as a <code>String</code>. * <p>If the highlighting has to become a fragment, no CSS styles will be * generated. * * @param name The name of the source file. * @param in The input string that provides the source code that needs to * be transformed. * @param encoding The encoding that will be used to read and write the * text. * @param fragment <code>true</code> if the generated XHTML should be a * fragment; or <code>false</code> if it should be a complete page * or <code>false</code> if it should be a complete document * @return the highlighted source code as XHTML in a string * @see #highlight(String, InputStream, OutputStream, String, boolean) * @since 1.0 */ public String highlight(String name, String in, String encoding, boolean fragment) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); highlight(name, new StringBufferInputStream(in), out, encoding, fragment); return out.toString(encoding); } /** * Returns a map of all the CSS styles that the renderer requires, * together with default definitions for them. * * @return The map of CSS styles. * @since 1.0 */ protected abstract Map getDefaultCssStyles(); /** * Looks up the CSS class identifier that corresponds to the syntax style. * * @param style The syntax style. * @return The requested CSS class identifier; or * <p><code>null</code> if the syntax style isn't supported. * @since 1.0 */ protected abstract String getCssClass(int style); /** * Returns the language-specific highlighting lexer that should be used * * @return The requested highlighting lexer. * @since 1.0 */ protected abstract ExplicitStateHighlighter getHighlighter(); /** * Returns all the CSS class definitions that should appear within the * <code>style</code> XHTML tag. * <p>This should support all the classes that the * <code>getCssClass(int)</code> method returns. * * @return The CSS class definitions * @see #getCssClass(int) * @since 1.0 */ protected String getCssClassDefinitions() { StringBuffer css = new StringBuffer(); Properties properties = new Properties(); URL jhighlighter_props = getClass().getClassLoader().getResource("jhighlight.properties"); if (jhighlighter_props != null) { try { URLConnection connection = jhighlighter_props.openConnection(); connection.setUseCaches(false); InputStream is = connection.getInputStream(); try { properties.load(is); } finally { is.close(); } } catch (IOException e) { Logger.getLogger("com.uwyn.jhighlight").warning("Error while reading the '" + jhighlighter_props.toExternalForm() + "' resource, using default CSS styles.\n" + ExceptionUtils.getExceptionStackTrace(e)); } } Iterator it = getDefaultCssStyles().entrySet().iterator(); Map.Entry entry; while (it.hasNext()) { entry = (Map.Entry)it.next(); String key = (String)entry.getKey(); css.append(key); css.append(" {\n"); if (properties.containsKey(key)) { css.append(properties.get(key)); } else { css.append(entry.getValue()); } css.append("\n}\n"); } return css.toString(); } /** * Returns the XHTML header that preceedes the highlighted source code. * <p>It will integrate the CSS class definitions and use the source's * name to indicate in XHTML which file has been highlighted. * * @param name The name of the source file. * @return The constructed XHTML header. * @since 1.0 */ protected String getXhtmlHeader(String name) { if (null == name) { name = ""; } return "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" + " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" + "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n" + "<head>\n" + " <meta http-equiv=\"content-type\" content=\"text/html; charset=ISO-8859-1\" />\n" + " <meta name=\"generator\" content=\"JHighlight v"+JHighlightVersion.getVersion()+" (http://jhighlight.dev.java.net)\" />\n" + " <title>" + StringUtils.encodeHtml(name) + "</title>\n" + " <link rel=\"Help\" href=\"http://jhighlight.dev.java.net\" />\n" + " <style type=\"text/css\">\n" + getCssClassDefinitions() + " </style>\n" + "</head>\n" + "<body>\n" + "<h1>" + StringUtils.encodeHtml(name) + "</h1>" + "<code>"; } /** * Returns the XHTML header that preceedes the highlighted source code for * a fragment. * * @param name The name of the source file. * @return The constructed XHTML header. * @since 1.0 */ protected String getXhtmlHeaderFragment(String name) { if (null == name) { name = ""; } return "<!-- "+name+" : generated by JHighlight v"+JHighlightVersion.getVersion()+" (http://jhighlight.dev.java.net) -->\n"; } /** * Returns the XHTML footer that nicely finishes the file after the * highlighted source code. * * @return The requested XHTML footer. * @since 1.0 */ protected String getXhtmlFooter() { return "</code>\n</body>\n</html>\n"; } }