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