1 /* 2 * This file is part of the LibreOffice project. 3 * 4 * This Source Code Form is subject to the terms of the Mozilla Public 5 * License, v. 2.0. If a copy of the MPL was not distributed with this 6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 * 8 * This file incorporates work covered by the following license notice: 9 * 10 * Licensed to the Apache Software Foundation (ASF) under one or more 11 * contributor license agreements. See the NOTICE file distributed 12 * with this work for additional information regarding copyright 13 * ownership. The ASF licenses this file to you under the Apache 14 * License, Version 2.0 (the "License"); you may not use this file 15 * except in compliance with the License. You may obtain a copy of 16 * the License at http://www.apache.org/licenses/LICENSE-2.0 . 17 */ 18 19 package org.libreoffice.report.pentaho.layoutprocessor; 20 21 import org.libreoffice.report.OfficeToken; 22 import org.libreoffice.report.pentaho.OfficeNamespaces; 23 import org.libreoffice.report.pentaho.model.ImageElement; 24 25 import java.util.logging.Logger; 26 27 import org.jfree.layouting.util.AttributeMap; 28 import org.jfree.report.DataSourceException; 29 import org.jfree.report.JFreeReportInfo; 30 import org.jfree.report.ReportDataFactoryException; 31 import org.jfree.report.ReportProcessingException; 32 import org.jfree.report.data.GlobalMasterRow; 33 import org.jfree.report.data.ReportDataRow; 34 import org.jfree.report.expressions.FormulaExpression; 35 import org.jfree.report.flow.FlowController; 36 import org.jfree.report.flow.ReportTarget; 37 import org.jfree.report.flow.layoutprocessor.LayoutController; 38 import org.jfree.report.flow.layoutprocessor.LayoutControllerUtil; 39 import org.jfree.report.structure.Element; 40 import org.jfree.report.structure.Node; 41 import org.jfree.report.structure.Section; 42 import org.jfree.report.util.TextUtilities; 43 44 import org.pentaho.reporting.libraries.base.util.ObjectUtilities; 45 import org.pentaho.reporting.libraries.formula.Formula; 46 import org.pentaho.reporting.libraries.formula.lvalues.LValue; 47 import org.pentaho.reporting.libraries.formula.parser.ParseException; 48 49 /** 50 * Produces an image. The image-structures itself (draw:frame and so on) are not generated here. This element produces a 51 * place-holder element and relies on the output target to compute a sensible position for the element. The report 52 * definition does not give any hints about the size of the image, so we have to derive this from the surrounding 53 * context. 54 * 55 * @since 05.03.2007 56 */ 57 public class ImageElementLayoutController 58 extends AbstractReportElementLayoutController 59 { 60 61 private static final Logger LOGGER = Logger.getLogger(ImageElementLayoutController.class.getName()); 62 private ImageElementContext context; 63 64 @Override delegateContentGeneration(final ReportTarget target)65 protected LayoutController delegateContentGeneration(final ReportTarget target) 66 throws ReportProcessingException, ReportDataFactoryException, 67 DataSourceException 68 { 69 final ImageElement imageElement = (ImageElement) getNode(); 70 final FormulaExpression formulaExpression = imageElement.getFormula(); 71 if (formulaExpression == null) 72 { 73 // A static image is easy. At least at this level. Don't ask about the weird things we have to do in the 74 // output targets... 75 final String linkTarget = imageElement.getImageData(); 76 generateImage(target, linkTarget, imageElement.getScaleMode(), imageElement.isPreserveIRI()); 77 } 78 else 79 { 80 final Object value = 81 LayoutControllerUtil.evaluateExpression(getFlowController(), imageElement, formulaExpression); 82 generateImage(target, value, imageElement.getScaleMode(), imageElement.isPreserveIRI()); 83 } 84 return join(getFlowController()); 85 } 86 generateImage(final ReportTarget target, final Object linkTarget, final String scale, final boolean preserveIri)87 private void generateImage(final ReportTarget target, 88 final Object linkTarget, 89 final String scale, 90 final boolean preserveIri) 91 throws ReportProcessingException, DataSourceException 92 { 93 if (linkTarget == null) 94 { 95 return; 96 } 97 98 final AttributeMap image = new AttributeMap(); 99 image.setAttribute(JFreeReportInfo.REPORT_NAMESPACE, Element.NAMESPACE_ATTRIBUTE, JFreeReportInfo.REPORT_NAMESPACE); 100 image.setAttribute(JFreeReportInfo.REPORT_NAMESPACE, Element.TYPE_ATTRIBUTE, OfficeToken.IMAGE); 101 image.setAttribute(JFreeReportInfo.REPORT_NAMESPACE, OfficeToken.SCALE, scale); 102 image.setAttribute(JFreeReportInfo.REPORT_NAMESPACE, OfficeToken.PRESERVE_IRI, String.valueOf(preserveIri)); 103 image.setAttribute(JFreeReportInfo.REPORT_NAMESPACE, "image-context", createContext()); 104 image.setAttribute(JFreeReportInfo.REPORT_NAMESPACE, OfficeToken.IMAGE_DATA, linkTarget); 105 target.startElement(image); 106 target.endElement(image); 107 } 108 createContext()109 protected ImageElementContext createContext() 110 { 111 if (context == null) 112 { 113 114 // Step 1: Find the parent cell. 115 final LayoutController cellController = findParentCell(); 116 if (cellController == null) 117 { 118 LOGGER.warning("Image is not contained in a table. Unable to calculate the image-size."); 119 return null; 120 } 121 final Element tableCell = (Element) cellController.getNode(); 122 final int rowSpan = TextUtilities.parseInt((String) tableCell.getAttribute(OfficeNamespaces.TABLE_NS, "number-rows-spanned"), 1); 123 final int colSpan = TextUtilities.parseInt((String) tableCell.getAttribute(OfficeNamespaces.TABLE_NS, "number-columns-spanned"), 1); 124 if (rowSpan < 1 || colSpan < 1) 125 { 126 LOGGER.warning("Rowspan or colspan for image-size calculation was invalid."); 127 return null; 128 } 129 130 final LayoutController rowController = cellController.getParent(); 131 if (rowController == null) 132 { 133 LOGGER.warning("Table-Cell has no parent. Unable to calculate the image-size."); 134 return null; 135 } 136 final Section tableRow = (Section) rowController.getNode(); 137 // we are now making the assumption, that the row is a section, that contains the table-cell. 138 // This breaks the ability to return nodes or to construct reports on the fly, but the OO-report format 139 // is weird anyway and won't support such advanced techniques for the next few centuries... 140 final int columnPos = findNodeInSection(tableRow, tableCell, OfficeToken.COVERED_TABLE_CELL); 141 if (columnPos == -1) 142 { 143 LOGGER.warning("Table-Cell is not a direct child of the table-row. Unable to calculate the image-size."); 144 return null; 145 } 146 147 final LayoutController tableController = rowController.getParent(); 148 if (tableController == null) 149 { 150 LOGGER.warning("Table-Row has no Table. Unable to calculate the image-size."); 151 return null; 152 } 153 154 final Section table = (Section) tableController.getNode(); 155 // ok, we got a table, so as next we have to search for the columns now. 156 final Section columns = (Section) table.findFirstChild(OfficeNamespaces.TABLE_NS, OfficeToken.TABLE_COLUMNS); 157 if (columns.getNodeCount() <= columnPos + colSpan) 158 { 159 // the colspan is too large. The table definition is therefore invalid. We do not try to fix this. 160 LOGGER.warning( 161 "The Table's defined columns do not match the col-span or col-position. Unable to calculate the image-size."); 162 return null; 163 } 164 165 final ImageElementContext context = new ImageElementContext(colSpan, rowSpan); 166 addColumnStyles(context, columns, columnPos, colSpan); 167 // finally search the styles for the row now. 168 final int rowPos = findNodeInSection(table, tableRow, null); 169 if (rowPos == -1) 170 { 171 LOGGER.warning("Table-Cell is not a direct child of the table-row. Unable to calculate the image-size."); 172 return null; 173 } 174 175 addRowStyles(context, table, rowPos, rowSpan); 176 this.context = context; 177 } 178 return this.context; 179 } 180 findNodeInSection(final Section tableRow, final Element tableCell, final String secondType)181 private int findNodeInSection(final Section tableRow, 182 final Element tableCell, 183 final String secondType) 184 { 185 int retval = 0; 186 final Node[] nodes = tableRow.getNodeArray(); 187 final String namespace = tableCell.getNamespace(); 188 final String type = tableCell.getType(); 189 for (final Node node : nodes) 190 { 191 if (!(node instanceof Element)) 192 { 193 continue; 194 } 195 final Element child = (Element) node; 196 if (!ObjectUtilities.equal(child.getNamespace(), namespace) || (!ObjectUtilities.equal(child.getType(), type) && (secondType == null || !ObjectUtilities.equal(child.getType(), secondType)))) 197 { 198 continue; 199 } 200 201 if (node == tableCell) 202 { 203 return retval; 204 } 205 retval += 1; 206 } 207 return -1; 208 } 209 findParentCell()210 private LayoutController findParentCell() 211 { 212 LayoutController parent = getParent(); 213 while (parent != null) 214 { 215 final Object node = parent.getNode(); 216 if (node instanceof Element) 217 { 218 final Element element = (Element) node; 219 if (OfficeNamespaces.TABLE_NS.equals(element.getNamespace()) && "table-cell".equals(element.getType())) 220 { 221 return parent; 222 } 223 } 224 parent = parent.getParent(); 225 } 226 return null; 227 } 228 229 @Override isValueChanged()230 public boolean isValueChanged() 231 { 232 final ImageElement imageElement = (ImageElement) getNode(); 233 final FormulaExpression formulaExpression = imageElement.getFormula(); 234 if (formulaExpression == null) 235 { 236 final FlowController controller = getFlowController(); 237 final GlobalMasterRow masterRow = controller.getMasterRow(); 238 final ReportDataRow reportDataRow = masterRow.getReportDataRow(); 239 return reportDataRow.getCursor() == 0; 240 } 241 242 try 243 { 244 final Formula formula = formulaExpression.getCompiledFormula(); 245 final LValue lValue = formula.getRootReference(); 246 return FormatValueUtility.isReferenceChanged(this, lValue); 247 } 248 catch (ParseException e) 249 { 250 return false; 251 } 252 } 253 addColumnStyles(final ImageElementContext context, final Section columns, final int columnPos, final int colSpan)254 void addColumnStyles(final ImageElementContext context, final Section columns, final int columnPos, final int colSpan) 255 { 256 final Node[] columnDefs = columns.getNodeArray(); 257 int columnCounter = 0; 258 for (Node columnDef : columnDefs) 259 { 260 final Element column = (Element) columnDef; 261 262 if (!ObjectUtilities.equal(column.getNamespace(), OfficeNamespaces.TABLE_NS) || !ObjectUtilities.equal(column.getType(), OfficeToken.TABLE_COLUMN)) 263 { 264 continue; 265 } 266 if (columnCounter >= columnPos) 267 { 268 final String colStyle = (String) column.getAttribute(OfficeNamespaces.TABLE_NS, OfficeToken.STYLE_NAME); 269 context.setColStyle(columnCounter - columnPos, colStyle); 270 } 271 272 columnCounter += 1; 273 274 if (columnCounter >= (columnPos + colSpan)) 275 { 276 break; 277 } 278 279 } 280 } 281 addRowStyles(final ImageElementContext context, final Section table, final int rowPos, final int rowSpan)282 void addRowStyles(final ImageElementContext context, final Section table, final int rowPos, final int rowSpan) 283 { 284 final Node[] rows = table.getNodeArray(); 285 int rowCounter = 0; 286 for (Node row1 : rows) 287 { 288 final Element row = (Element) row1; 289 290 if (!ObjectUtilities.equal(row.getNamespace(), OfficeNamespaces.TABLE_NS) || !ObjectUtilities.equal(row.getType(), OfficeToken.TABLE_ROW)) 291 { 292 continue; 293 } 294 if (rowCounter >= rowPos) 295 { 296 final String rowStyle = (String) row.getAttribute(OfficeNamespaces.TABLE_NS, OfficeToken.STYLE_NAME); 297 context.setRowStyle(rowCounter - rowPos, rowStyle); 298 } 299 300 rowCounter += 1; 301 302 if (rowCounter >= (rowPos + rowSpan)) 303 { 304 break; 305 } 306 } 307 } 308 } 309