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