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