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 &quot;Device&quot;
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", "&apos;Courier New&apos;", "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