Java tutorial
// Copyright 2009 Google Inc. // // Licensed 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 com.google.visualization.datasource.render; import com.google.visualization.datasource.base.ReasonType; import com.google.visualization.datasource.base.ResponseStatus; import com.google.visualization.datasource.base.StatusType; import com.google.visualization.datasource.base.Warning; import com.google.visualization.datasource.datatable.ColumnDescription; import com.google.visualization.datasource.datatable.DataTable; import com.google.visualization.datasource.datatable.TableCell; import com.google.visualization.datasource.datatable.TableRow; import com.google.visualization.datasource.datatable.ValueFormatter; import com.google.visualization.datasource.datatable.value.BooleanValue; import com.google.visualization.datasource.datatable.value.ValueType; import com.ibm.icu.util.ULocale; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import java.io.StringWriter; import java.io.Writer; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; /** * Takes a data table and returns an html string. * * @author Nimrod T. */ public class HtmlRenderer { /** * Log. */ private static final Log log = LogFactory.getLog("HtmlRenderer"); /** * Private constructor. */ private HtmlRenderer() { } /** * Pattern for matching against <a> tags with hrefs. * Used in sanitizeDetailedMessage. */ private static final Pattern DETAILED_MESSAGE_A_TAG_REGEXP = Pattern .compile("([^<]*<a(( )*target=\"_blank\")*(( )*target='_blank')*" + "(( )*href=\"[^\"]*\")*(( )*href='[^']*')*>[^<]*</a>)+[^<]*"); /** * Pattern for matching against "javascript:". * Used in sanitizeDetailedMessage. */ private static final Pattern BAD_JAVASCRIPT_REGEXP = Pattern.compile("javascript(( )*):"); /** * Generates an HTML string representation of a data table. * * @param dataTable The data table to render. * @param locale The locale. If null, uses the default from * {@code LocaleUtil#getDefaultLocale}. * * @return The char sequence with the html string. */ public static CharSequence renderDataTable(DataTable dataTable, ULocale locale) { // Create an xml document with head and an empty body. Document document = createDocument(); Element bodyElement = appendHeadAndBody(document); // Populate the xml document. Element tableElement = document.createElement("table"); bodyElement.appendChild(tableElement); tableElement.setAttribute("border", "1"); tableElement.setAttribute("cellpadding", "2"); tableElement.setAttribute("cellspacing", "0"); // Labels tr element. List<ColumnDescription> columnDescriptions = dataTable.getColumnDescriptions(); Element trElement = document.createElement("tr"); trElement.setAttribute("style", "font-weight: bold; background-color: #aaa;"); for (ColumnDescription columnDescription : columnDescriptions) { Element tdElement = document.createElement("td"); tdElement.setTextContent(columnDescription.getLabel()); trElement.appendChild(tdElement); } tableElement.appendChild(trElement); Map<ValueType, ValueFormatter> formatters = ValueFormatter.createDefaultFormatters(locale); // Table tr elements. int rowCount = 0; for (TableRow row : dataTable.getRows()) { rowCount++; trElement = document.createElement("tr"); String backgroundColor = (rowCount % 2 != 0) ? "#f0f0f0" : "#ffffff"; trElement.setAttribute("style", "background-color: " + backgroundColor); List<TableCell> cells = row.getCells(); for (int c = 0; c < cells.size(); c++) { ValueType valueType = columnDescriptions.get(c).getType(); TableCell cell = cells.get(c); String cellFormattedText = cell.getFormattedValue(); if (cellFormattedText == null) { cellFormattedText = formatters.get(cell.getType()).format(cell.getValue()); } Element tdElement = document.createElement("td"); if (cell.isNull()) { tdElement.setTextContent("\u00a0"); } else { switch (valueType) { case NUMBER: tdElement.setAttribute("align", "right"); tdElement.setTextContent(cellFormattedText); break; case BOOLEAN: BooleanValue booleanValue = (BooleanValue) cell.getValue(); tdElement.setAttribute("align", "center"); if (booleanValue.getValue()) { tdElement.setTextContent("\u2714"); // Check mark. } else { tdElement.setTextContent("\u2717"); // X mark. } break; default: if (StringUtils.isEmpty(cellFormattedText)) { tdElement.setTextContent("\u00a0"); // nbsp. } else { tdElement.setTextContent(cellFormattedText); } } } trElement.appendChild(tdElement); } tableElement.appendChild(trElement); } bodyElement.appendChild(tableElement); // Warnings: for (Warning warning : dataTable.getWarnings()) { bodyElement.appendChild(document.createElement("br")); bodyElement.appendChild(document.createElement("br")); Element messageElement = document.createElement("div"); messageElement.setTextContent( warning.getReasonType().getMessageForReasonType() + ". " + warning.getMessage()); bodyElement.appendChild(messageElement); } return transformDocumentToHtmlString(document); } /** * Transforms a document to a valid html string. * * @param document The document to transform * * @return A string representation of a valid html. */ private static String transformDocumentToHtmlString(Document document) { // Generate a CharSequence from the xml document. Transformer transformer = null; try { transformer = TransformerFactory.newInstance().newTransformer(); } catch (TransformerConfigurationException e) { log.error("Couldn't create a transformer", e); throw new RuntimeException("Couldn't create a transformer. This should never happen.", e); } transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "-//W3C//DTD HTML 4.01//EN"); transformer.setOutputProperty(OutputKeys.METHOD, "html"); transformer.setOutputProperty(OutputKeys.VERSION, "4.01"); DOMSource source = new DOMSource(document); Writer writer = new StringWriter(); StreamResult result = new StreamResult(writer); try { transformer.transform(source, result); } catch (TransformerException e) { log.error("Couldn't transform", e); throw new RuntimeException("Couldn't transform. This should never happen.", e); } return writer.toString(); } /** * Sanitizes the html in the detailedMessage, to allow only href inside <a> tags. * * @param detailedMessage The detailedMessage. * * @return The sanitized detailedMessage. */ static String sanitizeDetailedMessage(String detailedMessage) { if (StringUtils.isEmpty(detailedMessage)) { return ""; } if (DETAILED_MESSAGE_A_TAG_REGEXP.matcher(detailedMessage).matches() && (!BAD_JAVASCRIPT_REGEXP.matcher(detailedMessage).find())) { // No need to escape. return detailedMessage; } else { // Need to html escape. return EscapeUtil.htmlEscape(detailedMessage); } } /** * Renders a simple html for the given responseStatus. * * @param responseStatus The response message. * * @return A simple html for the given responseStatus. */ public static CharSequence renderHtmlError(ResponseStatus responseStatus) { // Get the responseStatus details. StatusType status = responseStatus.getStatusType(); ReasonType reason = responseStatus.getReasonType(); String detailedMessage = responseStatus.getDescription(); // Create an xml document with head and an empty body. Document document = createDocument(); Element bodyElement = appendHeadAndBody(document); // Populate the xml document. Element oopsElement = document.createElement("h3"); oopsElement.setTextContent("Oops, an error occured."); bodyElement.appendChild(oopsElement); if (status != null) { String text = "Status: " + status.lowerCaseString(); appendSimpleText(document, bodyElement, text); } if (reason != null) { String text = "Reason: " + reason.getMessageForReasonType(null); appendSimpleText(document, bodyElement, text); } if (detailedMessage != null) { String text = "Description: " + sanitizeDetailedMessage(detailedMessage); appendSimpleText(document, bodyElement, text); } return transformDocumentToHtmlString(document); } /** * Creates a document element. * * @return A document element. */ private static Document createDocument() { DocumentBuilder documentBuilder = null; try { documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); } catch (ParserConfigurationException e) { log.error("Couldn't create a document builder", e); throw new RuntimeException("Couldn't create a document builder. This should never happen.", e); } Document document = documentBuilder.newDocument(); return document; } /** * Appends <html>, <head>, <title>, and <body> elements to the document. * * @param document The containing document. * * @return The <body> element. */ private static Element appendHeadAndBody(Document document) { Element htmlElement = document.createElement("html"); document.appendChild(htmlElement); Element headElement = document.createElement("head"); htmlElement.appendChild(headElement); Element titleElement = document.createElement("title"); titleElement.setTextContent("Google Visualization"); headElement.appendChild(titleElement); Element bodyElement = document.createElement("body"); htmlElement.appendChild(bodyElement); return bodyElement; } /** * Appends a simple text line to the body of the document. * * @param document The containing document. * @param bodyElement The body of the document. * @param text The text to append. */ private static void appendSimpleText(Document document, Element bodyElement, String text) { Element statusElement = document.createElement("div"); statusElement.setTextContent(text); bodyElement.appendChild(statusElement); } }