/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2006 - 2013 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.modules.output.table.html.helper;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.text.NumberFormat;
import org.pentaho.reporting.engine.classic.core.AttributeNames;
import org.pentaho.reporting.engine.classic.core.ClassicEngineBoot;
import org.pentaho.reporting.engine.classic.core.ClassicEngineInfo;
import org.pentaho.reporting.engine.classic.core.ReportAttributeMap;
import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorMetaData;
import org.pentaho.reporting.engine.classic.core.modules.output.table.base.CellBackground;
import org.pentaho.reporting.engine.classic.core.modules.output.table.base.SlimSheetLayout;
import org.pentaho.reporting.engine.classic.core.modules.output.table.html.HtmlPrinter;
import org.pentaho.reporting.engine.classic.core.modules.output.table.html.HtmlTableModule;
import org.pentaho.reporting.engine.classic.core.modules.output.table.html.URLRewriteException;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictGeomUtility;
import org.pentaho.reporting.libraries.base.config.Configuration;
import org.pentaho.reporting.libraries.base.util.MemoryStringReader;
import org.pentaho.reporting.libraries.fonts.encoding.EncodingRegistry;
import org.pentaho.reporting.libraries.repository.ContentIOException;
import org.pentaho.reporting.libraries.repository.ContentItem;
import org.pentaho.reporting.libraries.repository.ContentLocation;
import org.pentaho.reporting.libraries.repository.NameGenerator;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
import org.pentaho.reporting.libraries.xmlns.common.AttributeList;
import org.pentaho.reporting.libraries.xmlns.writer.XmlWriter;
import org.pentaho.reporting.libraries.xmlns.writer.XmlWriterSupport;
@SuppressWarnings("HardCodedStringLiteral")
public abstract class AbstractHtmlPrinter
{
public static final String XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
protected static final String[] XHTML_HEADER = {
"<!DOCTYPE html",
" PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"",
" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"};
protected static final StyleBuilder.CSSKeys[] EMPTY_CELL_ATTRNAMES = new StyleBuilder.CSSKeys[]{StyleBuilder.CSSKeys.FONT_SIZE};
protected static final String[] EMPTY_CELL_ATTRVALS = new String[]{"1pt"};
private static final String GENERATOR = ClassicEngineInfo.getInstance().getName() + " version "
+ ClassicEngineInfo.getInstance().getVersion();
private DefaultStyleBuilderFactory styleBuilderFactory;
private DefaultHtmlContentGenerator contentGenerator;
private Configuration configuration;
private ContentItem styleFile;
private String styleFileUrl;
private HtmlTagHelper tagHelper;
private boolean allowRawLinkTargets;
public AbstractHtmlPrinter(ResourceManager resourceManager)
{
if (resourceManager == null)
{
throw new NullPointerException("A resource-manager must be given.");
}
contentGenerator = new DefaultHtmlContentGenerator(resourceManager);
}
protected void initialize(Configuration configuration)
{
this.configuration = configuration;
this.contentGenerator.setCopyExternalImages("true".equals
(configuration.getConfigProperty(HtmlTableModule.COPY_EXTERNAL_IMAGES)));
this.allowRawLinkTargets = "true".equals
(configuration.getConfigProperty(HtmlTableModule.ALLOW_RAW_LINK_TARGETS));
styleBuilderFactory = new DefaultStyleBuilderFactory();
styleBuilderFactory.configure(ClassicEngineBoot.getInstance().getGlobalConfig());
this.tagHelper = new HtmlTagHelper(configuration, styleBuilderFactory);
}
public boolean isAllowRawLinkTargets()
{
return allowRawLinkTargets;
}
public StyleManager getStyleManager()
{
return this.tagHelper.getStyleManager();
}
public void setStyleManager(final StyleManager styleManager)
{
this.tagHelper.setStyleManager(styleManager);
}
public HtmlTagHelper getTagHelper()
{
return tagHelper;
}
public Configuration getConfiguration()
{
return configuration;
}
public void setDataWriter(final ContentLocation dataLocation,
final NameGenerator dataNameGenerator)
{
this.contentGenerator.setDataWriter(dataLocation, dataNameGenerator, getContentReWriteService());
}
protected abstract ContentUrlReWriteService getContentReWriteService();
public StyleBuilder getStyleBuilder()
{
return tagHelper.getStyleBuilder();
}
public DefaultStyleBuilderFactory getStyleBuilderFactory()
{
return styleBuilderFactory;
}
public DefaultHtmlContentGenerator getContentGenerator()
{
return contentGenerator;
}
protected ResourceManager getResourceManager()
{
return contentGenerator.getResourceManager();
}
protected boolean isProportionalColumnWidths()
{
return "true".equals(getConfiguration().getConfigProperty(HtmlTableModule.PROPORTIONAL_COLUMN_WIDTHS, "false"));
}
protected void writeColumnDeclaration(final SlimSheetLayout sheetLayout,
final XmlWriter xmlWriter)
throws IOException
{
StyleBuilder styleBuilder = getStyleBuilder();
DefaultStyleBuilderFactory styleBuilderFactory = getStyleBuilderFactory();
if (sheetLayout == null)
{
throw new NullPointerException();
}
final int colCount = sheetLayout.getColumnCount();
final int fullWidth = (int) StrictGeomUtility.toExternalValue(sheetLayout.getMaxWidth());
final String[] colWidths = new String[colCount];
final boolean proportionalColumnWidths = isProportionalColumnWidths();
final NumberFormat pointConverter = styleBuilder.getPointConverter();
final String unit;
if (proportionalColumnWidths)
{
unit = "%";
double totalWidth = 0;
for (int col = 0; col < colCount; col++)
{
final int width = (int) StrictGeomUtility.toExternalValue(sheetLayout.getCellWidth(col, col + 1));
final double colWidth = styleBuilderFactory.fixLengthForSafari(Math.max(1, width * 100.0d / fullWidth));
if (col == colCount - 1)
{
colWidths[col] = pointConverter.format(100 - totalWidth);
}
else
{
totalWidth += colWidth;
colWidths[col] = pointConverter.format(colWidth);
}
}
}
else
{
unit = "pt";
double totalWidth = 0;
for (int col = 0; col < colCount; col++)
{
final int width = (int) StrictGeomUtility.toExternalValue(sheetLayout.getCellWidth(col, col + 1));
final double colWidth = styleBuilderFactory.fixLengthForSafari(Math.max(1, width));
if (col == colCount - 1)
{
colWidths[col] = pointConverter.format(fullWidth - totalWidth);
}
else
{
totalWidth += colWidth;
colWidths[col] = pointConverter.format(colWidth);
}
}
}
for (int col = 0; col < colCount; col++)
{
// Print the table.
styleBuilder.clear();
styleBuilder.append(DefaultStyleBuilder.CSSKeys.WIDTH, colWidths[col], unit);
xmlWriter.writeTag(null, "col", "style", styleBuilder.toString(), XmlWriterSupport.CLOSE);
}
}
protected void writeCompleteHeader(final XmlWriter docWriter,
final String sheetName,
final ReportAttributeMap attributes,
final String styleSheetUrl,
final StyleManager inlineStyleSheet) throws IOException
{
Configuration configuration = getConfiguration();
final String encoding = configuration.getConfigProperty
(HtmlTableModule.ENCODING, EncodingRegistry.getPlatformDefaultEncoding());
docWriter.writeXmlDeclaration(encoding);
for (int i = 0; i < XHTML_HEADER.length; i++)
{
docWriter.writeText(XHTML_HEADER[i]);
docWriter.writeNewLine();
}
docWriter.writeTag(XHTML_NAMESPACE, "html", XmlWriterSupport.OPEN);
docWriter.writeTag(XHTML_NAMESPACE, "head", XmlWriterSupport.OPEN);
final String title = configuration.getConfigProperty(HtmlTableModule.TITLE);
if (title != null)
{
docWriter.writeTag(XHTML_NAMESPACE, "title", XmlWriterSupport.OPEN);
docWriter.writeTextNormalized(title, false);
docWriter.writeCloseTag();
}
// if no single title defined, use the sheetname function previously computed
else if (sheetName != null)
{
docWriter.writeTag(XHTML_NAMESPACE, "title", XmlWriterSupport.OPEN);
docWriter.writeTextNormalized(sheetName, true);
docWriter.writeCloseTag();
}
else
{
docWriter.writeTag(XHTML_NAMESPACE, "title", XmlWriterSupport.OPEN);
docWriter.writeText(" ");
docWriter.writeCloseTag();
}
writeMeta(docWriter, "subject",
configuration.getConfigProperty(HtmlTableModule.SUBJECT));
writeMeta(docWriter, "author",
configuration.getConfigProperty(HtmlTableModule.AUTHOR));
writeMeta(docWriter, "keywords",
configuration.getConfigProperty(HtmlTableModule.KEYWORDS));
writeMeta(docWriter, "generator", GENERATOR);
final AttributeList metaAttrs = new AttributeList();
metaAttrs.setAttribute(XHTML_NAMESPACE, "http-equiv", "content-type");
metaAttrs.setAttribute(XHTML_NAMESPACE, "content", "text/html; charset=" + encoding);
docWriter.writeTag(XHTML_NAMESPACE, "meta", metaAttrs, XmlWriterSupport.CLOSE);
if (styleSheetUrl != null)
{
final AttributeList attrList = new AttributeList();
attrList.setAttribute(XHTML_NAMESPACE, "type", "text/css");
attrList.setAttribute(XHTML_NAMESPACE, "rel", "stylesheet");
attrList.setAttribute(XHTML_NAMESPACE, "href", styleSheetUrl);
docWriter.writeTag(XHTML_NAMESPACE, "link", attrList, XmlWriterSupport.CLOSE);
}
else if (inlineStyleSheet != null)
{
docWriter.writeTag(XHTML_NAMESPACE, "style", "type", "text/css", XmlWriterSupport.OPEN);
StringWriter writer = new StringWriter();
inlineStyleSheet.write(writer);
docWriter.writeText(writer.toString());
docWriter.writeCloseTag();
}
final Object rawHeaderContent = attributes.getAttribute
(AttributeNames.Html.NAMESPACE, AttributeNames.Html.EXTRA_RAW_HEADER_CONTENT);
if (rawHeaderContent != null)
{
// Warning: This text is not escaped or processed in any way. it is *RAW* content.
docWriter.writeText(String.valueOf(rawHeaderContent));
}
docWriter.writeCloseTag();
}
private void writeMeta(final XmlWriter writer, final String name, final String value) throws IOException
{
if (value == null)
{
return;
}
final AttributeList attrList = new AttributeList();
attrList.setAttribute(XHTML_NAMESPACE, "name", name);
attrList.setAttribute(XHTML_NAMESPACE, "content", value);
writer.writeTag(XHTML_NAMESPACE, "meta", attrList, XmlWriterSupport.CLOSE);
}
protected StyleManager createStyleManager()
{
if (isCreateBodyFragment() == false && isInlineStylesRequested() == false)
{
return new GlobalStyleManager();
}
return new InlineStyleManager();
}
protected boolean isCreateBodyFragment()
{
return "true".equals(getConfiguration().getConfigProperty(HtmlTableModule.BODY_FRAGMENT, "false"));
}
protected boolean isInlineStylesRequested()
{
return "true".equals(getConfiguration().getConfigProperty(HtmlTableModule.INLINE_STYLE));
}
protected void generateHeaderOnOpen(final ReportAttributeMap attributeMap,
final String sheetName,
final XmlWriter xmlWriter) throws IOException
{
if (isCreateBodyFragment() == false)
{
if (isInlineStylesRequested())
{
writeCompleteHeader(xmlWriter, sheetName, attributeMap, null, null);
}
else
{
if (isExternalStyleSheetRequested())
{
if (isForceBufferedWriting() == false)
{
writeCompleteHeader(xmlWriter, sheetName, attributeMap, styleFileUrl, null);
}
}
}
xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, "body", XmlWriterSupport.OPEN);
}
}
protected void generateExternalStylePlaceHolder() throws ContentIOException, URLRewriteException
{
if (isExternalStyleSheetRequested() == false)
{
return;
}
this.styleFile = getContentGenerator().createItem("style", "text/css");
this.styleFileUrl = getContentReWriteService().rewriteContentDataItem(styleFile);
}
public ContentItem getStyleFile()
{
return styleFile;
}
public String getStyleFileUrl()
{
return styleFileUrl;
}
protected WriterService createWriterService(final OutputStream out) throws UnsupportedEncodingException
{
final String encoding = configuration.getConfigProperty
(HtmlTableModule.ENCODING, EncodingRegistry.getPlatformDefaultEncoding());
if (isCreateBodyFragment() == false)
{
if (isInlineStylesRequested())
{
return WriterService.createPassThroughService(out, encoding);
}
else
{
if (isExternalStyleSheetRequested() && isForceBufferedWriting() == false)
{
return WriterService.createPassThroughService(out, encoding);
}
else
{
return WriterService.createBufferedService(out, encoding);
}
}
}
else
{
return WriterService.createPassThroughService(out, encoding);
}
}
protected boolean isForceBufferedWriting()
{
return "true".equals(getConfiguration().getConfigProperty(HtmlTableModule.FORCE_BUFFER_WRITING));
}
protected boolean isExternalStyleSheetRequested()
{
if (isCreateBodyFragment())
{
// body-fragments have no header ..
return false;
}
// We will add the style-declarations directly to the HTML elements ..
if (isInlineStylesRequested())
{
return false;
}
// Without the ability to create external files, we cannot create external stylesheet.
if (getContentGenerator().isExternalContentAvailable() == false)
{
return false;
}
// User explicitly requested internal styles by disabling the external-style property.
return "true".equals(getConfiguration().getConfigProperty(HtmlTableModule.EXTERNALIZE_STYLE, "true"));
}
protected void performCloseFile(final String sheetName,
final ReportAttributeMap logicalPageBox,
final WriterService writer)
throws IOException, ContentIOException
{
XmlWriter xmlWriter = writer.getXmlWriter();
xmlWriter.writeCloseTag(); // for the opening table ..
final Object rawFooterContent = logicalPageBox.getAttribute(AttributeNames.Html.NAMESPACE,
AttributeNames.Html.EXTRA_RAW_FOOTER_CONTENT);
if (rawFooterContent != null)
{
xmlWriter.writeText(String.valueOf(rawFooterContent));
}
if (isCreateBodyFragment())
{
xmlWriter.close();
return;
}
ContentItem styleFile = getStyleFile();
if (styleFile != null)
{
final String encoding = getConfiguration().getConfigProperty
(HtmlTableModule.ENCODING, EncodingRegistry.getPlatformDefaultEncoding());
final Writer styleOut = new OutputStreamWriter
(new BufferedOutputStream(styleFile.getOutputStream()), encoding);
getStyleManager().write(styleOut);
styleOut.flush();
styleOut.close();
if (isForceBufferedWriting() == false)
{
// A complete header had been written when the processing started ..
xmlWriter.writeCloseTag(); // for the body tag
xmlWriter.writeCloseTag(); // for the HTML tag
xmlWriter.close();
return;
}
}
if (isInlineStylesRequested())
{
xmlWriter.writeCloseTag(); // for the body tag
xmlWriter.writeCloseTag(); // for the HTML tag
xmlWriter.close();
return;
}
// handle external stylesheets. They need to be injected into the header.
// finish the body fragment
xmlWriter.writeCloseTag(); // for the body ..
xmlWriter.flush();
final XmlWriter docWriter = writer.createHeaderXmlWriter();
if (styleFile != null)
{
// now its time to write the header with the link to the style-sheet-file
writeCompleteHeader(docWriter, sheetName, logicalPageBox, getStyleFileUrl(), null);
}
else
{
writeCompleteHeader(docWriter, sheetName, logicalPageBox, null, getStyleManager());
}
// no need to check for IOExceptions here, as we know the implementation does not create such things
final MemoryStringReader stringReader = writer.getBufferWriter().createReader();
docWriter.writeStream(stringReader);
stringReader.close();
docWriter.writeCloseTag(); // for the html ..
docWriter.close();
}
protected void openSheet(final ReportAttributeMap logicalPage,
final String sheetName,
final OutputProcessorMetaData metaData,
final SlimSheetLayout sheetLayout,
final XmlWriter xmlWriter) throws ContentIOException, URLRewriteException, IOException
{
setStyleManager(createStyleManager());
generateExternalStylePlaceHolder();
generateHeaderOnOpen(logicalPage, sheetName, xmlWriter);
final Object rawContent = logicalPage.getAttribute(AttributeNames.Html.NAMESPACE,
AttributeNames.Html.EXTRA_RAW_CONTENT);
if (rawContent != null)
{
xmlWriter.writeText(String.valueOf(rawContent));
}
// table name
if ("true".equals(metaData.getConfiguration().getConfigProperty
("org.pentaho.reporting.engine.classic.core.modules.output.table.html.EnableSheetNameProcessing")))
{
if (sheetName != null)
{
xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, "h1", getTagHelper().createSheetNameAttributes(), XmlWriterSupport.OPEN);
xmlWriter.writeTextNormalized(sheetName, true);
xmlWriter.writeCloseTag();
}
}
// table
xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, "table",
getTagHelper().createTableAttributes(sheetLayout, logicalPage),
XmlWriterSupport.OPEN);
writeColumnDeclaration(sheetLayout, xmlWriter);
}
protected void writeBackgroundCell(CellBackground background, XmlWriter xmlWriter) throws IOException
{
final boolean emptyCellsUseCSS = getTagHelper().isEmptyCellsUseCSS();
if (background == null)
{
if (emptyCellsUseCSS)
{
xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, "td", XmlWriterSupport.CLOSE);
}
else
{
final AttributeList attrs = new AttributeList();
attrs.setAttribute(HtmlPrinter.XHTML_NAMESPACE, "style", "font-size: 1pt");
xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, "td", attrs, XmlWriterSupport.OPEN);
xmlWriter.writeText(" ");
xmlWriter.writeCloseTag();
}
return;
}
StyleBuilder styleBuilder = getStyleBuilder();
DefaultStyleBuilderFactory styleBuilderFactory = getStyleBuilderFactory();
// Background cannot be null at this point ..
final String[] anchor = background.getAnchors();
if (anchor.length == 0 && emptyCellsUseCSS)
{
final StyleBuilder cellStyle = styleBuilderFactory.createCellStyle(styleBuilder, null, null, background, null, null);
final AttributeList cellAttributes = getTagHelper().createCellAttributes(1, 1, null, null, background, cellStyle);
xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, "td", cellAttributes, XmlWriterSupport.CLOSE);
}
else
{
final StyleBuilder cellStyle = styleBuilderFactory.createCellStyle
(styleBuilder, null, null, background, HtmlPrinter.EMPTY_CELL_ATTRNAMES, HtmlPrinter.EMPTY_CELL_ATTRVALS);
final AttributeList cellAttributes = getTagHelper().createCellAttributes(1, 1, null, null, background, cellStyle);
xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, "td", cellAttributes, XmlWriterSupport.OPEN);
for (int i = 0; i < anchor.length; i++)
{
xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, "a", "name", anchor[i], XmlWriterSupport.CLOSE);
}
xmlWriter.writeText(" ");
xmlWriter.writeCloseTag();
}
}
}