/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

package org.openoffice.xmerge.converter.xml.sxc;

import java.awt.Color;

import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Element;

import java.io.IOException;
import java.util.Vector;
import java.util.Enumeration;

import org.openoffice.xmerge.Document;
import org.openoffice.xmerge.ConvertData;
import org.openoffice.xmerge.ConvertException;
import org.openoffice.xmerge.DocumentSerializer;

import org.openoffice.xmerge.converter.xml.OfficeConstants;
import org.openoffice.xmerge.converter.xml.sxc.SxcDocument;
import org.openoffice.xmerge.converter.xml.sxc.CellStyle;
import org.openoffice.xmerge.converter.xml.StyleCatalog;

import org.openoffice.xmerge.util.Debug;
import org.openoffice.xmerge.util.XmlUtil;

/**
 *  <p>General spreadsheet implementation of <code>DocumentSerializer</code>
 *  for the {@link
 *  org.openoffice.xmerge.converter.xml.sxc.SxcPluginFactory
 *  SxcPluginFactory}.  Used with SXC <code>Document</code> objects.</p>
 *
 *  <p>The <code>serialize</code> method traverses the DOM
 *  <code>Document</code> from the given <code>Document</code> object.
 *  It uses a <code>DocEncoder</code> object for the actual conversion
 *  of contents to the device spreadsheet format.</p>
 *
 * @author      Paul Rank
 * @author      Mark Murnane
 */
public abstract class SxcDocumentSerializer implements OfficeConstants,
    DocumentSerializer {

    /**  The cell foreground <code>Color</code>. */
    private Color foreground = Color.black;

    /**  The cell background <code>Color</code>. */
    private Color background = Color.white;

    /**  The cell format. */
    private long format = 0;

    /**  <code>Format</code> object describing the cell. */
    private Format fmt = null;

    /**  The row number. */
    private int rowID = 1;

    /**  The column number. */
    private int colID = 1;

    /**  The number of times the current row is repeated. */
    private int rowsRepeated = 1;

    /**  The number of times the current column is repeated. */
    private int colsRepeated = 1;

    /**  The number of times the current column is repeated. */
    private StyleCatalog styleCat = null;
    /**
     *  An array of column widths of the current worksheet.  Width is
     *  measured in number of characters.
     */
    private Vector ColumnRowList;

    /**  Width, in characters, of the current cell display data. */
    private int displayWidth = 0;

    /**
     *  A <code>SpreadsheetEncoder</code> object for encoding to
     *  appropriate format.
     */
    protected SpreadsheetEncoder encoder = null;

    /**  <code>SxcDocument</code> object that this converter processes. */
    protected SxcDocument sxcDoc = null;


    /**
     *  Constructor.
     *
     *  @param  document  Input <code>SxcDocument</code>
     *                    <code>Document</code>.
     */
    public SxcDocumentSerializer(Document document) {
        fmt = new Format();
        sxcDoc = (SxcDocument) document;
    }


    /**
     *  <p>Method to convert a DOM <code>Document</code> into
     *  &quot;Device&quot; <code>Document</code> objects.</p>
     *
     *  <p>This method is not thread safe for performance reasons.
     *  This method should not be called from within two threads.
     *  It would be best to call this method only once per object
     *  instance.</p>
     *
     *  @return  <code>ConvertData</code> containing &quot;Device&quot;
     *           <code>Document</code> objects.
     *
     *  @throws  ConvertException  If any conversion error occurs.
     *  @throws  IOException       If any I/O error occurs.
     */
    public abstract ConvertData serialize() throws ConvertException,
        IOException;


    /**
     *  This method traverses <i>office:settings</i> <code>Element</code>.
     *
     *  @param  node  <i>office:settings</i> <code>Node</code>.
     *
     *  @throws  IOException  If any I/O error occurs.
     */
    public void traverseSettings(Node node) throws IOException {
        if (node.hasChildNodes()) {

            NodeList nodeList = node.getChildNodes();
            int len = nodeList.getLength();
            for (int i = 0; i < len; i++) {
                Node child = nodeList.item(i);

                if (child.getNodeType() == Node.ELEMENT_NODE) {
                    String nodeName = child.getNodeName();

                    if (nodeName.equals(TAG_CONFIG_ITEM_SET)) {

                        traverseSettings(child);

                    } else if (nodeName.equals(TAG_CONFIG_ITEM_MAP_INDEXED)) {

                        traverseSettings(child);

                     } else if (nodeName.equals(TAG_CONFIG_ITEM_MAP_ENTRY)) {

                        BookSettings bs = new BookSettings(child);
                        encoder.addSettings(bs);

                    } else {

                        Debug.log(Debug.TRACE, "<OTHERS " + XmlUtil.getNodeInfo(child) + " />");
                    }
                }
            }
        }
    }

    /*
     * Handles the loading of defined styles from the style.xml file as well
     * as automatic styles from the content.xml file.
     *
     * Any change to a defined style, such as a short bold section, falls into
     * the latter category.
     */
    protected void loadStyles(SxcDocument sxcDoc) {

        styleCat = new StyleCatalog(25);
        NodeList nl = null;
        String families[] = new String[] {	SxcConstants.COLUMN_STYLE_FAMILY,
                                            SxcConstants.ROW_STYLE_FAMILY,
                                            SxcConstants.TABLE_CELL_STYLE_FAMILY };
        Class classes[]   = new Class[] {	ColumnStyle.class,
                                            RowStyle.class,
                                            CellStyle.class};
        /*
         * Process the content XML for any other style info.
         */
        org.w3c.dom.Document contentDom = sxcDoc.getContentDOM();
        nl = contentDom.getElementsByTagName(TAG_OFFICE_AUTOMATIC_STYLES);
        if (nl.getLength() != 0) {
            styleCat.add(nl.item(0), families, classes, null, false);
        }

        org.w3c.dom.Document stylesDom = sxcDoc.getStyleDOM();
        nl = stylesDom.getElementsByTagName(TAG_OFFICE_STYLES);
        if (nl.getLength() != 0) {
            styleCat.add(nl.item(0), families, classes, null, false);
        }
    }

    /**
     *  This method traverses <i>office:body</i> <code>Element</code>.
     *
     *  @param  node  <i>office:body</i> <code>Node</code>.
     *
     *  @throws  IOException  If any I/O error occurs.
     */
    protected void traverseBody(Node node) throws IOException {

        Debug.log(Debug.TRACE, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        Debug.log(Debug.TRACE, "<DEBUGLOG>");

        if (node.hasChildNodes()) {

            NodeList nodeList = node.getChildNodes();
            int len = nodeList.getLength();

            for (int i = 0; i < len; i++) {
                Node searchNode = nodeList.item(i);
                if (searchNode.getNodeType() == Node.ELEMENT_NODE) {

                    String nodeName = searchNode.getNodeName();

                    if (nodeName.equals(TAG_NAMED_EXPRESSIONS)) {

                        traverseNamedExpressions(searchNode);

                    } else {

                        Debug.log(Debug.TRACE, "Skipping " + XmlUtil.getNodeInfo(searchNode) + " />");
                    }
                }
            }

            for (int i = 0; i < len; i++) {
                Node child = nodeList.item(i);

                if (child.getNodeType() == Node.ELEMENT_NODE) {
                    String nodeName = child.getNodeName();

                    if (nodeName.equals(TAG_TABLE)) {

                        traverseTable(child);

                    } else {

                        Debug.log(Debug.TRACE, "<OTHERS " + XmlUtil.getNodeInfo(child) + " />");
                    }
                }
            }
        }

        Debug.log(Debug.TRACE, "</DEBUGLOG>");
    }


    /**
     *  This method traverses the <i>table:table</i> element
     *  <code>Node</code>.
     *
     *  @param  node  A <i>table:table</i> <code>Node</code>.
     *
     *  @throws  IOException  If any I/O error occurs.
     */
    protected void traverseNamedExpressions(Node node) throws IOException {

        Debug.log(Debug.TRACE, "<NAMED:EXPRESSIONS>");

        NamedNodeMap att = node.getAttributes();

        if (node.hasChildNodes()) {

            NodeList nodeList = node.getChildNodes();
            int len = nodeList.getLength();

            for (int i = 0; i < len; i++) {
                Node child = nodeList.item(i);

                if (child.getNodeType() == Node.ELEMENT_NODE) {
                    NameDefinition nd = new NameDefinition(child);
                    encoder.setNameDefinition(nd);
                }
            }
        }

        Debug.log(Debug.TRACE, "</NAMED:EXPRESSIONS>");
    }

    /**
     *  This method traverses the <i>table:table</i> element
     *  <code>Node</code>.
     *
     *  @param  node  A <i>table:table</i> <code>Node</code>.
     *
     *  @throws  IOException  If any I/O error occurs.
     */
    protected void traverseTable(Node node) throws IOException {

        Debug.log(Debug.TRACE, "<TABLE>");

        ColumnRowList = new Vector();

        // Get table attributes
        // TODO - extract style from attribute

        NamedNodeMap att = node.getAttributes();

        String tableName =
            att.getNamedItem(ATTRIBUTE_TABLE_NAME).getNodeValue();

        rowID = 1;

        encoder.createWorksheet(tableName);

        if (node.hasChildNodes()) {

            NodeList nodeList = node.getChildNodes();
            int len = nodeList.getLength();

            for (int i = 0; i < len; i++) {
                Node child = nodeList.item(i);

                if (child.getNodeType() == Node.ELEMENT_NODE) {
                    String nodeName = child.getNodeName();

                    if (nodeName.equals(TAG_TABLE_ROW)) {
                        // TODO - handle all the possible rows
                        // spelled out in the entities

                        traverseTableRow(child);

                    } else if (nodeName.equals(TAG_TABLE_COLUMN)) {

                        traverseTableColumn(child);

                    } else if (nodeName.equals(TAG_TABLE_SCENARIO)) {

                        // TODO

                    } else {

                        Debug.log(Debug.TRACE, "<OTHERS " + XmlUtil.getNodeInfo(child) + " />");
                    }
                }
            }
        }

        // Add column width info to the current sheet
        encoder.setColumnRows(ColumnRowList);

        Debug.log(Debug.TRACE, "</TABLE>");
    }

    /**
     *  This method traverses the <i>table:table-row</i> element
     *  <code>Node</code>.
     *
     *  @param  node  A <i>table:table-row</i> <code>Node</code>.
     *
     *  @throws  IOException  If any I/O error occurs.
     */
    protected void traverseTableRow(Node node) throws IOException {

        // Get the attributes of the row
        NamedNodeMap cellAtt = node.getAttributes();

        if (cellAtt != null) {

            Node rowStyle =
                cellAtt.getNamedItem(ATTRIBUTE_TABLE_STYLE_NAME);

            Node tableNumRowRepeatingNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_NUM_ROWS_REPEATED);
            int repeatedRows = 1;

            if(tableNumRowRepeatingNode!=null) {
                String repeatStr = tableNumRowRepeatingNode.getNodeValue();
                Debug.log(Debug.TRACE, "traverseTableRow() repeated-rows : " + repeatStr);
                repeatedRows = Integer.parseInt(repeatStr);
            }

            String styleName = new String("");

            if ( rowStyle != null) {
                styleName = rowStyle.getNodeValue();
            }
            if(styleName.equalsIgnoreCase("Default") || styleName.length()==0) {

                Debug.log(Debug.TRACE, "No defined Row Style Attribute was found");

            } else {

                RowStyle rStyle = (	RowStyle)styleCat.lookup(styleName,
                                        SxcConstants.ROW_STYLE_FAMILY, null,
                                        RowStyle.class);

                int rowHeight = rStyle.getRowHeight();

                Debug.log(Debug.TRACE, "traverseTableRow() Row Height : " + rowHeight);
                ColumnRowInfo ri = new ColumnRowInfo(	rowHeight,
                                                            repeatedRows,
                                                            ColumnRowInfo.ROW,
                                                            rowHeight!=0);
                ColumnRowList.add(ri);
            }

            // Get the attribute representing the number of rows repeated
            Node rowsRepeatedNode =
                cellAtt.getNamedItem(ATTRIBUTE_TABLE_NUM_ROWS_REPEATED);

            // There is a number of rows repeated attribute:
            if (rowsRepeatedNode != null) {

                // Get the number of times the row is repeated
                String rowsRepeatedString = rowsRepeatedNode.getNodeValue();

                Integer rowsRepeatedInt = new Integer(rowsRepeatedString);

                rowsRepeated = rowsRepeatedInt.intValue();

            } else {

                // The row is not repeated
                rowsRepeated = 1;
            }
        }

        Debug.log(Debug.TRACE, "<TR>");

        if (node.hasChildNodes()) {

            NodeList nodeList = node.getChildNodes();
            int len = nodeList.getLength();

            for (int i = 0; i < len; i++) {
                Node child = nodeList.item(i);

                if (child.getNodeType() == Node.ELEMENT_NODE) {
                    String nodeName = child.getNodeName();

                    if (nodeName.equals(TAG_TABLE_CELL)) {

                        traverseCell(child);

                    } else {

                        Debug.log(Debug.TRACE, "<OTHERS " + XmlUtil.getNodeInfo(child) + " />");
                    }
                }
            }
        }

        // Increase the row counter by the number of rows which are repeated
        rowID += rowsRepeated;

        // Re-initialize number of rows repeated before processing the next
        // row data.
        rowsRepeated = 1;

        // When starting a new row, set the column counter back to the
        // first column.
        colID = 1;

        // Re-initialize number of columns repeated before processing
        // the next row data.
        colsRepeated = 1;

        Debug.log(Debug.TRACE, "</TR>");
    }


    /**
     *  This method traverses the <i>table:table-column</i>
     *  <code>Node</code>.  Not yet implemented.
     *
     *  @param  node  A <i>table:table-column</i> <code>Node</code>.
     *
     *  @throws  IOException  If any I/O error occurs.
     */
    protected void traverseTableColumn(Node node) throws IOException {

        Debug.log(Debug.TRACE, "traverseColumn() : ");
        NamedNodeMap cellAtt = node.getAttributes();
        Node tableStyleNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_STYLE_NAME);
        Node tableNumColRepeatingNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED);
        Node tableDefaultCellStyle = cellAtt.getNamedItem(ATTRIBUTE_DEFAULT_CELL_STYLE);

        int repeatedColumns = 1;
        int columnWidth = 0;
        ColumnRowInfo col = new ColumnRowInfo(ColumnRowInfo.COLUMN);

        if(tableNumColRepeatingNode!=null) {
            Debug.log(Debug.TRACE, "traverseColumn() repeated-cols : " + tableNumColRepeatingNode.getNodeValue());
            repeatedColumns = Integer.parseInt(tableNumColRepeatingNode.getNodeValue());
            col.setRepeated(repeatedColumns);
        }

        String cellStyleName = new String("");

        if(tableDefaultCellStyle!=null) {
            cellStyleName = tableDefaultCellStyle.getNodeValue();

            Debug.log(Debug.TRACE, "traverseColumn() default-cell-style : " + cellStyleName);
        }

        if(cellStyleName.equalsIgnoreCase("Default") || cellStyleName.length()==0) {

            Debug.log(Debug.TRACE, "No default cell Style Attribute was found");

        } else {

            CellStyle cellStyle = (CellStyle)styleCat.lookup(cellStyleName,
                                SxcConstants.TABLE_CELL_STYLE_FAMILY, null,
                                CellStyle.class);
            Format defaultFmt = new Format(cellStyle.getFormat());
            col.setFormat(defaultFmt);
        }

        String styleName = new String("");

        if(tableStyleNode!=null) {
            styleName = tableStyleNode.getNodeValue();
        }

        if(styleName.equalsIgnoreCase("Default") || styleName.length()==0) {

            Debug.log(Debug.TRACE, "No defined Style Attribute was found");

        } else {

            ColumnStyle cStyle = (ColumnStyle)styleCat.lookup(styleName,
                                SxcConstants.COLUMN_STYLE_FAMILY, null,
                                ColumnStyle.class);

            columnWidth = cStyle.getColWidth();
            col.setSize(columnWidth);
            Debug.log(Debug.TRACE, "traverseColumn() Column Width : " + columnWidth);

        }
        ColumnRowList.add(col);
    }

    /**
     *  This method traverses a <i>table:table-cell</i> element
     *  <code>Node</code>.
     *
     *  @param  node  a <i>table:table-cell</i> <code>Node</code>.
     *
     *  @throws  IOException  if any I/O error occurs.
     */
    protected void traverseCell(Node node) throws IOException {

        NamedNodeMap cellAtt = node.getAttributes();

        int debug_i=0;
        Node debug_attrib = null;
        fmt.clearFormatting();
        if (cellAtt == null || cellAtt.item(0) == null)
        {
           Debug.log(Debug.INFO, "No Cell Attributes\n");
           // return;
        }
        else
        {
           while ((debug_attrib = cellAtt.item(debug_i++)) != null)
           {
              Debug.log(Debug.INFO, "Cell Attribute " + debug_i +
                 ": " + debug_attrib.getNodeName() + " : " +
                 debug_attrib.getNodeValue() + "\n");
           }
        }

        // Get the type of data in the cell
        Node tableValueTypeNode =
            cellAtt.getNamedItem(ATTRIBUTE_TABLE_VALUE_TYPE);

        // Get the number of columns this cell is repeated
        Node colsRepeatedNode =
            cellAtt.getNamedItem(ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED);

        // Get the style type
        Node tableStyleNode =
            cellAtt.getNamedItem(ATTRIBUTE_TABLE_STYLE_NAME);

        String styleName = new String("");

        if(tableStyleNode!=null) {
            styleName = tableStyleNode.getNodeValue();
        }

        if(styleName.equalsIgnoreCase("Default")) {

            Debug.log(Debug.TRACE, "No defined Style Attribute was found");

        } else if(styleName.length()!=0) {

            CellStyle cStyle = (CellStyle)styleCat.lookup(styleName,
                                SxcConstants.TABLE_CELL_STYLE_FAMILY, null,
                                CellStyle.class);

            Format definedFormat = cStyle.getFormat();
            fmt = new Format(definedFormat);
        }

        // There is a number of cols repeated attribute
        if (colsRepeatedNode != null) {

            // Get the number of times the cell is repeated
            String colsRepeatedString = colsRepeatedNode.getNodeValue();

            Integer colsRepeatedInt = new Integer(colsRepeatedString);
            colsRepeated = colsRepeatedInt.intValue();
        } else {

            // The cell is not repeated
            colsRepeated = 1;
        }


        // if there is no style we need to check to see if there is a default
        // cell style defined in the table-column's

        if (fmt.isDefault() && styleName.length()==0) {
            int index = 1;
            for(Enumeration e = ColumnRowList.elements();e.hasMoreElements();) {
                ColumnRowInfo cri = (ColumnRowInfo) e.nextElement();
                if(cri.isColumn()) {
                    if(colID>=index && colID<(index+cri.getRepeated())) {
                        fmt = new Format(cri.getFormat());
                    }
                    index += cri.getRepeated();
                }
            }
        }


        // for (int j = 0; j < colsRepeated; j++) {


        if (tableValueTypeNode != null) {

            // Make sure we initialize to 0 the width of the current cell
            displayWidth = 0;

            String cellType =
                tableValueTypeNode.getNodeValue();

            if (cellType.equalsIgnoreCase(CELLTYPE_STRING)) {

                // has text:p tag
                fmt.setCategory(CELLTYPE_STRING);
                Node tableStringValueNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_STRING_VALUE);
                Debug.log(Debug.TRACE,"Cell Type String :  " + tableStringValueNode);
                if(tableStringValueNode != null) {
                    fmt.setValue(tableStringValueNode.getNodeValue());
                }

            } else if (cellType.equalsIgnoreCase(CELLTYPE_FLOAT)) {

                // has table:value attribute
                // has text:p tag

                // Determine the number of decimal places StarCalc
                // is displaying for this floating point output.
                fmt.setCategory(CELLTYPE_FLOAT);
                fmt.setDecimalPlaces(getDecimalPlaces(node));
                  Node tableValueNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_VALUE);
                fmt.setValue(tableValueNode.getNodeValue());


            } else if (cellType.equalsIgnoreCase(CELLTYPE_TIME)) {

                // has table:time-value attribute
                // has text:p tag - which is the value we convert

                fmt.setCategory(CELLTYPE_TIME);
                Node tableTimeNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_TIME_VALUE);
                fmt.setValue(tableTimeNode.getNodeValue());

            } else if (cellType.equalsIgnoreCase(CELLTYPE_DATE)) {

                // has table:date-value attribute
                // has text:p tag - which is the value we convert

                fmt.setCategory(CELLTYPE_DATE);
                Node tableDateNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_DATE_VALUE);
                fmt.setValue(tableDateNode.getNodeValue());

            } else if (cellType.equalsIgnoreCase(CELLTYPE_CURRENCY)) {

                // has table:currency
                // has table:value attribute
                // has text:p tag

                fmt.setCategory(CELLTYPE_CURRENCY);
                fmt.setDecimalPlaces(getDecimalPlaces(node));
                Node tableValueNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_VALUE);
                fmt.setValue(tableValueNode.getNodeValue());

            } else if (cellType.equalsIgnoreCase(CELLTYPE_BOOLEAN)) {

                // has table:boolean-value attribute
                // has text:p tag - which is the value we convert

                fmt.setCategory(CELLTYPE_BOOLEAN);
                Node tableBooleanNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_BOOLEAN_VALUE);
                fmt.setValue(tableBooleanNode.getNodeValue());

            } else if (cellType.equalsIgnoreCase(CELLTYPE_PERCENT)) {

                // has table:value attribute
                // has text:p tag

                fmt.setCategory(CELLTYPE_PERCENT);
                fmt.setDecimalPlaces(getDecimalPlaces(node));
                Node tableValueNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_VALUE);
                fmt.setValue(tableValueNode.getNodeValue());

            } else {

                Debug.log(Debug.TRACE,"No defined value type" + cellType);
                // Should never get here

            }
        }

        Node tableFormulaNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_FORMULA);

        if(tableFormulaNode != null)
        {
            if(tableValueTypeNode == null) {			// If there is no value-type Node we must assume string-value
                fmt.setCategory(CELLTYPE_STRING);
                Node tableStringValueNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_STRING_VALUE);
                fmt.setValue(tableStringValueNode.getNodeValue());
            }
            String cellFormula = tableFormulaNode.getNodeValue();
            addCell(cellFormula);
        } else {

            // Text node, Date node, or Time node
            //
            Debug.log(Debug.INFO,
            "TextNode, DateNode, TimeNode or BooleanNode\n");
            // This handles the case where we have style information but no content
            if (node.hasChildNodes()) {
                NodeList childList = node.getChildNodes();
                int len = childList.getLength();

                for (int i = 0; i < len; i++) {
                    Node child = childList.item(i);
                    if (child.getNodeType() == Node.ELEMENT_NODE) {
                        String childName = child.getNodeName();
                        if (childName.equals(TAG_PARAGRAPH)) {
                            traverseParagraph(child);
                        }
                    }
                }
            } else if(!fmt.isDefault()) {
                addCell("");
            }
        }

        // Clear out format for current cell after it is written
        format = 0;

        // Increase the column counter by the number of times the
        // last cell was repeated.
        colID += colsRepeated;

        // Re-initialize the number of columns repeated before processing
        // the next cell data.
        colsRepeated = 1;

    }


    /**
     *  This method traverses the <i>text:p</i> element <code>Node</code>.
     *
     *  @param  node  A <i>text:p</i> <code>Node</code>.
     *
     *  @throws  IOException  If any I/O error occurs.
     */
    protected void traverseParagraph(Node node) throws IOException {

        NamedNodeMap cellAtt = node.getAttributes();

        int debug_i=0;
        Node debug_attrib = null;
        if (cellAtt == null || cellAtt.item(0) == null)
        {
           Debug.log(Debug.INFO, "No Paragraph Attributes\n");
        }
        else
        {
           while ((debug_attrib = cellAtt.item(debug_i++)) != null)
           {
              Debug.log(Debug.INFO, "Paragraph Attribute " + debug_i +
                 ": " + debug_attrib.getNodeName() + " : " +
                 debug_attrib.getNodeValue() + "\n");
           }
        }

        if (node.hasChildNodes()) {

            NodeList nodeList = node.getChildNodes();

            int len = nodeList.getLength();

            StringBuffer buffer = new StringBuffer();

            for (int i = 0; i < len; i++) {

                Node child = nodeList.item(i);

                // TODO: need to handle space/tabs/newline nodes later
                short nodeType = child.getNodeType();

                switch (nodeType) {

                    case Node.TEXT_NODE:
                        buffer.append(child.getNodeValue());
                        break;

                    case Node.ENTITY_REFERENCE_NODE:

                        NodeList nodeList2 = child.getChildNodes();
                        int len2 = nodeList2.getLength();

                        for (int j = 0; j < len2; j++) {
                            Node child2 = nodeList2.item(j);

                            if (child2.getNodeType() == Node.TEXT_NODE) {
                                buffer.append(child2.getNodeValue());
                            }
                        }

                        break;
                }
            }

            String s = buffer.toString();
            // displayWidth = calculateContentWidth(s);
            addCell(s);

        }
    }


    /**
     *  This method will take the input cell value and add
     *  it to the spreadsheet <code>Document</code> we are currently
     *  encoding.  This method correctly handles cells that are
     *  repeated in either the row, cell, or both directions.
     *
     *  @param  cellValue  The contents of the cell we want to add
     *                     to the spreadsheet <code>Document</code>.
     *
     *  @throws  IOException  If any I/O error occurs.
     */
    protected void addCell(String cellValue) throws IOException {

        int col = colID;
        int row = rowID;

        for (int i = 0; i < rowsRepeated; i++) {

            // Log the columns when there are rowsRepeated.
            if (i > 0) {
                Debug.log(Debug.TRACE, "</TR>");
                Debug.log(Debug.TRACE, "<TR>");
            }

            col = colID;

            for (int j = 0; j < colsRepeated; j++) {

                Debug.log(Debug.TRACE, "<TD>");


                // Add the cell data to the encoded spreadsheet document
                encoder.addCell(row, col, fmt, cellValue);

                Debug.log(Debug.TRACE, cellValue);
                Debug.log(Debug.TRACE, "</TD>");

                col++;
            }

            row++;

        }

    }



    /**
     *  This method takes a <i>table:table-cell</i> <code>Node</code>
     *  and traverses down to the <i>text:p</i> tag.  The value is
     *  extracted from the <i>text:p</i> tag and the number of decimal
     *  places is calculated.
     *
     *  @param  node  A <i>table:table-cell</i> <code>Node</code>.
     *
     *  @return  The number of decimal places in the display
     *           string of the data in the input <code>Node</code>.
     */
    protected int getDecimalPlaces(Node node) {

        int decimals = 0;

        Element element = null;

        // cast org.w3c.dom.Node to org.w3c.dom.Element
        if (node instanceof Element) {
            element = (Element) node;
        } else {
            return decimals;
        }

        //  Traverse to the text:p element, there should only be one.
        NodeList list = element.getElementsByTagName(TAG_PARAGRAPH);

        if (list.getLength() != 1) {
            return decimals;
        }

        Node paragraph = list.item(0);

        if (paragraph.hasChildNodes()) {

            NodeList nodeList = paragraph.getChildNodes();

            int len = nodeList.getLength();

            for (int j = 0; j < len; j++) {

                Node child = nodeList.item(j);

                if (child.getNodeType() == Node.TEXT_NODE) {

                    String s = child.getNodeValue();

                    // displayWidth = calculateContentWidth(s);

                    int k = s.lastIndexOf(".");
                    if (k > 0) {
                        s = s.substring(k+1);
                        decimals = s.length();
                    }
                }
            }
        }

        return decimals;
    }

    /*
     * Utility method to retrieve a Node attribute.
     */
    private String getAttribute (Node node, String attribute) {
        NamedNodeMap attrNodes = node.getAttributes();

        if (attrNodes != null) {
            Node attr = attrNodes.getNamedItem(attribute);
            if (attr != null) {
                return attr.getNodeValue();
            }
        }
        return null;
    }

}

