Package org.pentaho.reporting.engine.classic.core.modules.output.table.html

Source Code of org.pentaho.reporting.engine.classic.core.modules.output.table.html.HtmlTextExtractor

/*
* 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) 2001 - 2009 Object Refinery Ltd, Pentaho Corporation and Contributors..  All rights reserved.
*/

package org.pentaho.reporting.engine.classic.core.modules.output.table.html;

import java.io.IOException;
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.ImageContainer;
import org.pentaho.reporting.engine.classic.core.InvalidReportStateException;
import org.pentaho.reporting.engine.classic.core.ReportAttributeMap;
import org.pentaho.reporting.engine.classic.core.URLImageContainer;
import org.pentaho.reporting.engine.classic.core.imagemap.ImageMap;
import org.pentaho.reporting.engine.classic.core.imagemap.parser.ImageMapWriter;
import org.pentaho.reporting.engine.classic.core.layout.model.BlockRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.CanvasRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.InlineRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.LayoutNodeTypes;
import org.pentaho.reporting.engine.classic.core.layout.model.ParagraphRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderNode;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderableReplacedContent;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderableReplacedContentBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderableText;
import org.pentaho.reporting.engine.classic.core.layout.model.SpacerRenderNode;
import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorMetaData;
import org.pentaho.reporting.engine.classic.core.layout.output.RenderUtility;
import org.pentaho.reporting.engine.classic.core.layout.text.GlyphList;
import org.pentaho.reporting.engine.classic.core.modules.output.table.base.DefaultTextExtractor;
import org.pentaho.reporting.engine.classic.core.modules.output.table.html.helper.HtmlOutputProcessingException;
import org.pentaho.reporting.engine.classic.core.modules.output.table.html.helper.StyleBuilder;
import org.pentaho.reporting.engine.classic.core.modules.output.table.html.helper.StyleManager;
import org.pentaho.reporting.engine.classic.core.style.ElementStyleKeys;
import org.pentaho.reporting.engine.classic.core.style.StyleSheet;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictBounds;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictGeomUtility;
import org.pentaho.reporting.libraries.base.util.FastStack;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.pentaho.reporting.libraries.repository.ContentIOException;
import org.pentaho.reporting.libraries.resourceloader.ResourceKey;
import org.pentaho.reporting.libraries.resourceloader.factory.drawable.DrawableWrapper;
import org.pentaho.reporting.libraries.xmlns.common.AttributeList;
import org.pentaho.reporting.libraries.xmlns.writer.CharacterEntityParser;
import org.pentaho.reporting.libraries.xmlns.writer.HtmlCharacterEntities;
import org.pentaho.reporting.libraries.xmlns.writer.XmlWriter;
import org.pentaho.reporting.libraries.xmlns.writer.XmlWriterSupport;

/**
* Creation-Date: 02.11.2007, 15:58:29
*
* @author Thomas Morgner
*/
public class HtmlTextExtractor extends DefaultTextExtractor
{
  private static class ElementProcessInfo
  {
    private boolean link;
    private boolean extraAttributes;

    private ElementProcessInfo(final boolean link, final boolean extraAttributes)
    {
      this.link = link;
      this.extraAttributes = extraAttributes;
    }

    public boolean isLink()
    {
      return link;
    }

    public boolean isExtraAttributes()
    {
      return extraAttributes;
    }
  }

  private static final String DIV_TAG = "div";
  private static final String HREF_ATTR = "href";
  private static final String TARGET_ATTR = "target";
  private static final String TITLE_ATTR = "title";
  private static final String A_TAG = "a";
  private static final String BR_TAG = "br";
  private static final String SPAN_TAG = "span";
  private static final String IMG_TAG = "img";
  private static final String SRC_ATTR = "src";
  private static final String USEMAP_ATTR = "usemap";
  private static final String PT_UNIT = "pt";
  private static final String WIDTH_STYLE = "width";
  private static final String HEIGHT_STYLE = "height";
  private static final String ALT_ATTR = "alt";

  private FastStack processStack;
  private OutputProcessorMetaData metaData;
  private XmlWriter xmlWriter;
  private StyleManager styleManager;
  private StyleBuilder styleBuilder;
  private HtmlContentGenerator contentGenerator;
  private CharacterEntityParser characterEntityParser;
  private boolean result;
  private boolean safariLengthFix;
  private RenderBox firstElement;
  private boolean useWhitespacePreWrap;

  public HtmlTextExtractor(final OutputProcessorMetaData metaData,
                           final XmlWriter xmlWriter,
                           final StyleManager styleManager,
                           final HtmlContentGenerator contentGenerator)
  {
    super(metaData);
    if (xmlWriter == null)
    {
      throw new NullPointerException();
    }
    if (styleManager == null)
    {
      throw new NullPointerException();
    }
    if (contentGenerator == null)
    {
      throw new NullPointerException();
    }

    this.contentGenerator = contentGenerator;
    this.processStack = new FastStack();
    this.metaData = metaData;
    this.xmlWriter = xmlWriter;
    this.styleManager = styleManager;
    this.styleBuilder = new StyleBuilder();
    this.characterEntityParser = HtmlCharacterEntities.getEntityParser();
    this.safariLengthFix = ("true".equals(ClassicEngineBoot.getInstance().getGlobalConfig().getConfigProperty
        ("org.pentaho.reporting.engine.classic.core.modules.output.table.html.SafariLengthHack")));
    this.useWhitespacePreWrap = ("true".equals(ClassicEngineBoot.getInstance().getGlobalConfig().getConfigProperty
        ("org.pentaho.reporting.engine.classic.core.modules.output.table.html.UseWhitespacePreWrap")));
  }

  public boolean performOutput(final RenderBox content, final boolean ignoreFirstElement) throws IOException
  {
    styleBuilder.clear();
    clearText();
    setRawResult(null);
    result = false;
    processStack.clear();

    if (ignoreFirstElement)
    {
      firstElement = content;
    }

    try
    {
      if (content.getNodeType() == LayoutNodeTypes.TYPE_BOX_PARAGRAPH)
      {
        processParagraphCell((ParagraphRenderBox) content);
      }
      else if (content.getNodeType() == LayoutNodeTypes.TYPE_BOX_CONTENT)
      {
        processRenderableContent((RenderableReplacedContentBox) content);
      }
      else
      {
        processBoxChilds(content);
      }
    }
    finally
    {
      processStack.clear();
    }
    return result;
  }

  /**
   * Prints the contents of a canvas box. This can happen only once per cell, as every canvas box creates its
   * own cell at some point. If for some strange reason a canvas box appears in the middle of a box-structure,
   * your layouter is probably a mess and this method will treat the box as a generic content container.
   *
   * @param box the canvas box
   * @return true, if the child content will be processed, false otherwise.
   */
  public boolean startCanvasBox(final CanvasRenderBox box)
  {
    if (box.getStaticBoxLayoutProperties().isVisible() == false)
    {
      return false;
    }

    try
    {
      final ReportAttributeMap attrs = box.getAttributes();
      final boolean extraAttributes;
      if (firstElement != box)
      {
        final AttributeList attrList = new AttributeList();
        HtmlPrinter.applyHtmlAttributes(attrs, attrList);
        if (attrList.isEmpty() == false)
        {
          xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, DIV_TAG, attrList, XmlWriterSupport.OPEN);
        }
        extraAttributes = false;
      }
      else
      {
        extraAttributes = true;
      }

      final Object rawContent = attrs.getAttribute(AttributeNames.Html.NAMESPACE,
          AttributeNames.Html.EXTRA_RAW_CONTENT);
      if (rawContent != null)
      {
        xmlWriter.writeText(String.valueOf(rawContent));
      }

      final StyleSheet styleSheet = box.getStyleSheet();
      final String target = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_TARGET);
      if (target != null)
      {
        final String window = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_WINDOW);
        final AttributeList linkAttr = new AttributeList();
        linkAttr.setAttribute(HtmlPrinter.XHTML_NAMESPACE, HREF_ATTR, target);
        if (window != null && StringUtils.startsWithIgnoreCase(target, "javascript:") == false)
        {
          linkAttr.setAttribute(HtmlPrinter.XHTML_NAMESPACE, TARGET_ATTR, normalizeWindow(window));
        }
        final String title = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_TITLE);
        if (title != null)
        {
          linkAttr.setAttribute(HtmlPrinter.XHTML_NAMESPACE, TITLE_ATTR, title);
        }
        xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, A_TAG, linkAttr, XmlWriterSupport.OPEN);
        processStack.push(new ElementProcessInfo(true, extraAttributes));
      }
      else
      {
        processStack.push(new ElementProcessInfo(false, extraAttributes));
      }

      if (Boolean.TRUE.equals(attrs.getAttribute(AttributeNames.Html.NAMESPACE, AttributeNames.Html.SURPRESS_CONTENT)))
      {
        return false;
      }

      return true;
    }
    catch (IOException e)
    {
      throw new HtmlOutputProcessingException("Failed to perform IO", e);
    }
  }

  private String normalizeWindow(final String window)
  {
    if ("_top".equalsIgnoreCase(window))
    {
      return "_top";
    }
    if ("_self".equalsIgnoreCase(window))
    {
      return "_self";
    }
    if ("_parent".equalsIgnoreCase(window))
    {
      return "_parent";
    }
    if ("_blank".equalsIgnoreCase(window))
    {
      return "_blank";
    }
    return window;
  }

  public void finishCanvasBox(final CanvasRenderBox box)
  {
    if (box.getStaticBoxLayoutProperties().isVisible() == false)
    {
      return;
    }
    try
    {
      final ElementProcessInfo info = (ElementProcessInfo) processStack.pop();
      if (info.isLink())
      {
        xmlWriter.writeCloseTag();
      }

      final Object rawFooterContent = box.getAttributes().getAttribute(AttributeNames.Html.NAMESPACE,
          AttributeNames.Html.EXTRA_RAW_FOOTER_CONTENT);
      if (rawFooterContent != null)
      {
        xmlWriter.writeText(String.valueOf(rawFooterContent));
      }

      if (info.isExtraAttributes())
      {
        xmlWriter.writeCloseTag();
      }
    }
    catch (IOException e)
    {
      throw new HtmlOutputProcessingException("Failed to perform IO", e);
    }
  }

  /**
   * Prints a paragraph cell. This is a special entry point used by the processContent method and is never
   * called from elsewhere. This method assumes that the attributes of the paragraph have been processed as
   * part of the table-cell processing.
   *
   * @param box the paragraph box
   * @throws IOException if an IO error occured.
   */
  protected void processParagraphCell(final ParagraphRenderBox box) throws IOException
  {
    if (box.getStaticBoxLayoutProperties().isVisible() == false)
    {
      return;
    }

    final StyleSheet styleSheet = box.getStyleSheet();
    final String target = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_TARGET);
    if (target != null)
    {
      final String window = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_WINDOW);
      final AttributeList linkAttr = new AttributeList();
      linkAttr.setAttribute(HtmlPrinter.XHTML_NAMESPACE, HREF_ATTR, target);
      if (window != null && StringUtils.startsWithIgnoreCase(target, "javascript:") == false)
      {
        linkAttr.setAttribute(HtmlPrinter.XHTML_NAMESPACE, TARGET_ATTR, normalizeWindow(window));
      }
      final String title = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_TITLE);
      if (title != null)
      {
        linkAttr.setAttribute(HtmlPrinter.XHTML_NAMESPACE, TITLE_ATTR, title);
      }

      styleManager.updateStyle(HtmlPrinter.produceTextStyle
          (styleBuilder, box, false, false, safariLengthFix, useWhitespacePreWrap),
          linkAttr);
      xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, A_TAG, linkAttr, XmlWriterSupport.OPEN);
    }

    if (Boolean.TRUE.equals
        (box.getAttributes().getAttribute(AttributeNames.Html.NAMESPACE, AttributeNames.Html.SURPRESS_CONTENT)) == false)
    {
      processParagraphChilds(box);
    }

    if (target != null)
    {
      xmlWriter.writeCloseTag(); // closing the span ..
    }

  }

  protected void addEmptyBreak()
  {
    try
    {
      xmlWriter.writeText(" ");
    }
    catch (IOException e)
    {
      throw new HtmlOutputProcessingException("Failed to perform IO", e);
    }
  }

  protected void addSoftBreak()
  {
    try
    {
      xmlWriter.writeText(" ");
    }
    catch (IOException e)
    {
      throw new HtmlOutputProcessingException("Failed to perform IO", e);
    }
  }

  protected void addLinebreak()
  {
    try
    {
      result = true;
      xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, BR_TAG, XmlWriterSupport.CLOSE);
    }
    catch (IOException e)
    {
      throw new HtmlOutputProcessingException("Failed to perform IO", e);
    }
  }

  protected boolean startBlockBox(final BlockRenderBox box)
  {
    if (box.getStaticBoxLayoutProperties().isVisible() == false)
    {
      return false;
    }

    try
    {
      final AttributeList attrList = new AttributeList();
      final ReportAttributeMap attrs = box.getAttributes();
      if (firstElement != box)
      {
        HtmlPrinter.applyHtmlAttributes(attrs, attrList);
        styleManager.updateStyle(HtmlPrinter.produceTextStyle
            (styleBuilder, box, true, false, safariLengthFix, useWhitespacePreWrap), attrList);
      }

      xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, DIV_TAG, attrList, XmlWriterSupport.OPEN);

      final Object rawContent = attrs.getAttribute(AttributeNames.Html.NAMESPACE,
          AttributeNames.Html.EXTRA_RAW_CONTENT);
      if (rawContent != null)
      {
        xmlWriter.writeText(String.valueOf(rawContent));
      }

      final StyleSheet styleSheet = box.getStyleSheet();
      if (firstElement != box)
      {
        final String anchor = (String) styleSheet.getStyleProperty(ElementStyleKeys.ANCHOR_NAME);
        if (anchor != null)
        {
          xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, A_TAG, "name", anchor, XmlWriterSupport.CLOSE);
        }
      }
      final String target = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_TARGET);
      if (target != null)
      {
        final String window = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_WINDOW);
        final AttributeList linkAttr = new AttributeList();
        linkAttr.setAttribute(HtmlPrinter.XHTML_NAMESPACE, HREF_ATTR, target);
        if (window != null && StringUtils.startsWithIgnoreCase(target, "javascript:") == false)
        {
          linkAttr.setAttribute(HtmlPrinter.XHTML_NAMESPACE, TARGET_ATTR, normalizeWindow(window));
        }
        final String title = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_TITLE);
        if (title != null)
        {
          linkAttr.setAttribute(HtmlPrinter.XHTML_NAMESPACE, TITLE_ATTR, title);
        }
        xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, A_TAG, linkAttr, XmlWriterSupport.OPEN);
        processStack.push(new ElementProcessInfo(true, true));
      }
      else
      {
        processStack.push(new ElementProcessInfo(false, true));
      }

      if (Boolean.TRUE.equals(attrs.getAttribute(AttributeNames.Html.NAMESPACE, AttributeNames.Html.SURPRESS_CONTENT)))
      {
        return false;
      }

      return true;
    }
    catch (IOException e)
    {
      throw new HtmlOutputProcessingException("Failed to perform IO", e);
    }
  }

  protected void finishBlockBox(final BlockRenderBox box)
  {
    if (box.getStaticBoxLayoutProperties().isVisible() == false)
    {
      return;
    }

    try
    {
      final ElementProcessInfo info = (ElementProcessInfo) processStack.pop();
      if (info.isLink())
      {
        // close anchor tag
        xmlWriter.writeCloseTag();
      }

      final Object rawFooterContent = box.getAttributes().getAttribute(AttributeNames.Html.NAMESPACE,
          AttributeNames.Html.EXTRA_RAW_FOOTER_CONTENT);
      if (rawFooterContent != null)
      {
        xmlWriter.writeText(String.valueOf(rawFooterContent));
      }

      // close mandatory DIV
      xmlWriter.writeCloseTag();
    }
    catch (IOException e)
    {
      throw new HtmlOutputProcessingException("Failed to perform IO", e);
    }
  }

  /**
   * Like a canvas box, a row-box should be split into several cells already. Therefore we treat it as a generic
   * content container instead.
   *
   * @param box
   * @return
   */
  protected boolean startRowBox(final RenderBox box)
  {
    if (box.getStaticBoxLayoutProperties().isVisible() == false)
    {
      return false;
    }

    try
    {
      final AttributeList attrList = new AttributeList();
      final ReportAttributeMap attrs = box.getAttributes();
      if (firstElement != box)
      {
        HtmlPrinter.applyHtmlAttributes(attrs, attrList);
        styleManager.updateStyle(HtmlPrinter.produceTextStyle
            (styleBuilder, box, true, false, safariLengthFix, useWhitespacePreWrap), attrList);
      }

      xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, DIV_TAG, attrList, XmlWriterSupport.OPEN);

      final Object rawContent = attrs.getAttribute(AttributeNames.Html.NAMESPACE,
          AttributeNames.Html.EXTRA_RAW_CONTENT);
      if (rawContent != null)
      {
        xmlWriter.writeText(String.valueOf(rawContent));
      }

      final StyleSheet styleSheet = box.getStyleSheet();
      if (firstElement != box)
      {
        final String anchor = (String) styleSheet.getStyleProperty(ElementStyleKeys.ANCHOR_NAME);
        if (anchor != null)
        {
          xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, A_TAG, "name", anchor, XmlWriterSupport.CLOSE);
        }
      }

      final String target = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_TARGET);
      if (target != null)
      {
        final String window = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_WINDOW);
        final AttributeList linkAttr = new AttributeList();
        linkAttr.setAttribute(HtmlPrinter.XHTML_NAMESPACE, HREF_ATTR, target);
        if (window != null && StringUtils.startsWithIgnoreCase(target, "javascript:") == false)
        {
          linkAttr.setAttribute(HtmlPrinter.XHTML_NAMESPACE, TARGET_ATTR, normalizeWindow(window));
        }
        final String title = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_TITLE);
        if (title != null)
        {
          linkAttr.setAttribute(HtmlPrinter.XHTML_NAMESPACE, TITLE_ATTR, title);
        }
        xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, A_TAG, linkAttr, XmlWriterSupport.OPEN);
        processStack.push(new ElementProcessInfo(true, true));
      }
      else
      {
        processStack.push(new ElementProcessInfo(false, true));
      }

      if (Boolean.TRUE.equals(attrs.getAttribute(AttributeNames.Html.NAMESPACE, AttributeNames.Html.SURPRESS_CONTENT)))
      {
        return false;
      }

      return true;
    }
    catch (IOException e)
    {
      throw new HtmlOutputProcessingException("Failed to perform IO", e);
    }
  }

  protected void finishRowBox(final RenderBox box)
  {
    if (box.getStaticBoxLayoutProperties().isVisible() == false)
    {
      return;
    }

    try
    {
      final ElementProcessInfo info = (ElementProcessInfo) processStack.pop();
      if (info.isLink())
      {
        // close anchor tag
        xmlWriter.writeCloseTag();
      }

      final Object rawFooterContent = box.getAttributes().getAttribute(AttributeNames.Html.NAMESPACE,
          AttributeNames.Html.EXTRA_RAW_FOOTER_CONTENT);
      if (rawFooterContent != null)
      {
        xmlWriter.writeText(String.valueOf(rawFooterContent));
      }

      xmlWriter.writeCloseTag();
    }
    catch (IOException e)
    {
      throw new HtmlOutputProcessingException("Failed to perform IO", e);
    }
  }

  protected boolean startInlineBox(final InlineRenderBox box)
  {
    if (box.getStaticBoxLayoutProperties().isVisible() == false)
    {
      return false;
    }

    try
    {
      final boolean extraAttributes;
      final ReportAttributeMap attrs = box.getAttributes();
      if (firstElement != box)
      {
        final AttributeList attrList = new AttributeList();
        HtmlPrinter.applyHtmlAttributes(attrs, attrList);
        styleManager.updateStyle(HtmlPrinter.produceTextStyle
            (styleBuilder, box, true, true, safariLengthFix, useWhitespacePreWrap), attrList);

        if (attrList.isEmpty() == false)
        {
          xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, SPAN_TAG, attrList, XmlWriterSupport.OPEN);
          extraAttributes = true;
        }
        else
        {
          extraAttributes = false;
        }
      }
      else
      {
        extraAttributes = false;
      }

      final Object rawContent = attrs.getAttribute(AttributeNames.Html.NAMESPACE,
          AttributeNames.Html.EXTRA_RAW_CONTENT);
      if (rawContent != null)
      {
        xmlWriter.writeText(String.valueOf(rawContent));
      }

      final StyleSheet styleSheet = box.getStyleSheet();
      if (firstElement != box)
      {
        final String anchor = (String) styleSheet.getStyleProperty(ElementStyleKeys.ANCHOR_NAME);
        if (anchor != null)
        {
          xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, A_TAG, "name", anchor, XmlWriterSupport.CLOSE);
        }
      }

      final String target = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_TARGET);
      if (target != null)
      {
        final String window = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_WINDOW);
        final AttributeList linkAttr = new AttributeList();
        linkAttr.setAttribute(HtmlPrinter.XHTML_NAMESPACE, HREF_ATTR, target);
        if (window != null && StringUtils.startsWithIgnoreCase(target, "javascript:") == false)
        {
          linkAttr.setAttribute(HtmlPrinter.XHTML_NAMESPACE, TARGET_ATTR, normalizeWindow(window));
        }
        final String title = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_TITLE);
        if (title != null)
        {
          linkAttr.setAttribute(HtmlPrinter.XHTML_NAMESPACE, TITLE_ATTR, title);
        }
        xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, A_TAG, linkAttr, XmlWriterSupport.OPEN);
        processStack.push(new ElementProcessInfo(true, extraAttributes));
      }
      else
      {
        processStack.push(new ElementProcessInfo(false, extraAttributes));
      }

      if (Boolean.TRUE.equals(attrs.getAttribute(AttributeNames.Html.NAMESPACE, AttributeNames.Html.SURPRESS_CONTENT)))
      {
        return false;
      }

      return true;
    }
    catch (IOException e)
    {
      throw new HtmlOutputProcessingException("Failed to perform IO", e);
    }
  }

  protected void finishInlineBox(final InlineRenderBox box)
  {
    if (box.getStaticBoxLayoutProperties().isVisible() == false)
    {
      return;
    }

    try
    {
      final ElementProcessInfo info = (ElementProcessInfo) processStack.pop();
      if (info.isLink())
      {
        // close anchor tag
        xmlWriter.writeCloseTag();
      }

      final Object rawFooterContent = box.getAttributes().getAttribute(AttributeNames.Html.NAMESPACE,
          AttributeNames.Html.EXTRA_RAW_FOOTER_CONTENT);
      if (rawFooterContent != null)
      {
        xmlWriter.writeText(String.valueOf(rawFooterContent));
      }

      if (info.isExtraAttributes())
      {
        xmlWriter.writeCloseTag();
      }
    }
    catch (IOException e)
    {
      throw new HtmlOutputProcessingException("Failed to perform IO", e);
    }
  }


  protected void processOtherNode(final RenderNode node)
  {
    try
    {
      if (node.getNodeType() == LayoutNodeTypes.TYPE_NODE_TEXT)
      {
        super.processOtherNode(node);
        return;
      }

      if (node.isVirtualNode())
      {
        return;
      }

      if (node.getNodeType() == LayoutNodeTypes.TYPE_NODE_SPACER)
      {
        final SpacerRenderNode spacer = (SpacerRenderNode) node;
        final int count = Math.max(1, spacer.getSpaceCount());
        for (int i = 0; i < count; i++)
        {
          xmlWriter.writeText(" ");
        }
      }
    }
    catch (IOException e)
    {
      throw new RuntimeException("Failed", e);
    }
  }

  protected void processRenderableContent(final RenderableReplacedContentBox node)
  {
    try
    {
      final ReportAttributeMap map = node.getAttributes();
      final AttributeList attrs = new AttributeList();
      HtmlPrinter.applyHtmlAttributes(map, attrs);
      if (attrs.isEmpty() == false)
      {
        xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, DIV_TAG, attrs, XmlWriterSupport.OPEN);
      }

      final StyleSheet styleSheet = node.getStyleSheet();
      final String anchor = (String) styleSheet.getStyleProperty(ElementStyleKeys.ANCHOR_NAME);
      if (anchor != null)
      {
        xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, A_TAG, "name", anchor, XmlWriterSupport.CLOSE);
      }

      final String target = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_TARGET);
      if (target != null)
      {
        final String window = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_WINDOW);
        final AttributeList linkAttr = new AttributeList();
        linkAttr.setAttribute(HtmlPrinter.XHTML_NAMESPACE, HREF_ATTR, target);
        if (window != null && StringUtils.startsWithIgnoreCase(target, "javascript:") == false)
        {
          linkAttr.setAttribute(HtmlPrinter.XHTML_NAMESPACE, TARGET_ATTR, normalizeWindow(window));
        }
        final String title = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_TITLE);
        if (title != null)
        {
          linkAttr.setAttribute(HtmlPrinter.XHTML_NAMESPACE, TITLE_ATTR, title);
        }
        xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, A_TAG, linkAttr, XmlWriterSupport.OPEN);
      }

      processReplacedContent(node);

      if (target != null)
      {
        xmlWriter.writeCloseTag();
      }
      if (attrs.isEmpty() == false)
      {
        xmlWriter.writeCloseTag();
      }
    }
    catch (IOException e)
    {
      throw new RuntimeException("Failed", e);
    }
    catch (ContentIOException e)
    {
      throw new RuntimeException("Failed", e);
    }
  }

  /**
   * @noinspection StringConcatenation
   */
  private void processReplacedContent(final RenderableReplacedContentBox node) throws IOException, ContentIOException
  {

    final RenderableReplacedContent rc = node.getContent();
    final Object rawObject = rc.getRawObject();
    // We have to do three things here. First, w have to check what kind
    // of content we deal with.
    // todo: Check whether the raw-object would be understood by the browser
    if (rawObject instanceof URLImageContainer)
    {
      final URLImageContainer urlImageContainer = (URLImageContainer) rawObject;
      final ResourceKey source = urlImageContainer.getResourceKey();
      if (source != null)
      {
        // Cool, we have access to the raw-data. Thats always nice as we
        // dont have to recode the whole thing. We can only recode images, not drawables.
        if (contentGenerator.isRegistered(source) == false)
        {
          // Write image reference; return the name of the reference. This method will
          // return null, if the image is not recognized (it is no JPG, PNG or GIF image)
          final String name = contentGenerator.writeRaw(source);
          if (name != null)
          {
            // Write image reference ..
            final AttributeList attrList = new AttributeList();
            attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE, SRC_ATTR, name);
            attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE, "border", "0");
            // width and height and scaling and so on ..
            final StyleBuilder imgStyle = produceImageStyle(node);
            if (imgStyle == null)
            {
              final AttributeList clipAttrList = new AttributeList();
              final StyleBuilder divStyle = produceClipStyle(node);
              styleManager.updateStyle(divStyle, clipAttrList);

              xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, DIV_TAG, clipAttrList, XmlWriter.OPEN);
              xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, IMG_TAG, attrList, XmlWriter.CLOSE);
              xmlWriter.writeCloseTag();
            }
            else
            {
              styleManager.updateStyle(imgStyle, attrList);
              xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, IMG_TAG, attrList, XmlWriter.CLOSE);
            }

            contentGenerator.registerContent(source, name);
            result = true;
            return;
          }
          else
          {
            // Mark this object as non-readable. This way we dont retry the failed operation
            // over and over again.
            contentGenerator.registerFailure(source);
          }
        }
        else
        {
          final String cachedName = contentGenerator.getRegisteredName(source);
          if (cachedName != null)
          {
            final AttributeList attrList = new AttributeList();
            attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE, SRC_ATTR, cachedName);
            attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE, "border", "0");
            // width and height and scaling and so on ..
            final StyleBuilder imgStyle = produceImageStyle(node);
            if (imgStyle == null)
            {
              final AttributeList clipAttrList = new AttributeList();
              final StyleBuilder divStyle = produceClipStyle(node);
              styleManager.updateStyle(divStyle, clipAttrList);

              xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, DIV_TAG, clipAttrList, XmlWriter.OPEN);
              xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, IMG_TAG, attrList, XmlWriter.CLOSE);
              xmlWriter.writeCloseTag();
            }
            else
            {
              styleManager.updateStyle(imgStyle, attrList);
              xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, IMG_TAG, attrList, XmlWriter.CLOSE);
            }
            result = true;
            return;
          }
        }
      }
    }

    // Fallback: (At the moment, we only support drawables and images.)
    final ReportAttributeMap attributes = node.getAttributes();
    if (rawObject instanceof ImageContainer)
    {
      final String type = RenderUtility.getEncoderType(attributes);
      final float quality = RenderUtility.getEncoderQuality(attributes);

      // Make it a PNG file ..
      //xmlWriter.writeComment("Image content source:" + source);
      final String name = contentGenerator.writeImage((ImageContainer) rawObject, type, quality, true);
      if (name != null)
      {
        // Write image reference ..
        final AttributeList attrList = new AttributeList();
        attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE, SRC_ATTR, name);
        attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE, "border", "0");
        final Object titleText = attributes.getAttribute(AttributeNames.Html.NAMESPACE, AttributeNames.Html.TITLE);
        if (titleText != null)
        {
          attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE, TITLE_ATTR, String.valueOf(titleText));
        }

        final Object altText = attributes.getAttribute(AttributeNames.Html.NAMESPACE, AttributeNames.Html.ALT);
        if (altText != null)
        {
          attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE, ALT_ATTR, String.valueOf(altText));
        }
        // width and height and scaling and so on ..
        final StyleBuilder imgStyle = produceImageStyle(node);
        if (imgStyle == null)
        {
          final AttributeList clipAttrList = new AttributeList();
          final StyleBuilder divStyle = produceClipStyle(node);
          styleManager.updateStyle(divStyle, clipAttrList);

          xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, DIV_TAG, clipAttrList, XmlWriterSupport.OPEN);
          xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, IMG_TAG, attrList, XmlWriterSupport.CLOSE);
          xmlWriter.writeCloseTag();
        }
        else
        {
          styleManager.updateStyle(imgStyle, attrList);
          xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, IMG_TAG, attrList, XmlWriterSupport.CLOSE);
        }
        result = true;
      }

      return;
    }

    if (rawObject instanceof DrawableWrapper)
    {
      // render it into an Buffered image and make it a PNG file.
      final DrawableWrapper drawable = (DrawableWrapper) rawObject;
      final StrictBounds cb = new StrictBounds(node.getX(), node.getY(), node.getWidth(), node.getHeight());
      final ImageContainer image = RenderUtility.createImageFromDrawable(drawable, cb, node,
          metaData);
      if (image == null)
      {
        //xmlWriter.writeComment("Drawable content [No image generated]:" + source);
        return;
      }

      final String type = RenderUtility.getEncoderType(attributes);
      final float quality = RenderUtility.getEncoderQuality(attributes);

      final String name = contentGenerator.writeImage(image, type, quality, true);
      if (name == null)
      {
        //xmlWriter.writeComment("Drawable content [No image written]:" + source);
        return;
      }

      //xmlWriter.writeComment("Drawable content:" + source);
      // Write image reference ..
      final ImageMap imageMap;
      final AttributeList attrList = new AttributeList();
      attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE, SRC_ATTR, name);
      attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE, "border", "0");

      final Object imageMapNameOverride = attributes.getAttribute
          (AttributeNames.Html.NAMESPACE, AttributeNames.Html.IMAGE_MAP_OVERRIDE);
      if (imageMapNameOverride != null)
      {
        attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE, USEMAP_ATTR, String.valueOf(imageMapNameOverride));
        imageMap = null;
      }
      else
      {
        // only generate a image map, if the user does not specify their own onw via the override.
        // Of course, they would have to provide the map by other means as well.
        imageMap = RenderUtility.extractImageMap(node);

        if (imageMap != null)
        {
          final String mapName = imageMap.getAttribute(HtmlPrinter.XHTML_NAMESPACE, "name");
          if (mapName != null)
          {
            attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE, USEMAP_ATTR, "#" + mapName);
          }
          else
          {
            final String generatedName = "generated_" + name + "_map"; //NON-NLS
            imageMap.setAttribute(HtmlPrinter.XHTML_NAMESPACE, "name", generatedName);
            //noinspection MagicCharacter
            attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE, USEMAP_ATTR, '#' + generatedName);//NON-NLS
          }
        }
      }

      final Object titleText = attributes.getAttribute(AttributeNames.Html.NAMESPACE, AttributeNames.Html.TITLE);
      if (titleText != null)
      {
        attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE, TITLE_ATTR, String.valueOf(titleText));
      }

      final Object altText = attributes.getAttribute(AttributeNames.Html.NAMESPACE, AttributeNames.Html.ALT);
      if (altText != null)
      {
        attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE, ALT_ATTR, String.valueOf(altText));
      }
      // width and height and scaling and so on ..
      final StyleBuilder imgStyle = produceImageStyle(node);
      if (imgStyle == null)
      {
        final AttributeList clipAttrList = new AttributeList();
        final StyleBuilder divStyle = produceClipStyle(node);
        styleManager.updateStyle(divStyle, clipAttrList);

        xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, DIV_TAG, clipAttrList, XmlWriterSupport.OPEN);
        xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, IMG_TAG, attrList, XmlWriterSupport.CLOSE);
        xmlWriter.writeCloseTag();
      }
      else
      {
        styleManager.updateStyle(imgStyle, attrList);
        xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, IMG_TAG, attrList, XmlWriterSupport.CLOSE);
      }

      if (imageMap != null)
      {
        ImageMapWriter.writeImageMap(xmlWriter, imageMap, RenderUtility.getNormalizationScale(metaData));
      }
      result = true;
    }
  }

  private StyleBuilder produceClipStyle(final RenderableReplacedContentBox rc)
  {
    styleBuilder.clear(); // cuts down on object creation

    final long nodeWidth = rc.getWidth();
    final long nodeHeight = rc.getHeight();
    final NumberFormat pointConverter = styleBuilder.getPointConverter();
    styleBuilder.append("overflow", "hidden"); //NON-NLS
    styleBuilder.append(WIDTH_STYLE, pointConverter.format//NON-NLS
        (HtmlPrinter.fixLengthForSafari(StrictGeomUtility.toExternalValue(nodeWidth), safariLengthFix)), PT_UNIT);
    styleBuilder.append(HEIGHT_STYLE, pointConverter.format//NON-NLS
        (HtmlPrinter.fixLengthForSafari(StrictGeomUtility.toExternalValue(nodeHeight), safariLengthFix)), PT_UNIT);
    return styleBuilder;
  }

  /**
   * Populates the style builder with the style information for the image based on the RenderableReplacedContent
   *
   * @param rc th renderable content node.
   * @return the style-builder with the image style or null, if the image must be clipped.
   */
  private StyleBuilder produceImageStyle(final RenderableReplacedContentBox rc)
  {
    styleBuilder.clear(); // cuts down on object creation

    final NumberFormat pointConverter = styleBuilder.getPointConverter();
    final RenderableReplacedContent content = rc.getContent();
    final long contentWidth = content.getContentWidth();
    final long nodeWidth = rc.getWidth();
    final long contentHeight = content.getContentHeight();
    final long nodeHeight = rc.getHeight();

    final StyleSheet styleSheet = rc.getStyleSheet();
    if (styleSheet.getBooleanStyleProperty(ElementStyleKeys.SCALE))
    {
      if (styleSheet.getBooleanStyleProperty(ElementStyleKeys.KEEP_ASPECT_RATIO) &&
          (contentWidth > 0 && contentHeight > 0))
      {
        final double scaleFactor = Math.min(nodeWidth / (double) contentWidth, nodeHeight / (double) contentHeight);

        styleBuilder.append(WIDTH_STYLE, pointConverter.format
            (HtmlPrinter.fixLengthForSafari(StrictGeomUtility.toExternalValue((long) (contentWidth * scaleFactor)),
                safariLengthFix)), PT_UNIT);
        styleBuilder.append(HEIGHT_STYLE, pointConverter.format
            (HtmlPrinter.fixLengthForSafari(StrictGeomUtility.toExternalValue((long) (contentHeight * scaleFactor)),
                safariLengthFix)), PT_UNIT);
      }
      else
      {
        styleBuilder.append(WIDTH_STYLE, pointConverter.format
            (HtmlPrinter.fixLengthForSafari(StrictGeomUtility.toExternalValue(nodeWidth), safariLengthFix)), PT_UNIT);
        styleBuilder.append(HEIGHT_STYLE, pointConverter.format
            (HtmlPrinter.fixLengthForSafari(StrictGeomUtility.toExternalValue(nodeHeight), safariLengthFix)), PT_UNIT);
      }
    }
    else
    {
      // for plain drawable content, there is no intrinsic-width or height, so we have to use the computed
      // width and height instead.
      if (contentWidth > nodeWidth || contentHeight > nodeHeight)
      {
        // There is clipping involved. The img-element does *not* receive a width or height property.
        // the width and height is applied to an external DIV element instead.
        return null;
      }

      if (contentWidth == 0 && contentHeight == 0)
      {
        // Drawable content has no intrinsic height or width, therefore we must not use the content size at all.
        styleBuilder.append(WIDTH_STYLE, pointConverter.format
            (HtmlPrinter.fixLengthForSafari(StrictGeomUtility.toExternalValue(nodeWidth), safariLengthFix)), PT_UNIT);
        styleBuilder.append(HEIGHT_STYLE, pointConverter.format
            (HtmlPrinter.fixLengthForSafari(StrictGeomUtility.toExternalValue(nodeHeight), safariLengthFix)), PT_UNIT);
      }
      else
      {
        final long width = Math.min(nodeWidth, contentWidth);
        final long height = Math.min(nodeHeight, contentHeight);
        styleBuilder.append(WIDTH_STYLE, pointConverter.format
            (HtmlPrinter.fixLengthForSafari(StrictGeomUtility.toExternalValue(width), safariLengthFix)), PT_UNIT);
        styleBuilder.append(HEIGHT_STYLE, pointConverter.format
            (HtmlPrinter.fixLengthForSafari(StrictGeomUtility.toExternalValue(height), safariLengthFix)), PT_UNIT);
      }
    }
    return styleBuilder;
  }

  protected void drawText(final RenderableText renderableText, final long contentX2)
  {
    try
    {

      if (renderableText.getLength() == 0)
      {
        // This text is empty.
        return;
      }
      if (renderableText.isNodeVisible(getParagraphBounds(), isOverflowX(), isOverflowY()) == false)
      {
        return;
      }

      final String text;
      if (contentX2 >= (renderableText.getX() + renderableText.getWidth()))
      {
        final GlyphList gs = renderableText.getGlyphs();
        text = gs.getText(renderableText.getOffset(), renderableText.getLength(), getCodePointBuffer());
      }
      else
      {
        final GlyphList gs = renderableText.getGlyphs();
        final int maxLength = computeMaximumTextSize(renderableText, contentX2);
        text = gs.getText(renderableText.getOffset(), maxLength, getCodePointBuffer());
      }

      if (text.length() > 0)
      {
        xmlWriter.writeText(characterEntityParser.encodeEntities(text));
        if (text.trim().length() > 0)
        {
          result = true;
        }
        clearText();
      }
    }
    catch (IOException ioe)
    {
      throw new InvalidReportStateException("Failed to write text", ioe);
    }

  }
}
TOP

Related Classes of org.pentaho.reporting.engine.classic.core.modules.output.table.html.HtmlTextExtractor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.