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 24 package org.openoffice.xmerge.converter.xml.sxc; 25 26 import org.w3c.dom.NodeList; 27 import org.w3c.dom.Node; 28 import org.w3c.dom.Element; 29 30 import java.io.IOException; 31 import java.util.Enumeration; 32 33 import org.openoffice.xmerge.Document; 34 import org.openoffice.xmerge.ConvertData; 35 import org.openoffice.xmerge.ConvertException; 36 import org.openoffice.xmerge.DocumentDeserializer; 37 import org.openoffice.xmerge.converter.xml.OfficeConstants; 38 import org.openoffice.xmerge.converter.xml.sxc.SxcDocument; 39 import org.openoffice.xmerge.converter.xml.sxc.BookSettings; 40 import org.openoffice.xmerge.converter.xml.sxc.NameDefinition; 41 import org.openoffice.xmerge.converter.xml.sxc.CellStyle; 42 import org.openoffice.xmerge.converter.xml.Style; 43 import org.openoffice.xmerge.converter.xml.StyleCatalog; 44 import org.openoffice.xmerge.util.Debug; 45 46 /** 47 * <p>General spreadsheet implementation of <code>DocumentDeserializer</code> 48 * for the {@link 49 * org.openoffice.xmerge.converter.xml.sxc.SxcPluginFactory 50 * SxcPluginFactory}. Used with SXC <code>Document</code> objects.</p> 51 * 52 * <p>The <code>deserialize</code> method uses a <code>DocDecoder</code> 53 * to read the device spreadsheet format into a <code>String</code> 54 * object, then it calls <code>buildDocument</code> to create a 55 * <code>SxcDocument</code> object from it.</p> 56 * 57 * @author Paul Rank 58 * @author Mark Murnane 59 * @author Martin Maher 60 */ 61 public abstract class SxcDocumentDeserializer implements OfficeConstants, 62 DocumentDeserializer { 63 64 /** 65 * A <code>SpreadsheetDecoder</code> object for decoding from 66 * device formats. 67 */ 68 private SpreadsheetDecoder decoder = null; 69 70 /** A w3c <code>Document</code>. */ 71 private org.w3c.dom.Document settings = null; 72 73 /** A w3c <code>Document</code>. */ 74 private org.w3c.dom.Document doc = null; 75 76 /** An <code>ConvertData</code> object assigned to this object. */ 77 private ConvertData cd = null; 78 79 /** A style catalog for the workbook */ 80 private StyleCatalog styleCat = null; 81 82 private int textStyles = 1; 83 private int colStyles = 1; 84 private int rowStyles = 1; 85 86 /** 87 * Constructor. 88 * 89 * @param cd <code>ConvertData</code> consisting of a 90 * device content object. 91 */ SxcDocumentDeserializer(ConvertData cd)92 public SxcDocumentDeserializer(ConvertData cd) { 93 this.cd = cd; 94 } 95 96 97 /** 98 * This abstract method will be implemented by concrete subclasses 99 * and will return an application-specific Decoder. 100 * 101 * @param workbook The WorkBook to read. 102 * @param password The WorkBook password. 103 * 104 * @return The appropriate <code>SpreadSheetDecoder</code>. 105 * 106 * @throws IOException If any I/O error occurs. 107 */ createDecoder(String workbook, String[] worksheetNames, String password)108 public abstract SpreadsheetDecoder createDecoder(String workbook, String[] worksheetNames, String password) 109 throws IOException; 110 111 112 /** 113 * <p>This method will return the name of the WorkBook from the 114 * <code>ConvertData</code>. Allows for situations where the 115 * WorkBook name differs from the Device Content name.</p> 116 * 117 * <p>Implemented in the Deserializer as the Decoder's constructor requires 118 * a name.</p> 119 * 120 * @param cd The <code>ConvertData</code> containing the Device 121 * content. 122 * 123 * @return The WorkBook name. 124 */ getWorkbookName(ConvertData cd)125 protected abstract String getWorkbookName(ConvertData cd) throws IOException; 126 127 128 /** 129 * This method will return the name of the WorkSheet from the 130 * <code>ConvertData</code>. 131 * 132 * @param cd The <code>ConvertData</code> containing the Device 133 * content. 134 * 135 * @return The WorkSheet names. 136 */ getWorksheetNames(ConvertData cd)137 protected abstract String[] getWorksheetNames(ConvertData cd) throws IOException; 138 139 140 /** 141 * <p>Method to convert a set of "Device" 142 * <code>Document</code> objects into a <code>SxcDocument</code> 143 * object and returns it as a <code>Document</code>.</p> 144 * 145 * <p>This method is not thread safe for performance reasons. 146 * This method should not be called from within two threads. 147 * It would be best to call this method only once per object 148 * instance.</p> 149 * 150 * @return document An <code>SxcDocument</code> consisting 151 * of the data converted from the input 152 * stream. 153 * 154 * @throws ConvertException If any conversion error occurs. 155 * @throws IOException If any I/O error occurs. 156 */ deserialize()157 public Document deserialize() throws ConvertException, 158 IOException { 159 160 // Get the name of the WorkBook from the ConvertData. 161 String[] worksheetNames = getWorksheetNames(cd); 162 String workbookName = getWorkbookName(cd); 163 164 // Create a document 165 SxcDocument sxcDoc = new SxcDocument(workbookName); 166 sxcDoc.initContentDOM(); 167 sxcDoc.initSettingsDOM(); 168 169 // Default to an initial 5 entries in the catalog. 170 styleCat = new StyleCatalog(5); 171 172 doc = sxcDoc.getContentDOM(); 173 settings = sxcDoc.getSettingsDOM(); 174 initFontTable(); 175 // Little fact for the curious reader: workbookName should 176 // be the name of the StarCalc file minus the file extension suffix. 177 178 // Create a Decoder to decode the DeviceContent to a spreadsheet document 179 // TODO - we aren't using a password in StarCalc, so we can 180 // use any value for password here. If StarCalc XML supports 181 // passwords in the future, we should try to get the correct 182 // password value here. 183 // 184 decoder = createDecoder(workbookName, worksheetNames, "password"); 185 186 Debug.log(Debug.TRACE, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); 187 Debug.log(Debug.TRACE, "<DEBUGLOG>"); 188 189 decoder.addDeviceContent(cd); 190 decode(); 191 192 Debug.log(Debug.TRACE, "</DEBUGLOG>"); 193 194 return sxcDoc; 195 } 196 197 /** 198 * This initializes a font table so we can imclude some basic font 199 * support for spreadsheets. 200 * 201 */ initFontTable()202 private void initFontTable() { 203 204 String fontTable[]= new String[] { "Tahoma", "Tahoma", "swiss", "variable", 205 "Courier New", "'Courier New'", "modern", "fixed"}; 206 // Traverse to the office:body element. 207 // There should only be one. 208 NodeList list = doc.getElementsByTagName(TAG_OFFICE_FONT_DECLS); 209 Node root = list.item(0); 210 211 for(int i=0;i<fontTable.length;) { 212 213 // Create an element node for the table 214 Element tableElement = (Element) doc.createElement(TAG_STYLE_FONT_DECL); 215 216 tableElement.setAttribute(ATTRIBUTE_STYLE_NAME, fontTable[i++]); 217 tableElement.setAttribute(ATTRIBUTE_FO_FONT_FAMILY, fontTable[i++]); 218 tableElement.setAttribute(ATTRIBUTE_FO_FONT_FAMILY_GENERIC, fontTable[i++]); 219 tableElement.setAttribute(ATTRIBUTE_STYLE_FONT_PITCH, fontTable[i++]); 220 221 root.appendChild(tableElement); 222 } 223 224 } 225 226 /** 227 * Outer level method used to decode a WorkBook 228 * into a <code>Document</code>. 229 * 230 * @throws IOException If any I/O error occurs. 231 */ decode()232 protected void decode() throws IOException { 233 234 // Get number of worksheets 235 int numSheets = decoder.getNumberOfSheets(); 236 // #i33702# - check for an Empty InputStream. 237 if(numSheets == 0) 238 { 239 System.err.println("Error decoding invalid Input stream"); 240 return; 241 } 242 243 // Traverse to the office:body element. 244 // There should only be one. 245 NodeList list = doc.getElementsByTagName(TAG_OFFICE_BODY); 246 Node node = list.item(0); 247 248 for (int i = 0; i < numSheets; i++) { 249 250 // Set the decoder to the correct worksheet 251 decoder.setWorksheet(i); 252 253 int len = list.getLength(); 254 255 if (len > 0) { 256 257 // Process the spreadsheet 258 processTable(node); 259 } 260 } 261 262 // Add the Defined Name table if there is one 263 Enumeration nameDefinitionTable = decoder.getNameDefinitions(); 264 if(nameDefinitionTable.hasMoreElements()) { 265 processNameDefinition(node, nameDefinitionTable); 266 } 267 268 // add settings 269 NodeList settingsList = settings.getElementsByTagName(TAG_OFFICE_SETTINGS); 270 Node settingsNode = settingsList.item(0);; 271 processSettings(settingsNode); 272 273 } 274 275 276 277 /** 278 * This method process the settings portion 279 * of the <code>Document</code>. 280 * 281 * @param root The root <code>Node</code> of the 282 * <code>Document</code> we are building. This 283 * <code>Node</code> should be a TAG_OFFICE_SETTINGS 284 * tag. 285 */ processSettings(Node root)286 protected void processSettings(Node root) { 287 288 Element configItemSetEntry = (Element) settings.createElement(TAG_CONFIG_ITEM_SET); 289 configItemSetEntry.setAttribute(ATTRIBUTE_CONFIG_NAME, "view-settings"); 290 Element configItemMapIndexed = (Element) settings.createElement(TAG_CONFIG_ITEM_MAP_INDEXED); 291 configItemMapIndexed.setAttribute(ATTRIBUTE_CONFIG_NAME, "Views"); 292 Element configItemMapEntry = (Element) settings.createElement(TAG_CONFIG_ITEM_MAP_ENTRY); 293 BookSettings bs = (BookSettings) decoder.getSettings(); 294 bs.writeNode(settings, configItemMapEntry); 295 296 configItemMapIndexed.appendChild(configItemMapEntry); 297 configItemSetEntry.appendChild(configItemMapIndexed); 298 root.appendChild(configItemSetEntry); 299 } 300 301 /** 302 * This method process a Name Definition Table and generates a portion 303 * of the <code>Document</code>. 304 * 305 * @param root The root <code>Node</code> of the 306 * <code>Document</code> we are building. This 307 * <code>Node</code> should be a TAG_OFFICE_BODY 308 * tag. 309 * 310 * @throws IOException If any I/O error occurs. 311 */ processNameDefinition(Node root, Enumeration eNameDefinitions)312 protected void processNameDefinition(Node root, Enumeration eNameDefinitions) throws IOException { 313 314 Debug.log(Debug.TRACE, "<NAMED-EXPRESSIONS>"); 315 316 Element namedExpressionsElement = (Element) doc.createElement(TAG_NAMED_EXPRESSIONS); 317 318 while(eNameDefinitions.hasMoreElements()) { 319 320 NameDefinition tableEntry = (NameDefinition) eNameDefinitions.nextElement(); 321 tableEntry.writeNode(doc, namedExpressionsElement); 322 } 323 324 root.appendChild(namedExpressionsElement); 325 326 Debug.log(Debug.TRACE, "</NAMED-EXPRESSIONS>"); 327 } 328 329 /** 330 * This method process a WorkSheet and generates a portion 331 * of the <code>Document</code>. A spreadsheet is represented 332 * as a table Node in StarOffice XML format. 333 * 334 * @param root The root <code>Node</code> of the 335 * <code>Document</code> we are building. This 336 * <code>Node</code> should be a TAG_OFFICE_BODY 337 * tag. 338 * 339 * @throws IOException If any I/O error occurs. 340 */ processTable(Node root)341 protected void processTable(Node root) throws IOException { 342 343 Debug.log(Debug.TRACE, "<TABLE>"); 344 345 // Create an element node for the table 346 Element tableElement = (Element) doc.createElement(TAG_TABLE); 347 348 // Get the sheet name 349 String sheetName = decoder.getSheetName(); 350 351 // Set the table name attribute 352 tableElement.setAttribute(ATTRIBUTE_TABLE_NAME, sheetName); 353 354 // TODO - style currently hardcoded - get real value 355 // Set table style-name attribute 356 tableElement.setAttribute(ATTRIBUTE_TABLE_STYLE_NAME, "Default"); 357 358 // Append the table element to the root node 359 root.appendChild(tableElement); 360 361 Debug.log(Debug.TRACE, "<SheetName>" + sheetName + "</SheetName>"); 362 363 // add the various different table-columns 364 processColumns(tableElement); 365 366 // Get each cell and add to doc 367 processCells(tableElement); 368 369 Debug.log(Debug.TRACE, "</TABLE>"); 370 } 371 372 /** 373 * <p>This method process the cells in a <code>Document</code> 374 * and generates a portion of the <code>Document</code>.</p> 375 * 376 * <p>This method assumes that records are sorted by 377 * row and then column.</p> 378 * 379 * @param root The <code>Node</code> of the <code>Document</code> 380 * we are building that we will append our cell 381 * <code>Node</code> objects. This <code>Node</code> 382 * should be a TAG_TABLE tag. 383 * 384 * @throws IOException If any I/O error occurs. 385 */ processColumns(Node root)386 protected void processColumns(Node root) throws IOException { 387 388 for(Enumeration e = decoder.getColumnRowInfos();e.hasMoreElements();) { 389 390 ColumnRowInfo ci = (ColumnRowInfo) e.nextElement(); 391 if(ci.isColumn()) { 392 ColumnStyle cStyle = new ColumnStyle("Default",SxcConstants.COLUMN_STYLE_FAMILY, 393 SxcConstants.DEFAULT_STYLE, ci.getSize(), null); 394 395 Style result[] = (Style[]) styleCat.getMatching(cStyle); 396 String styleName; 397 if(result.length==0) { 398 399 cStyle.setName("co" + colStyles++); 400 styleName = cStyle.getName(); 401 Debug.log(Debug.TRACE,"No existing style found, adding " + styleName); 402 styleCat.add(cStyle); 403 } else { 404 ColumnStyle existingStyle = (ColumnStyle) result[0]; 405 styleName = existingStyle.getName(); 406 Debug.log(Debug.TRACE,"Existing style found : " + styleName); 407 } 408 409 // Create an element node for the new row 410 Element colElement = (Element) doc.createElement(TAG_TABLE_COLUMN); 411 colElement.setAttribute(ATTRIBUTE_TABLE_STYLE_NAME, styleName); 412 if(ci.getRepeated()!=1) { 413 String repeatStr = String.valueOf(ci.getRepeated()); 414 colElement.setAttribute(ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED, repeatStr); 415 } 416 root.appendChild(colElement); 417 } 418 } 419 } 420 421 /** 422 * <p>This method process the cells in a <code>Document</code> 423 * and generates a portion of the <code>Document</code>.</p> 424 * 425 * <p>This method assumes that records are sorted by 426 * row and then column.</p> 427 * 428 * @param root The <code>Node</code> of the <code>Document</code> 429 * we are building that we will append our cell 430 * <code>Node</code> objects. This <code>Node</code> 431 * should be a TAG_TABLE tag. 432 * 433 * @throws IOException If any I/O error occurs. 434 */ processCells(Node root)435 protected void processCells(Node root) throws IOException { 436 437 // The current row element 438 Element rowElement = null; 439 440 // The current cell element 441 Element cellElement = null; 442 443 // The row number - we may not have any rows (empty sheet) 444 // so set to zero. 445 int row = 0; 446 447 // The column number - This is the expected column number of 448 // the next cell we are reading. 449 int col = 1; 450 451 // The number of columns in the spreadsheet 452 int lastColumn = decoder.getNumberOfColumns(); 453 454 // 455 Node autoStylesNode = null; 456 457 // Loop over all cells in the spreadsheet 458 while (decoder.goToNextCell()) { 459 460 // Get the row number 461 int newRow = decoder.getRowNumber(); 462 463 // Is the cell in a new row, or part of the current row? 464 if (newRow != row) { 465 466 // Make sure that all the cells in the previous row 467 // have been entered. 468 if (col <= lastColumn && rowElement != null) { 469 int numSkippedCells = lastColumn - col + 1; 470 addEmptyCells(numSkippedCells, rowElement); 471 } 472 473 // log an end row - if we already have a row 474 if (row != 0) { 475 Debug.log(Debug.TRACE, "</tr>"); 476 } 477 478 // How far is the new row from the last row? 479 int deltaRows = newRow - row; 480 481 // Check if we have skipped any rows 482 if (deltaRows > 1) { 483 // Add in empty rows 484 addEmptyRows(deltaRows-1, root, lastColumn); 485 } 486 487 // Re-initialize column (since we are in a new row) 488 col = 1; 489 490 // Create an element node for the new row 491 rowElement = (Element) doc.createElement(TAG_TABLE_ROW); 492 493 494 for(Enumeration e = decoder.getColumnRowInfos();e.hasMoreElements();) { 495 ColumnRowInfo cri = (ColumnRowInfo) e.nextElement(); 496 if(cri.isRow() && cri.getRepeated()==newRow-1) { 497 // We have the correct Row BIFFRecord for this row 498 RowStyle rStyle = new RowStyle("Default",SxcConstants.ROW_STYLE_FAMILY, 499 SxcConstants.DEFAULT_STYLE, cri.getSize(), null); 500 501 Style result[] = (Style[]) styleCat.getMatching(rStyle); 502 String styleName; 503 if(result.length==0) { 504 505 rStyle.setName("ro" + rowStyles++); 506 styleName = rStyle.getName(); 507 Debug.log(Debug.TRACE,"No existing style found, adding " + styleName); 508 styleCat.add(rStyle); 509 } else { 510 RowStyle existingStyle = (RowStyle) result[0]; 511 styleName = existingStyle.getName(); 512 Debug.log(Debug.TRACE,"Existing style found : " + styleName); 513 } 514 rowElement.setAttribute(ATTRIBUTE_TABLE_STYLE_NAME, styleName); 515 // For now we will not use the repeat column attribute 516 } 517 } 518 519 // Append the row element to the root node 520 root.appendChild(rowElement); 521 522 // Update row number 523 row = newRow; 524 525 Debug.log(Debug.TRACE, "<tr>"); 526 } 527 528 // Get the column number of the current cell 529 int newCol = decoder.getColNumber(); 530 531 // Check to see if some columns were skipped 532 if (newCol != col) { 533 534 // How many columns have we skipped? 535 int numColsSkipped = newCol - col; 536 537 addEmptyCells(numColsSkipped, rowElement); 538 539 // Update the column number to account for the 540 // skipped cells 541 col = newCol; 542 } 543 544 // Lets start dealing with the cell data 545 Debug.log(Debug.TRACE, "<td>"); 546 547 // Get the cell's contents 548 String cellContents = decoder.getCellContents(); 549 550 // Get the type of the data in the cell 551 String cellType = decoder.getCellDataType(); 552 553 // Get the cell format 554 Format fmt = decoder.getCellFormat(); 555 556 // Create an element node for the cell 557 cellElement = (Element) doc.createElement(TAG_TABLE_CELL); 558 559 Node bodyNode = doc.getElementsByTagName(TAG_OFFICE_BODY).item(0); 560 561 // Not every document has an automatic style tag 562 autoStylesNode = doc.getElementsByTagName( 563 TAG_OFFICE_AUTOMATIC_STYLES).item(0); 564 565 if (autoStylesNode == null) { 566 autoStylesNode = doc.createElement(TAG_OFFICE_AUTOMATIC_STYLES); 567 doc.insertBefore(autoStylesNode, bodyNode); 568 } 569 570 CellStyle tStyle = new 571 CellStyle( "Default",SxcConstants.TABLE_CELL_STYLE_FAMILY, 572 SxcConstants.DEFAULT_STYLE, fmt, null); 573 String styleName; 574 Style result[] = (Style[]) styleCat.getMatching(tStyle); 575 if(result.length==0) { 576 577 tStyle.setName("ce" + textStyles++); 578 styleName = tStyle.getName(); 579 Debug.log(Debug.TRACE,"No existing style found, adding " + styleName); 580 styleCat.add(tStyle); 581 } else { 582 CellStyle existingStyle = (CellStyle) result[0]; 583 styleName = existingStyle.getName(); 584 Debug.log(Debug.TRACE,"Existing style found : " + styleName); 585 } 586 587 cellElement.setAttribute(ATTRIBUTE_TABLE_STYLE_NAME, styleName); 588 589 // Store the cell data into the appropriate attributes 590 processCellData(cellElement, cellType, cellContents); 591 592 // Append the cell element to the row node 593 rowElement.appendChild(cellElement); 594 595 // Append the cellContents as a text node 596 Element textElement = (Element) doc.createElement(TAG_PARAGRAPH); 597 cellElement.appendChild(textElement); 598 textElement.appendChild(doc.createTextNode(cellContents)); 599 600 Debug.log(Debug.TRACE, cellContents); 601 Debug.log(Debug.TRACE, "</td>"); 602 603 // Increment to the column number of the next expected cell 604 col++; 605 } 606 607 // Make sure that the last row is padded correctly 608 if (col <= lastColumn && rowElement != null) { 609 int numSkippedCells = lastColumn - col + 1; 610 addEmptyCells(numSkippedCells, rowElement); 611 } 612 613 // Now write the style catalog to the document 614 if(autoStylesNode!=null) { 615 Debug.log(Debug.TRACE,"Well the autostyle node was found!!!"); 616 NodeList nl = styleCat.writeNode(doc, "dummy").getChildNodes(); 617 int nlLen = nl.getLength(); // nl.item reduces the length 618 for (int i = 0; i < nlLen; i++) { 619 autoStylesNode.appendChild(nl.item(0)); 620 } 621 } 622 623 if (row != 0) { 624 625 // The sheet does have rows, so write out a /tr 626 Debug.log(Debug.TRACE, "</tr>"); 627 } 628 } 629 630 631 /** 632 * This method will add empty rows to the <code>Document</code>. 633 * It is called when the conversion process encounters 634 * a row (or rows) that do not contain any data in its cells. 635 * 636 * @param numEmptyRows The number of empty rows that we 637 * need to add to the <code>Document</code>. 638 * @param root The <code>Node</code> of the 639 * <code>Document</code> we are building 640 * that we will append our empty row 641 * <code>Node</code> objects. This 642 * <code>Node</code> should be a TAG_TABLE 643 * tag. 644 * @param numEmptyCells The number of empty cells in the 645 * empty row. 646 */ addEmptyRows(int numEmptyRows, Node root, int numEmptyCells)647 protected void addEmptyRows(int numEmptyRows, Node root, int numEmptyCells) { 648 649 // Create an element node for the row 650 Element rowElement = (Element) doc.createElement(TAG_TABLE_ROW); 651 652 // TODO - style currently hardcoded - get real value 653 // Set row style-name attribute 654 rowElement.setAttribute(ATTRIBUTE_TABLE_STYLE_NAME, "Default"); 655 656 // Set cell number-rows-repeated attribute 657 rowElement.setAttribute(ATTRIBUTE_TABLE_NUM_ROWS_REPEATED, 658 Integer.toString(numEmptyRows)); 659 660 // Append the row element to the root node 661 root.appendChild(rowElement); 662 663 // Open Office requires the empty row to have an empty cell (or cells) 664 addEmptyCells(numEmptyCells, rowElement); 665 666 // Write empty rows to the log 667 for (int i = 0; i < numEmptyRows; i++) { 668 Debug.log(Debug.TRACE, "<tr />"); 669 } 670 671 } 672 673 674 /** 675 * This method will add empty cells to the <code>Document</code>. 676 * It is called when the conversion process encounters a row 677 * that contains some cells without data. 678 * 679 * @param numColsSkipped The number of empty cells 680 * that we need to add to the 681 * current row. 682 * @param row The <code>Node</code> of the 683 * <code>Document</code> we 684 * are building that we will 685 * append our empty cell 686 * <code>Node</code> objects. 687 * This <code>Node</code> should 688 * be a TAG_TABLE_ROW tag. 689 */ addEmptyCells(int numColsSkipped, Node row)690 protected void addEmptyCells(int numColsSkipped, Node row) { 691 692 // Create an empty cellElement 693 Element cellElement = (Element) doc.createElement(TAG_TABLE_CELL); 694 695 // TODO - style currently hardcoded - get real value 696 // Set cell style-name attribute 697 cellElement.setAttribute(ATTRIBUTE_TABLE_STYLE_NAME, "Default"); 698 699 // If we skipped more than 1 cell, we must set the 700 // appropriate attribute 701 if (numColsSkipped > 1) { 702 703 // Set cell number-columns-repeated attribute 704 cellElement.setAttribute(ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED, 705 Integer.toString(numColsSkipped)); 706 } 707 708 // Append the empty cell element to the row node 709 row.appendChild(cellElement); 710 711 // Write empty cells to the log 712 for (int i = 0; i < numColsSkipped; i++) { 713 Debug.log(Debug.TRACE, "<td />"); 714 } 715 } 716 717 718 /** 719 * This method process the data in a cell and sets 720 * the appropriate attributes on the cell <code>Element</code>. 721 * 722 * @param cellElement A TAG_TABLE_CELL <code>Element</code> 723 * that we will be adding attributes to 724 * based on the type of data in the cell. 725 * @param type The type of data contained in the cell. 726 * @param contents The contents of the data contained in 727 * the cell. 728 */ processCellData(Element cellElement, String type, String contents)729 protected void processCellData(Element cellElement, String type, 730 String contents) { 731 732 // Set cell value-type attribute 733 cellElement.setAttribute(ATTRIBUTE_TABLE_VALUE_TYPE, type); 734 735 // Does the cell contain a formula? 736 if (contents.startsWith("=")) { 737 738 cellElement.setAttribute(ATTRIBUTE_TABLE_FORMULA, contents); 739 740 cellElement.setAttribute(ATTRIBUTE_TABLE_VALUE, decoder.getCellValue()); 741 // String data does not require any additional attributes 742 } else if (!type.equals(CELLTYPE_STRING)) { 743 744 if (type.equals(CELLTYPE_TIME)) { 745 746 // Data returned in StarOffice XML format, so store in 747 // attribute 748 cellElement.setAttribute(ATTRIBUTE_TABLE_TIME_VALUE, 749 contents); 750 751 } else if (type.equals(CELLTYPE_DATE)) { 752 753 // Data returned in StarOffice XML format, so store in 754 // attribute 755 cellElement.setAttribute(ATTRIBUTE_TABLE_DATE_VALUE, 756 contents); 757 758 } else if (type.equals(CELLTYPE_BOOLEAN)) { 759 760 // StarOffice XML format requires stored boolean value 761 // to be in lower case 762 cellElement.setAttribute(ATTRIBUTE_TABLE_BOOLEAN_VALUE, 763 contents.toLowerCase()); 764 765 } else if (type.equals(CELLTYPE_CURRENCY)) { 766 // TODO - StarOffice XML format requires a correct style to 767 // display currencies correctly. Need to implement styles. 768 // TODO - USD is for US currencies. Need to pick up 769 // the correct currency location from the source file. 770 cellElement.setAttribute(ATTRIBUTE_TABLE_CURRENCY, "USD"); 771 772 // Data comes stripped of currency symbols 773 cellElement.setAttribute(ATTRIBUTE_TABLE_VALUE, contents); 774 775 } else if (type.equals(CELLTYPE_PERCENT)) { 776 // Data comes stripped of percent signs 777 cellElement.setAttribute(ATTRIBUTE_TABLE_VALUE, contents); 778 779 } else { 780 // Remaining data types use table-value attribute 781 782 cellElement.setAttribute(ATTRIBUTE_TABLE_VALUE, contents); 783 } 784 } 785 } 786 787 } 788 789