Java tutorial
/* * 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 java.io.*; import java.util.Iterator; import java.util.Map; import org.apache.commons.lang3.StringEscapeUtils; import com.uwyn.jhighlight.highlighter.ExplicitStateHighlighter; import com.uwyn.jhighlight.utils.StringUtils; import com.uwyn.jhighlight.utils.VersionUtils; /** * 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 */ @Override 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 = -1; boolean newline = false; 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 != -1 && !newline) { w.write("</span>"); } w.write("<span class=\"" + css_class + "\">"); previous_style = style; } } newline = false; w.write(StringEscapeUtils.escapeHtml4(token.replace("\n", "")).replace(" ", " ")); index += length; } 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 */ @Override public String highlight(String name, String in, String encoding, boolean fragment) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); highlight(name, new ByteArrayInputStream(in.getBytes(encoding)), 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<String, String> 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() { StringBuilder css = new StringBuilder(); Iterator<Map.Entry<String, String>> it = getDefaultCssStyles().entrySet().iterator(); Map.Entry<String, String> entry; while (it.hasNext()) { entry = it.next(); String key = entry.getKey(); css.append(key); css.append(" {\n"); 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) { name = org.apache.commons.lang3.StringUtils.defaultString(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" + VersionUtils.getVersion() + " (" + VersionUtils.URL + ")\" />\n" + " <title>" + StringEscapeUtils.escapeHtml4(name) + "</title>\n" + " <link rel=\"Help\" href=\"" + VersionUtils.URL + "\" />\n" + " <style type=\"text/css\">\n" + getCssClassDefinitions() + " </style>\n" + "</head>\n" + "<body>\n" + "<h1>" + StringEscapeUtils.escapeHtml4(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) { return "<!-- " + org.apache.commons.lang3.StringUtils.defaultString(name) + " : generated by JHighlight v" + VersionUtils.getVersion() + " (" + VersionUtils.URL + ") -->\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"; } }