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.output.spreadsheet;
24 
25 import com.sun.star.report.DataSourceFactory;
26 import com.sun.star.report.ImageService;
27 import com.sun.star.report.InputRepository;
28 import com.sun.star.report.OfficeToken;
29 import com.sun.star.report.OutputRepository;
30 import com.sun.star.report.pentaho.OfficeNamespaces;
31 import com.sun.star.report.pentaho.PentahoReportEngineMetaData;
32 import com.sun.star.report.pentaho.model.OfficeMasterPage;
33 import com.sun.star.report.pentaho.model.OfficeMasterStyles;
34 import com.sun.star.report.pentaho.model.OfficeStyle;
35 import com.sun.star.report.pentaho.model.OfficeStyles;
36 import com.sun.star.report.pentaho.model.OfficeStylesCollection;
37 import com.sun.star.report.pentaho.model.PageSection;
38 import com.sun.star.report.pentaho.output.OfficeDocumentReportTarget;
39 import com.sun.star.report.pentaho.output.StyleUtilities;
40 import com.sun.star.report.pentaho.output.text.MasterPageFactory;
41 import com.sun.star.report.pentaho.styles.LengthCalculator;
42 
43 import java.io.IOException;
44 
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.HashSet;
48 import java.util.List;
49 import java.util.Set;
50 
51 import org.jfree.layouting.input.style.values.CSSNumericType;
52 import org.jfree.layouting.input.style.values.CSSNumericValue;
53 import org.jfree.layouting.util.AttributeMap;
54 import org.jfree.report.DataFlags;
55 import org.jfree.report.DataSourceException;
56 import org.jfree.report.JFreeReportInfo;
57 import org.jfree.report.ReportProcessingException;
58 import org.jfree.report.flow.ReportJob;
59 import org.jfree.report.flow.ReportStructureRoot;
60 import org.jfree.report.flow.ReportTargetUtil;
61 import org.jfree.report.structure.Element;
62 import org.jfree.report.structure.Section;
63 import org.jfree.report.util.IntegerCache;
64 
65 import org.pentaho.reporting.libraries.resourceloader.ResourceKey;
66 import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
67 import org.pentaho.reporting.libraries.xmlns.common.AttributeList;
68 import org.pentaho.reporting.libraries.xmlns.writer.XmlWriter;
69 import org.pentaho.reporting.libraries.xmlns.writer.XmlWriterSupport;
70 
71 
72 /**
73  * Creation-Date: 03.11.2007
74  *
75  * @author Michael D'Amour
76  */
77 public class SpreadsheetRawReportTarget extends OfficeDocumentReportTarget
78 {
79 
80     private static final String[] FOPROPS = new String[]
81     {
82         "letter-spacing", "font-variant", "text-transform"
83     };
84     private static final String NUMBERCOLUMNSSPANNED = "number-columns-spanned";
85     private static final String[] STYLEPROPS = new String[]
86     {
87         "text-combine", "font-pitch-complex", "text-rotation-angle", "font-name", "text-blinking", "letter-kerning", "text-combine-start-char", "text-combine-end-char", "text-position", "text-scale"
88     };
89     private static final int CELL_WIDTH_FACTOR = 10000;
90     private static final String TRANSPARENT = "transparent";
91     private boolean paragraphFound = false;
92     private boolean paragraphHandled = false;
93 
94     /**
95      * This class represents a column boundary, not in width, but it's actual boundary location. One of the motivations
96      * for creating this class was to be able to record the boundaries for each incoming table while consuming as few
97      * objects/memory as possible.
98      */
99     private static class ColumnBoundary implements Comparable
100     {
101 
102         private final Set tableIndices;
103         private final long boundary;
104 
ColumnBoundary(final long boundary)105         private ColumnBoundary(final long boundary)
106         {
107             this.tableIndices = new HashSet();
108             this.boundary = boundary;
109         }
110 
addTableIndex(final int table)111         public void addTableIndex(final int table)
112         {
113             tableIndices.add(IntegerCache.getInteger(table));
114         }
115 
getBoundary()116         public float getBoundary()
117         {
118             return boundary;
119         }
120 
isContainedByTable(final int table)121         public boolean isContainedByTable(final int table)
122         {
123             final Integer index = IntegerCache.getInteger(table);
124             return tableIndices.contains(index);
125         }
126 
compareTo(final Object arg0)127         public int compareTo(final Object arg0)
128         {
129             if (arg0.equals(this))
130             {
131                 return 0;
132             }
133             if (arg0 instanceof ColumnBoundary)
134             {
135                 if (boundary > ((ColumnBoundary) arg0).boundary)
136                 {
137                     return 1;
138                 }
139                 else
140                 {
141                     return -1;
142                 }
143             }
144             return 1;
145         }
146 
equals(final Object obj)147         public boolean equals(final Object obj)
148         {
149             return obj instanceof ColumnBoundary && ((ColumnBoundary) obj).boundary == boundary;
150         }
151 
hashCode()152         public int hashCode()
153         {
154             assert false : "hashCode not designed";
155             return 42; // any arbitrary constant will do
156         }
157     }
158     private String tableBackgroundColor; // null means transparent ...
159     private static final ColumnBoundary[] EMPTY_COLBOUNDS = new ColumnBoundary[0];
160     private boolean elementBoundaryCollectionPass;
161     private boolean oleHandled;
162     private final List columnBoundaryList;
163     private long currentRowBoundaryMarker;
164     private ColumnBoundary[] sortedBoundaryArray;
165     private ColumnBoundary[] boundariesForTableArray;
166     private int tableCounter;
167     private int columnCounter;
168     private int columnSpanCounter;
169     private int currentSpan = 0;
170     private String unitsOfMeasure;
171     final private List shapes;
172     final private List ole;
173     final private List rowHeights;
174 
SpreadsheetRawReportTarget(final ReportJob reportJob, final ResourceManager resourceManager, final ResourceKey baseResource, final InputRepository inputRepository, final OutputRepository outputRepository, final String target, final ImageService imageService, final DataSourceFactory dataSourceFactory)175     public SpreadsheetRawReportTarget(final ReportJob reportJob,
176             final ResourceManager resourceManager,
177             final ResourceKey baseResource,
178             final InputRepository inputRepository,
179             final OutputRepository outputRepository,
180             final String target,
181             final ImageService imageService,
182             final DataSourceFactory dataSourceFactory)
183             throws ReportProcessingException
184     {
185         super(reportJob, resourceManager, baseResource, inputRepository, outputRepository, target, imageService, dataSourceFactory);
186         columnBoundaryList = new ArrayList();
187         elementBoundaryCollectionPass = true;
188         rowHeights = new ArrayList();
189         shapes = new ArrayList();
190         ole = new ArrayList();
191         oleHandled = false;
192     }
193 
startOther(final AttributeMap attrs)194     public void startOther(final AttributeMap attrs) throws DataSourceException, ReportProcessingException
195     {
196         if (ReportTargetUtil.isElementOfType(JFreeReportInfo.REPORT_NAMESPACE, OfficeToken.OBJECT_OLE, attrs))
197         {
198             if (isElementBoundaryCollectionPass() && getCurrentRole() != ROLE_TEMPLATE)
199             {
200                 ole.add(attrs);
201             }
202             oleHandled = true;
203             return;
204         }
205         final String namespace = ReportTargetUtil.getNamespaceFromAttribute(attrs);
206         if (isRepeatingSection() || isFilteredNamespace(namespace))
207         {
208             return;
209         }
210 
211         final String elementType = ReportTargetUtil.getElemenTypeFromAttribute(attrs);
212         if (OfficeNamespaces.TEXT_NS.equals(namespace) && OfficeToken.P.equals(elementType) && !paragraphHandled)
213         {
214             paragraphFound = true;
215             return;
216         }
217 
218         if (OfficeNamespaces.DRAWING_NS.equals(namespace) && OfficeToken.FRAME.equals(elementType))
219         {
220             if (isElementBoundaryCollectionPass() && getCurrentRole() != ROLE_TEMPLATE)
221             {
222                 final LengthCalculator len = new LengthCalculator();
223                 for (int i = 0; i < rowHeights.size(); i++)
224                 {
225                     len.add((CSSNumericValue) rowHeights.get(i));
226                     // val += ((CSSNumericValue)rowHeights.get(i)).getValue();
227                 }
228 
229                 rowHeights.clear();
230                 final CSSNumericValue currentRowHeight = len.getResult();
231                 rowHeights.add(currentRowHeight);
232                 attrs.setAttribute(OfficeNamespaces.DRAWING_NS, "z-index", String.valueOf(shapes.size()));
233                 final String y = (String) attrs.getAttribute(OfficeNamespaces.SVG_NS, "y");
234                 if (y != null)
235                 {
236                     len.add(parseLength(y));
237                     final CSSNumericValue currentY = len.getResult();
238                     attrs.setAttribute(OfficeNamespaces.SVG_NS, "y", currentY.getValue() + currentY.getType().getType());
239                 }
240                 shapes.add(attrs);
241             }
242             return;
243         }
244         if (oleHandled)
245         {
246             if (isElementBoundaryCollectionPass() && getCurrentRole() != ROLE_TEMPLATE)
247             {
248                 ole.add(attrs);
249             }
250             return;
251         }
252 
253         // if this is the report namespace, write out a table definition ..
254         if (OfficeNamespaces.TABLE_NS.equals(namespace) && OfficeToken.TABLE.equals(elementType))
255         {
256             // whenever we see a new table, we increment our tableCounter
257             // this is used to keep tracked of the boundary conditions per table
258             tableCounter++;
259         }
260 
261         if (isElementBoundaryCollectionPass())
262         {
263             collectBoundaryForElement(attrs);
264         }
265         else
266         // if (!isElementBoundaryCollectionPass())
267         {
268             try
269             {
270                 processElement(attrs, namespace, elementType);
271             }
272             catch (IOException e)
273             {
274                 throw new ReportProcessingException(OfficeDocumentReportTarget.FAILED, e);
275             }
276         }
277     }
278 
startReportSection(final AttributeMap attrs, final int role)279     protected void startReportSection(final AttributeMap attrs, final int role) throws IOException, DataSourceException, ReportProcessingException
280     {
281         if ((role == OfficeDocumentReportTarget.ROLE_SPREADSHEET_PAGE_HEADER || role == OfficeDocumentReportTarget.ROLE_SPREADSHEET_PAGE_FOOTER) && (!PageSection.isPrintWithReportHeader(attrs) || !PageSection.isPrintWithReportFooter(attrs)))
282         {
283             startBuffering(new OfficeStylesCollection(), true);
284         }
285         else
286         {
287             super.startReportSection(attrs, role);
288         }
289     }
290 
endReportSection(final AttributeMap attrs, final int role)291     protected void endReportSection(final AttributeMap attrs, final int role) throws IOException, DataSourceException, ReportProcessingException
292     {
293         if ((role == OfficeDocumentReportTarget.ROLE_SPREADSHEET_PAGE_HEADER || role == OfficeDocumentReportTarget.ROLE_SPREADSHEET_PAGE_FOOTER) && (!PageSection.isPrintWithReportHeader(attrs) || !PageSection.isPrintWithReportFooter(attrs)))
294         {
295             finishBuffering();
296         }
297         else
298         {
299             super.endReportSection(attrs, role);
300         }
301     }
302 
handleParagraph()303     private void handleParagraph()
304     {
305         if (paragraphFound)
306         {
307             try
308             {
309                 final XmlWriter xmlWriter = getXmlWriter();
310                 xmlWriter.writeTag(OfficeNamespaces.TEXT_NS, OfficeToken.P, null, XmlWriterSupport.OPEN);
311                 paragraphHandled = true;
312                 paragraphFound = false;
313             }
314             catch (IOException ex)
315             {
316                 LOGGER.error("ReportProcessing failed", ex);
317             }
318         }
319     }
320 
processElement(final AttributeMap attrs, final String namespace, final String elementType)321     private void processElement(final AttributeMap attrs, final String namespace, final String elementType)
322             throws IOException, ReportProcessingException
323     {
324         final XmlWriter xmlWriter = getXmlWriter();
325 
326         if (ReportTargetUtil.isElementOfType(OfficeNamespaces.TABLE_NS, OfficeToken.TABLE, attrs))
327         {
328             // a new table means we must clear our "calculated" table boundary array cache
329             boundariesForTableArray = null;
330 
331             final String tableStyle = (String) attrs.getAttribute(OfficeNamespaces.TABLE_NS, OfficeToken.STYLE_NAME);
332             if (tableStyle == null)
333             {
334                 tableBackgroundColor = null;
335             }
336             else
337             {
338                 final Object raw = StyleUtilities.queryStyle(getPredefinedStylesCollection(), OfficeToken.TABLE, tableStyle,
339                         "table-properties", OfficeNamespaces.FO_NS, OfficeToken.BACKGROUND_COLOR);
340                 if (raw == null || TRANSPARENT.equals(raw))
341                 {
342                     tableBackgroundColor = null;
343                 }
344                 else
345                 {
346                     tableBackgroundColor = String.valueOf(raw);
347                 }
348             }
349             return;
350         }
351 
352         if (ReportTargetUtil.isElementOfType(OfficeNamespaces.TABLE_NS, OfficeToken.TABLE_COLUMN, attrs) || ReportTargetUtil.isElementOfType(OfficeNamespaces.TABLE_NS, OfficeToken.TABLE_COLUMNS, attrs))
353         {
354             return;
355         }
356 
357         // covered-table-cell elements may appear in the input from row or column spans. In the event that we hit a
358         // column-span we simply ignore these elements because we are going to adjust the span to fit the uniform table.
359         if (ReportTargetUtil.isElementOfType(OfficeNamespaces.TABLE_NS, OfficeToken.COVERED_TABLE_CELL, attrs))
360         {
361             if (columnSpanCounter > 0)
362             {
363                 columnSpanCounter--;
364             }
365 
366             if (columnSpanCounter == 0)
367             {
368                 // if we weren't expecting a covered-table-cell, let's use it, it's probably from a row-span
369                 columnCounter++;
370                 final int span = getColumnSpanForCell(tableCounter, columnCounter, 1);
371                 // use the calculated span for the column in the uniform table to create any additional covered-table-cell
372                 // elements
373                 for (int i = 0; i < span; i++)
374                 {
375                     xmlWriter.writeTag(namespace, OfficeToken.COVERED_TABLE_CELL, null, XmlWriter.CLOSE);
376                 }
377             }
378             return;
379         }
380 
381         if (ReportTargetUtil.isElementOfType(OfficeNamespaces.TABLE_NS, OfficeToken.TABLE_ROW, attrs))
382         {
383             // a new row means our column counter gets reset
384             columnCounter = 0;
385             // Lets make sure the color of the table is ok ..
386             if (tableBackgroundColor != null)
387             {
388                 final String styleName = (String) attrs.getAttribute(OfficeNamespaces.TABLE_NS, OfficeToken.STYLE_NAME);
389                 final OfficeStyle style = deriveStyle(OfficeToken.TABLE_ROW, styleName);
390                 Element tableRowProperties = style.getTableRowProperties();
391                 if (tableRowProperties == null)
392                 {
393                     tableRowProperties = new Section();
394                     tableRowProperties.setNamespace(OfficeNamespaces.STYLE_NS);
395                     tableRowProperties.setType("table-row-properties");
396                     tableRowProperties.setAttribute(OfficeNamespaces.FO_NS, OfficeToken.BACKGROUND_COLOR, tableBackgroundColor);
397                     style.addNode(tableRowProperties);
398                 }
399                 else
400                 {
401                     final Object oldValue = tableRowProperties.getAttribute(OfficeNamespaces.FO_NS, OfficeToken.BACKGROUND_COLOR);
402                     if (oldValue == null || TRANSPARENT.equals(oldValue))
403                     {
404                         tableRowProperties.setAttribute(OfficeNamespaces.FO_NS, OfficeToken.BACKGROUND_COLOR, tableBackgroundColor);
405                     }
406                 }
407                 attrs.setAttribute(OfficeNamespaces.TABLE_NS, OfficeToken.STYLE_NAME, style.getStyleName());
408             }
409         }
410         else if (ReportTargetUtil.isElementOfType(OfficeNamespaces.TABLE_NS, OfficeToken.TABLE_CELL, attrs))
411         {
412             columnCounter++;
413             final String styleName = (String) attrs.getAttribute(OfficeNamespaces.TABLE_NS, OfficeToken.STYLE_NAME);
414             if (styleName != null)
415             {
416                 final OfficeStyle cellStyle = getPredefinedStylesCollection().getStyle(OfficeToken.TABLE_CELL, styleName);
417                 if (cellStyle != null)
418                 {
419                     final Section textProperties = (Section) cellStyle.getTextProperties();
420                     if (textProperties != null)
421                     {
422                         for (String i : FOPROPS)
423                         {
424                             textProperties.setAttribute(OfficeNamespaces.FO_NS, i, null);
425                         }
426                         textProperties.setAttribute(OfficeNamespaces.TEXT_NS, "display", null);
427                         for (String i : STYLEPROPS)
428                         {
429                             textProperties.setAttribute(OfficeNamespaces.STYLE_NS, i, null);
430                         }
431                     }
432                     final Section props = (Section) cellStyle.getTableCellProperties();
433                     if (props != null)
434                     {
435                         final Object raw = props.getAttribute(OfficeNamespaces.FO_NS, OfficeToken.BACKGROUND_COLOR);
436                         if (TRANSPARENT.equals(raw))
437                         {
438                             props.setAttribute(OfficeNamespaces.FO_NS, OfficeToken.BACKGROUND_COLOR, null);
439                             // cellStyle.removeNode(props);
440                         }
441                     }
442                 }
443                 attrs.setAttribute(OfficeNamespaces.TABLE_NS, OfficeToken.STYLE_NAME, styleName);
444             }
445 
446             final String numColSpanStr = (String) attrs.getAttribute(namespace, NUMBERCOLUMNSSPANNED);
447             int initialColumnSpan = columnSpanCounter = 1;
448             if (numColSpanStr != null)
449             {
450                 initialColumnSpan = Integer.parseInt(numColSpanStr);
451                 columnSpanCounter = initialColumnSpan;
452             }
453             final int span = getColumnSpanForCell(tableCounter, columnCounter, initialColumnSpan);
454             if (initialColumnSpan > 1)
455             {
456                 // add the initial column span to our column counter index (subtract 1, since it is counted by default)
457                 columnCounter += initialColumnSpan - 1;
458             }
459 
460             // if (span < initialColumnSpan)
461             // {
462             // // ColumnBoundary cbs[] = getBoundariesForTable(tableCounter);
463             // // for (int i = 0; i < cbs.length; i++)
464             // // {
465             // // System.out.print(cbs[i].getBoundary() + " ");
466             // // }
467             // // System.out.println();
468             //
469             // LOGGER.error("A cell cannot span less than the declared columns: Declared=" + initialColumnSpan + " Computed="
470             // + span);
471             // }
472 
473             // there's no point to create number-columns-spanned attributes if we only span 1 column
474             if (span > 1)
475             {
476                 attrs.setAttribute(namespace, NUMBERCOLUMNSSPANNED, "" + span);
477                 currentSpan = span;
478             }
479             // we must also generate "covered-table-cell" elements for each column spanned
480             // but we'll do this in the endElement, after we close this OfficeToken.TABLE_CELL
481         }
482 
483         // All styles have to be processed or you will loose the paragraph-styles and inline text-styles.
484         // ..
485         performStyleProcessing(attrs);
486 
487         final AttributeList attrList = buildAttributeList(attrs);
488         xmlWriter.writeTag(namespace, elementType, attrList, XmlWriter.OPEN);
489         // System.out.println("elementType = " + elementType);
490     }
491 
collectBoundaryForElement(final AttributeMap attrs)492     private void collectBoundaryForElement(final AttributeMap attrs)
493     {
494         if (ReportTargetUtil.isElementOfType(OfficeNamespaces.TABLE_NS, OfficeToken.TABLE_COLUMNS, attrs))
495         {
496             // A table row resets the column counter.
497             resetCurrentRowBoundaryMarker();
498         }
499         else if (ReportTargetUtil.isElementOfType(OfficeNamespaces.TABLE_NS, OfficeToken.TABLE_COLUMN, attrs))
500         {
501             final String styleName = (String) attrs.getAttribute(OfficeNamespaces.TABLE_NS, OfficeToken.STYLE_NAME);
502             if (styleName == null)
503             {
504                 // This should not happen, but if it does, we will ignore that cell.
505                 return;
506             }
507 
508             final OfficeStyle style = getPredefinedStylesCollection().getStyle(OfficeToken.TABLE_COLUMN, styleName);
509             if (style == null)
510             {
511                 // Now this is very bad. It means that there is no style defined with the given name.
512                 return;
513             }
514 
515             final Element tableColumnProperties = style.getTableColumnProperties();
516             String widthStr = (String) tableColumnProperties.getAttribute("column-width");
517             widthStr = widthStr.substring(0, widthStr.indexOf(getUnitsOfMeasure(widthStr)));
518             final float val = Float.parseFloat(widthStr) * CELL_WIDTH_FACTOR;
519             addColumnWidthToRowBoundaryMarker((long) val);
520             ColumnBoundary currentRowBoundary = new ColumnBoundary(getCurrentRowBoundaryMarker());
521             final List columnBoundaryList_ = getColumnBoundaryList();
522             final int idx = columnBoundaryList_.indexOf(currentRowBoundary);
523             if (idx == -1)
524             {
525                 columnBoundaryList_.add(currentRowBoundary);
526             }
527             else
528             {
529                 currentRowBoundary = (ColumnBoundary) columnBoundaryList_.get(idx);
530             }
531             currentRowBoundary.addTableIndex(tableCounter);
532         }
533     }
534 
getUnitsOfMeasure(final String str)535     private String getUnitsOfMeasure(final String str)
536     {
537         if (unitsOfMeasure == null || "".equals(unitsOfMeasure))
538         {
539             if (str == null || "".equals(str))
540             {
541                 unitsOfMeasure = "cm";
542                 return unitsOfMeasure;
543             }
544 
545             // build units of measure, set it
546             int i = str.length() - 1;
547             for (; i >= 0; i--)
548             {
549                 final char c = str.charAt(i);
550                 if (Character.isDigit(c) || c == '.' || c == ',')
551                 {
552                     break;
553                 }
554             }
555             unitsOfMeasure = str.substring(i + 1);
556         }
557         return unitsOfMeasure;
558     }
559 
createTableShapes()560     private void createTableShapes() throws ReportProcessingException
561     {
562         if (!shapes.isEmpty())
563         {
564             try
565             {
566                 final XmlWriter xmlWriter = getXmlWriter();
567                 // at this point we need to generate the table-columns section based on our boundary table
568                 // <table:shapes>
569                 // <draw:frame />
570                 // ..
571                 // </table:shapes>
572                 xmlWriter.writeTag(OfficeNamespaces.TABLE_NS, OfficeToken.SHAPES, null, XmlWriterSupport.OPEN);
573 
574 
575                 for (int i = 0; i < shapes.size(); i++)
576                 {
577                     final AttributeMap attrs = (AttributeMap) shapes.get(i);
578                     final AttributeList attrList = buildAttributeList(attrs);
579                     attrList.removeAttribute(OfficeNamespaces.DRAWING_NS, OfficeToken.STYLE_NAME);
580                     xmlWriter.writeTag(OfficeNamespaces.DRAWING_NS, OfficeToken.FRAME, attrList, XmlWriterSupport.OPEN);
581                     startChartProcessing((AttributeMap) ole.get(i));
582 
583                     xmlWriter.writeCloseTag();
584                 }
585                 xmlWriter.writeCloseTag();
586             }
587             catch (IOException e)
588             {
589                 throw new ReportProcessingException(OfficeDocumentReportTarget.FAILED, e);
590             }
591         }
592     }
593 
createTableColumns()594     private void createTableColumns() throws ReportProcessingException
595     {
596         try
597         {
598             final XmlWriter xmlWriter = getXmlWriter();
599             // at this point we need to generate the table-columns section based on our boundary table
600             // <table-columns>
601             // <table-column style-name="coX"/>
602             // ..
603             // </table-columns>
604             // the first boundary is '0' which is a placeholder so we will ignore it
605             xmlWriter.writeTag(OfficeNamespaces.TABLE_NS, OfficeToken.TABLE_COLUMNS, null, XmlWriterSupport.OPEN);
606 
607             // blow away current column styles
608             // start processing at i=1 because we added a boundary for "0" which is virtual
609             final ColumnBoundary[] cba = getSortedColumnBoundaryArray();
610             for (int i = 1; i < cba.length; i++)
611             {
612                 final ColumnBoundary cb = cba[i];
613                 float columnWidth = cb.getBoundary();
614                 if (i > 1)
615                 {
616                     columnWidth -= cba[i - 1].getBoundary();
617                 }
618                 columnWidth = columnWidth / CELL_WIDTH_FACTOR;
619                 final OfficeStyle style = deriveStyle(OfficeToken.TABLE_COLUMN, ("co" + i + "_"));
620                 final Section tableColumnProperties = new Section();
621                 tableColumnProperties.setType("table-column-properties");
622                 tableColumnProperties.setNamespace(style.getNamespace());
623                 final String width = String.format("%f", columnWidth);
624                 tableColumnProperties.setAttribute(style.getNamespace(),
625                         "column-width", width + getUnitsOfMeasure(null));
626                 style.addNode(tableColumnProperties);
627 
628                 final AttributeList myAttrList = new AttributeList();
629                 myAttrList.setAttribute(OfficeNamespaces.TABLE_NS, OfficeToken.STYLE_NAME, style.getStyleName());
630                 xmlWriter.writeTag(OfficeNamespaces.TABLE_NS, OfficeToken.TABLE_COLUMN, myAttrList, XmlWriterSupport.CLOSE);
631             }
632             xmlWriter.writeCloseTag();
633         }
634         catch (IOException e)
635         {
636             throw new ReportProcessingException(OfficeDocumentReportTarget.FAILED, e);
637         }
638     }
639 
endOther(final AttributeMap attrs)640     protected void endOther(final AttributeMap attrs) throws DataSourceException, ReportProcessingException
641     {
642         if (ReportTargetUtil.isElementOfType(JFreeReportInfo.REPORT_NAMESPACE, OfficeToken.OBJECT_OLE, attrs) || oleHandled)
643         {
644             oleHandled = false;
645             return;
646         }
647 
648         if (ReportTargetUtil.isElementOfType(OfficeNamespaces.TABLE_NS, OfficeToken.TABLE_ROW, attrs) && isElementBoundaryCollectionPass() && getCurrentRole() != ROLE_TEMPLATE)
649         {
650             final String styleName = (String) attrs.getAttribute(OfficeNamespaces.TABLE_NS, OfficeToken.STYLE_NAME);
651             rowHeights.add(computeRowHeight(styleName));
652         }
653 
654         if (isRepeatingSection() || isElementBoundaryCollectionPass())
655         {
656             return;
657         }
658 
659         final String namespace = ReportTargetUtil.getNamespaceFromAttribute(attrs);
660         if (isFilteredNamespace(namespace))
661         {
662             return;
663         }
664         final String elementType = ReportTargetUtil.getElemenTypeFromAttribute(attrs);
665         if (OfficeNamespaces.DRAWING_NS.equals(namespace) && OfficeToken.FRAME.equals(elementType))
666         {
667             return;
668         }
669 
670         // if this is the report namespace, write out a table definition ..
671         if (OfficeNamespaces.TABLE_NS.equals(namespace) && (OfficeToken.TABLE.equals(elementType) || OfficeToken.COVERED_TABLE_CELL.equals(elementType) || OfficeToken.TABLE_COLUMN.equals(elementType) || OfficeToken.TABLE_COLUMNS.equals(elementType)))
672         {
673             return;
674         }
675 
676         if (!paragraphHandled && OfficeNamespaces.TEXT_NS.equals(namespace) && OfficeToken.P.equals(elementType))
677         {
678             if (!paragraphHandled)
679             {
680                 return;
681             }
682 
683             paragraphHandled = false;
684         }
685         try
686         {
687             final XmlWriter xmlWriter = getXmlWriter();
688             xmlWriter.writeCloseTag();
689             // table-cell elements may have a number-columns-spanned attribute which indicates how many
690             // 'covered-table-cell' elements we need to generate
691             generateCoveredTableCells(attrs);
692         }
693         catch (IOException e)
694         {
695             throw new ReportProcessingException(OfficeDocumentReportTarget.FAILED, e);
696         }
697     }
698 
generateCoveredTableCells(final AttributeMap attrs)699     private void generateCoveredTableCells(final AttributeMap attrs) throws IOException
700     {
701         if (!ReportTargetUtil.isElementOfType(OfficeNamespaces.TABLE_NS, OfficeToken.TABLE_CELL, attrs))
702         {
703             return;
704         }
705 
706         // do this after we close the tag
707         final XmlWriter xmlWriter = getXmlWriter();
708         // final Object attribute = attrs.getAttribute(OfficeNamespaces.TABLE_NS,NUMBERCOLUMNSSPANNED);
709         // final int span = TextUtilities.parseInt((String) attribute, 0);
710         final int span = currentSpan;
711         currentSpan = 0;
712         for (int i = 1; i < span; i++)
713         {
714             xmlWriter.writeTag(OfficeNamespaces.TABLE_NS, OfficeToken.COVERED_TABLE_CELL, null, XmlWriter.CLOSE);
715         }
716     }
717 
getExportDescriptor()718     public String getExportDescriptor()
719     {
720         return "raw/" + PentahoReportEngineMetaData.OPENDOCUMENT_SPREADSHEET;
721     }
722 
723     // /////////////////////////////////////////////////////////////////////////
processText(final String text)724     public void processText(final String text) throws DataSourceException, ReportProcessingException
725     {
726         if (!(isRepeatingSection() || isElementBoundaryCollectionPass()))
727         {
728             handleParagraph();
729             super.processText(text);
730         }
731     }
732 
processContent(final DataFlags value)733     public void processContent(final DataFlags value) throws DataSourceException, ReportProcessingException
734     {
735         if (!(isRepeatingSection() || isElementBoundaryCollectionPass()))
736         {
737             handleParagraph();
738             super.processContent(value);
739         }
740     }
741 
getStartContent()742     protected String getStartContent()
743     {
744         return "spreadsheet";
745     }
746 
startContent(final AttributeMap attrs)747     protected void startContent(final AttributeMap attrs) throws IOException, DataSourceException,
748             ReportProcessingException
749     {
750         if (!isElementBoundaryCollectionPass())
751         {
752             final XmlWriter xmlWriter = getXmlWriter();
753             xmlWriter.writeTag(OfficeNamespaces.OFFICE_NS, getStartContent(), null, XmlWriterSupport.OPEN);
754 
755             writeNullDate();
756 
757             final AttributeMap tableAttributes = new AttributeMap();
758             tableAttributes.setAttribute(JFreeReportInfo.REPORT_NAMESPACE, Element.NAMESPACE_ATTRIBUTE, OfficeNamespaces.TABLE_NS);
759             tableAttributes.setAttribute(JFreeReportInfo.REPORT_NAMESPACE, Element.TYPE_ATTRIBUTE, OfficeToken.TABLE);
760             tableAttributes.setAttribute(OfficeNamespaces.TABLE_NS, OfficeToken.STYLE_NAME, generateInitialTableStyle());
761             tableAttributes.setAttribute(OfficeNamespaces.TABLE_NS, "name", "Report");
762 
763             performStyleProcessing(tableAttributes);
764 
765             xmlWriter.writeTag(OfficeNamespaces.TABLE_NS, OfficeToken.TABLE, buildAttributeList(tableAttributes), XmlWriterSupport.OPEN);
766             createTableShapes();
767             createTableColumns();
768         }
769     }
770 
generateInitialTableStyle()771     private String generateInitialTableStyle() throws ReportProcessingException
772     {
773         final OfficeStylesCollection predefStyles = getPredefinedStylesCollection();
774         final OfficeStyles commonStyles = predefStyles.getAutomaticStyles();
775         if (!commonStyles.containsStyle(OfficeToken.TABLE, "Initial_Table"))
776         {
777             final String masterPageName = createMasterPage();
778 
779             final OfficeStyle tableStyle = new OfficeStyle();
780             tableStyle.setStyleFamily(OfficeToken.TABLE);
781             tableStyle.setStyleName("Initial_Table");
782             tableStyle.setAttribute(OfficeNamespaces.STYLE_NS, "master-page-name", masterPageName);
783             final Element tableProperties = produceFirstChild(tableStyle, OfficeNamespaces.STYLE_NS, "table-properties");
784             tableProperties.setAttribute(OfficeNamespaces.FO_NS, OfficeToken.BACKGROUND_COLOR, TRANSPARENT);
785             commonStyles.addStyle(tableStyle);
786         }
787         return "Initial_Table";
788     }
789 
createMasterPage()790     private String createMasterPage() throws ReportProcessingException
791     {
792         final OfficeStylesCollection predefStyles = getPredefinedStylesCollection();
793         final MasterPageFactory masterPageFactory = new MasterPageFactory(predefStyles.getMasterStyles());
794         final OfficeMasterPage masterPage;
795         if (!masterPageFactory.containsMasterPage("Standard", null, null))
796         {
797             masterPage = masterPageFactory.createMasterPage("Standard", null, null);
798 
799             final CSSNumericValue zeroLength = CSSNumericValue.createValue(CSSNumericType.CM, 0);
800             final String pageLayoutTemplate = masterPage.getPageLayout();
801             if (pageLayoutTemplate == null)
802             {
803                 // there is no pagelayout. Create one ..
804                 final String derivedLayout = masterPageFactory.createPageStyle(getGlobalStylesCollection().getAutomaticStyles(), zeroLength, zeroLength);
805                 masterPage.setPageLayout(derivedLayout);
806             }
807             else
808             {
809                 final String derivedLayout = masterPageFactory.derivePageStyle(pageLayoutTemplate,
810                         getPredefinedStylesCollection().getAutomaticStyles(),
811                         getGlobalStylesCollection().getAutomaticStyles(), zeroLength, zeroLength);
812                 masterPage.setPageLayout(derivedLayout);
813             }
814 
815             final OfficeStylesCollection officeStylesCollection = getGlobalStylesCollection();
816             final OfficeMasterStyles officeMasterStyles = officeStylesCollection.getMasterStyles();
817             officeMasterStyles.addMasterPage(masterPage);
818         }
819         else
820         {
821             masterPage = masterPageFactory.getMasterPage("Standard", null, null);
822         }
823         return masterPage.getStyleName();
824     }
825 
endContent(final AttributeMap attrs)826     protected void endContent(final AttributeMap attrs) throws IOException, DataSourceException,
827             ReportProcessingException
828     {
829         // todo
830         if (!isElementBoundaryCollectionPass())
831         {
832             final XmlWriter xmlWriter = getXmlWriter();
833             xmlWriter.writeCloseTag();
834             xmlWriter.writeCloseTag();
835         }
836     }
837 
endReport(final ReportStructureRoot report)838     public void endReport(final ReportStructureRoot report) throws DataSourceException, ReportProcessingException
839     {
840         super.endReport(report);
841         setElementBoundaryCollectionPass(false);
842         resetTableCounter();
843         columnCounter = 0;
844         copyMeta();
845     }
846 
isElementBoundaryCollectionPass()847     private boolean isElementBoundaryCollectionPass()
848     {
849         return elementBoundaryCollectionPass;
850     }
851 
setElementBoundaryCollectionPass(final boolean elementBoundaryCollectionPass)852     private void setElementBoundaryCollectionPass(final boolean elementBoundaryCollectionPass)
853     {
854         this.elementBoundaryCollectionPass = elementBoundaryCollectionPass;
855     }
856 
getSortedColumnBoundaryArray()857     private ColumnBoundary[] getSortedColumnBoundaryArray()
858     {
859         if (sortedBoundaryArray == null)
860         {
861             getColumnBoundaryList().add(new ColumnBoundary(0));
862             sortedBoundaryArray = (ColumnBoundary[]) getColumnBoundaryList().toArray(new ColumnBoundary[getColumnBoundaryList().size()]);
863             Arrays.sort(sortedBoundaryArray);
864         }
865         return sortedBoundaryArray;
866     }
867 
getColumnBoundaryList()868     private List getColumnBoundaryList()
869     {
870         return columnBoundaryList;
871     }
872 
addColumnWidthToRowBoundaryMarker(final long width)873     private void addColumnWidthToRowBoundaryMarker(final long width)
874     {
875         currentRowBoundaryMarker += width;
876     }
877 
getCurrentRowBoundaryMarker()878     private long getCurrentRowBoundaryMarker()
879     {
880         return currentRowBoundaryMarker;
881     }
882 
resetTableCounter()883     private void resetTableCounter()
884     {
885         tableCounter = 0;
886     }
887 
resetCurrentRowBoundaryMarker()888     private void resetCurrentRowBoundaryMarker()
889     {
890         currentRowBoundaryMarker = 0;
891     }
892 
getBoundariesForTable(final int table)893     private ColumnBoundary[] getBoundariesForTable(final int table)
894     {
895         if (boundariesForTableArray == null)
896         {
897             final List boundariesForTable = new ArrayList();
898             final List boundaryList = getColumnBoundaryList();
899             for (int i = 0; i < boundaryList.size(); i++)
900             {
901                 final ColumnBoundary b = (ColumnBoundary) boundaryList.get(i);
902                 if (b.isContainedByTable(table))
903                 {
904                     boundariesForTable.add(b);
905                 }
906             }
907             boundariesForTableArray = (ColumnBoundary[]) boundariesForTable.toArray(new ColumnBoundary[boundariesForTable.size()]);
908             Arrays.sort(boundariesForTableArray);
909         }
910         return boundariesForTableArray;
911     }
912 
getColumnSpanForCell(final int table, final int col, final int initialColumnSpan)913     private int getColumnSpanForCell(final int table, final int col, final int initialColumnSpan)
914     {
915         final ColumnBoundary[] globalBoundaries = getSortedColumnBoundaryArray();
916         final ColumnBoundary[] tableBoundaries = getBoundariesForTable(table);
917         // how many column boundaries in the globalBoundaries list fall between the currentRowWidth and the next boundary
918         // for the current row
919 
920         float cellBoundary = tableBoundaries[col - 1].getBoundary();
921         float cellWidth = tableBoundaries[col - 1].getBoundary();
922 
923         if (col > 1)
924         {
925             cellWidth = cellWidth - tableBoundaries[col - 2].getBoundary();
926         }
927 
928         if (initialColumnSpan > 1)
929         {
930             // ok we've got some additional spanning specified on the input
931             final int index = (col - 1) + (initialColumnSpan - 1);
932             cellWidth += tableBoundaries[index].getBoundary() - tableBoundaries[col - 1].getBoundary();
933             cellBoundary = tableBoundaries[index].getBoundary();
934         }
935 
936         int beginBoundaryIndex = 0;
937         int endBoundaryIndex = globalBoundaries.length - 1;
938         for (int i = 0; i < globalBoundaries.length; i++)
939         {
940             // find beginning boundary
941             if (globalBoundaries[i].getBoundary() <= cellBoundary - cellWidth)
942             {
943                 beginBoundaryIndex = i;
944             }
945             if (globalBoundaries[i].getBoundary() <= cellBoundary)
946             {
947                 endBoundaryIndex = i;
948             }
949         }
950         final int span = endBoundaryIndex - beginBoundaryIndex;
951         // span will be zero for the first column, so we adjust it to 1
952         if (span == 0)
953         {
954             return 1;
955         }
956         // System.out.println("table = " + table + " col = " + col + " rowBoundaries.length = " + tableBoundaries.length + "
957         // cellWidth = " + cellWidth + " span = " + span);
958         return span;
959     }
960 
getTargetMimeType()961     protected String getTargetMimeType()
962     {
963         return "application/vnd.oasis.opendocument.spreadsheet";
964     }
965 }
966