/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 * * 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 openbook.tools.converter; import java.util.HashSet; import java.util.Set; import openbook.tools.parser.JavaParser; import openbook.tools.util.TextProcessingUtility; import org.antlr.runtime.Token; /** * Renders Java source tokens as HTML tags. * This renderer renders the parsed Java tokens with HTML styles. * The styles of the tokens are determined by their types such as Java keywords, identifiers, * comments etc. Moreover, an identifier can be a <em>custom</em> type if it matched a given * list of identifiers. The actual HTML text is enclosed in <span id="style">token<\span> * to apply the style. The styles are defined in a Cascaded Style Sheet (CSS). * The cascaded style sheet by default is named <code>java.css</code>. * <br> * The rendering takes care of line breaks and white space in the following way to work * around some limitations of Swing based HTML editor kit's usage of CSS attributes. * <LI> Line Breaks: Swing Editor seems to require an explicit carriage return-line feed * character to render in separate line. While a normal browser works with <br> * tag alone. * <LI> White space: The CSS property <code>white-space</code> is sufficient for browsers * to preserve white space within <span> tags. But Swing Editor seems to require * explicit <code>nbsp;</code> for white spaces within <span> tags. * <br> * Two boolean properties are provided to control these two properties. * <br> * <LI>Line Numbering: A boolean property controls whether line numbers will be printed. * Line numbers are printed in 4 digits with leading zeros, by default. * <LI>Line Number Anchoring: An anchor can be specified at every line. The anchor * is <code>line.nnn</code> where <code>nnn</code> is the actual line number without * any leading zero. * <LI> JavaDoc comment : The JavaDoc comments can use characters that if reproduced * exactly in HTML output can confuse the rendering process. On the other hand, the * JavaDoc tags that define an anchor in the source code or creates a hyperlink should * be preserved in the HTML output. The capacity and limitation of processing HTML tages * inside JavaDoc comments are described in {@linkplain TextProcessingUtility here}. * * * @author Pinaki Poddar * */ public class HTMLTokenRenderer implements TokenRenderer { private String stylesheet = "java.css"; private boolean showLineNumber = true; private boolean anchorLineNumber = false; private boolean addLineBreak = true; private boolean addExplicitSpace = true; private String lineNumberFormat = "%04d"; /** * The CSS named styles. */ public static final String CSS_CUSTOM = "custom"; public static final String CSS_KEYWORD = "keyword"; public static final String CSS_ANNOTATION = "annotation"; public static final String CSS_ENUM = "enum"; public static final String CSS_COMMENT = "comment"; public static final String CSS_LITERAL = "literal"; public static final String CSS_DECIMAL = "decimal"; public static final String CSS_LINE_NO = "lineno"; private Set<String> customIdentifiers = new HashSet<String>(); public static final String NEW_LINE = "\r\n"; public static final String HTML_BR_TAG = "<br>"; public static final String HTML_SPACE = " "; /** * Gets a end-of-line string: a HTML <br> tag followed by carriage return and line feed. */ public String endLine(int line) { return addLineBreak ? HTML_BR_TAG + NEW_LINE : NEW_LINE; } /** * Gets a string for beginning of a new line. */ public String newLine(int line) { String result = ""; if (showLineNumber) { result = span(CSS_LINE_NO, String.format(lineNumberFormat, line) + fillWhiteSpace(" ")); } if (anchorLineNumber) { result = "<A name=" + quote("line."+line) + ">" + result + "</A>"; } return result; } @Override public String render(int decision, Token token) { String text = token.getText(); String result = ""; int type = token.getType(); switch (type) { case JavaParser.Identifier: case 73: // annotation symbol @ if (customIdentifiers.contains(text)) { result = span(CSS_CUSTOM, text); } else if (decision == 66 || decision == 14) { result = span(CSS_ANNOTATION, text); } else if (decision == 28) { result = span(CSS_ENUM, text); } else { result = text; } break; case JavaParser.COMMENT: case JavaParser.LINE_COMMENT : result = span(CSS_COMMENT, TextProcessingUtility.replaceHTMLSpecialCharacters(text)); break; case JavaParser.StringLiteral : case JavaParser.CharacterLiteral: result = span(CSS_LITERAL, text); break; case JavaParser.DecimalLiteral: case JavaParser.HexDigit: case JavaParser.HexLiteral: result = span(CSS_DECIMAL, text); break; default: if (isWhiteSpace(text)) { result = fillWhiteSpace(text); } else if (text.length() == 1) { if (text.charAt(0) == '>') { result = ">"; } else if (text.charAt(0) == '<') { result = "<"; } else { result = text; } } else { result = span(CSS_KEYWORD, text); } } return result; } String span(String id, String txt) { return "<span id=" + quote(id) + ">" + txt + "</span>"; } String quote(String s) { return "\""+s+"\""; } boolean isWhiteSpace(String txt) { for (int i = 0; i < txt.length(); i++) { if (!Character.isWhitespace(txt.charAt(i))) return false; } return true; } String fillWhiteSpace(String txt) { StringBuilder space = new StringBuilder(); for (int i = 0; i < txt.length(); i++) { char ch = txt.charAt(i); if (ch != '\r' && ch != '\n') space.append(addExplicitSpace ? HTML_SPACE : ch); } return space.toString(); } /** * Gets the opening <BODY> and <HTML> tags and the <link type="stylesheet"> clause. */ public String getPrologue() { return insertLines( "<HTML>", "<HEAD>", "<link rel="+ quote("stylesheet")+ " type=" + quote("text/css") + " href=" + quote(stylesheet) + ">", "</HEAD>", "<BODY>"); } /** * Gets the closing <BODY> and <HTML> tags */ public String getEpilogue() { return insertLines(" ", "</BODY>", "</HTML>"); } private String insertLines(String...lines) { StringBuilder buf = new StringBuilder(); for (String line : lines) { if (buf.length() != 0) buf.append(NEW_LINE); buf.append(line); } return buf.toString(); } // Bean Style setters for auto-configuration /** * Gets the stylesheet to be linked at HTML output. */ public String getStylesheet() { return stylesheet; } /** * Sets the stylesheet to be linked at the HTML output. */ public void setStylesheet(String stylesheet) { this.stylesheet = stylesheet; } /** * Affirms if a line number will be added in HTML output. */ public boolean getShowLineNumber() { return showLineNumber; } /** * Sets if a line number will be added in HTML output. */ public void setShowLineNumber(boolean showLineNumber) { this.showLineNumber = showLineNumber; } /** * Affirms if an anchor will be created on every line. */ public boolean getAnchorLineNumber() { return anchorLineNumber; } /** * Sets if an anchor will be created on every line. */ public void setAnchorLineNumber(boolean anchorLineNumber) { this.anchorLineNumber = anchorLineNumber; } /** * Affirms if explicit line break (carriage return and line feed) will be added * at the HTML output. * * @see #endLine(int) */ public boolean getAddLineBreak() { return addLineBreak; } /** * Sets if explicit line break (carriage return and line feed) will be added * at the HTML output. * * @see #endLine(int) */ public void setAddLineBreak(boolean addLineBreak) { this.addLineBreak = addLineBreak; } /** * Affirms if explicit <code> </code> will be added at the HTML output. */ public boolean getAddExplicitSpace() { return addExplicitSpace; } /** * Sets if explicit <code> </code> will be added at the HTML output. */ public void setAddExplicitSpace(boolean addSpace) { this.addExplicitSpace = addSpace; } /** * Gets the format string to format line number such as <code>"%%0%4d"</code> * for a 4-digit number with leading zeros. */ public String getLineNumberFormat() { return lineNumberFormat; } /** * Sets the format string to format line number such as <code>"%%0%4d"</code> * for a 4-digit number with leading zeros. */ public void setLineNumberFormat(String lineNumberFormat) { this.lineNumberFormat = lineNumberFormat; } }