/*
 * JasperReports - Free Java Reporting Library.
 * Copyright (C) 2001 - 2009 Jaspersoft Corporation. All rights reserved.
 * http://www.jaspersoft.com
 *
 * Unless you have purchased a commercial license agreement from Jaspersoft,
 * the following license terms apply:
 *
 * This program is part of JasperReports.
 *
 * JasperReports is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JasperReports 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 for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with JasperReports. If not, see <http://www.gnu.org/licenses/>.
 */

package net.sf.jasperreports.engine.export.xmlss;

import java.awt.Color;
import java.awt.Font;
import java.awt.font.TextAttribute;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.text.AttributedCharacterIterator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import net.sf.jasperreports.engine.JRAbstractExporter;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JRFont;
import net.sf.jasperreports.engine.JRPrintElement;
import net.sf.jasperreports.engine.JRPrintElementIndex;
import net.sf.jasperreports.engine.JRPrintFrame;
import net.sf.jasperreports.engine.JRPrintHyperlink;
import net.sf.jasperreports.engine.JRPrintPage;
import net.sf.jasperreports.engine.JRPrintText;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.base.JRBasePrintText;
import net.sf.jasperreports.engine.export.CutsInfo;
import net.sf.jasperreports.engine.export.ExporterNature;
import net.sf.jasperreports.engine.export.GenericElementHandlerEnviroment;
import net.sf.jasperreports.engine.export.JRExportProgressMonitor;
import net.sf.jasperreports.engine.export.JRExporterGridCell;
import net.sf.jasperreports.engine.export.JRGridLayout;
import net.sf.jasperreports.engine.export.JRHyperlinkProducer;
import net.sf.jasperreports.engine.export.JRXlsAbstractExporterParameter;
import net.sf.jasperreports.engine.export.JRXlsExporterParameter;
import net.sf.jasperreports.engine.export.ResetableExporterFilter;
import net.sf.jasperreports.engine.export.data.BooleanTextValue;
import net.sf.jasperreports.engine.export.data.DateTextValue;
import net.sf.jasperreports.engine.export.data.NumberTextValue;
import net.sf.jasperreports.engine.export.data.TextValue;
import net.sf.jasperreports.engine.type.OrientationEnum;
import net.sf.jasperreports.engine.util.JRColorUtil;
import net.sf.jasperreports.engine.util.JRProperties;
import net.sf.jasperreports.engine.util.JRStringUtil;
import net.sf.jasperreports.engine.util.JRStyledText;


/**
 * Exports a JasperReports document to XML Spreadsheet format. It has character output type and exports the document to a
 * grid-based layout.
 * @author sanda zaharia (shertage@users.sourceforge.net)
 * @version $Id: JRXmlssExporter.java 3675 2010-04-02 08:58:23Z shertage $
 */
public class JRXmlssExporter extends JRAbstractExporter
{
	/**
	 * The exporter key, as used in
	 * {@link GenericElementHandlerEnviroment#getHandler(net.sf.jasperreports.engine.JRGenericElementType, String)}.
	 */
	public static final String XMLSS_EXPORTER_KEY = JRProperties.PROPERTY_PREFIX + "xmlss";

	private static final String XMLSS_EXPORTER_PROPERTIES_PREFIX = JRProperties.PROPERTY_PREFIX + "export.xmlss.";

	/**
	 *
	 */
	protected static final String JR_PAGE_ANCHOR_PREFIX = "JR_PAGE_ANCHOR_";

	/**
	 *
	 */
	protected static final String HORIZONTAL_ALIGN_LEFT = "start";
	protected static final String HORIZONTAL_ALIGN_RIGHT = "end";
	protected static final String HORIZONTAL_ALIGN_CENTER = "center";
	protected static final String HORIZONTAL_ALIGN_JUSTIFY = "justified";

	/**
	 *
	 */
	protected static final String VERTICAL_ALIGN_TOP = "top";
	protected static final String VERTICAL_ALIGN_MIDDLE = "middle";
	protected static final String VERTICAL_ALIGN_BOTTOM = "bottom";

	public static final String IMAGE_NAME_PREFIX = "img_";
	protected static final int IMAGE_NAME_PREFIX_LEGTH = IMAGE_NAME_PREFIX.length();

	protected static final String[] PAGE_LAYOUT = new String[] {"Portrait", "Portrait", "Landscape"};

	/**
	 *
	 */
	protected Writer tempBodyWriter = null;
	protected Writer tempStyleWriter = null;

	protected JRExportProgressMonitor progressMonitor = null;
	protected Map rendererToImagePathMap = null;
	protected Map imageMaps;
	protected List imagesToProcess = null;

	protected int reportIndex = 0;
	protected int pageIndex = 0;
	protected int tableIndex = 0;
	protected boolean startPage;

	/**
	 *
	 */
	protected String encoding = null;


	/**
	 *
	 */
	protected boolean isWrapBreakWord = false;

	/**
	 * @deprecated
	 */
	protected Map fontMap = null;

	private LinkedList backcolorStack;
	private Color backcolor;

	private XmlssStyleCache styleCache = null;

	protected ExporterNature nature = null;

	protected File destFile = null;

	/**
	 *
	 */
	protected boolean isOnePagePerSheet;
	protected boolean isRemoveEmptySpaceBetweenRows;
	protected boolean isRemoveEmptySpaceBetweenColumns;
	protected boolean isWhitePageBackground;
	protected boolean isAutoDetectCellType = false;
	protected boolean isDetectCellType;
	protected boolean isFontSizeFixEnabled;
	protected boolean isIgnoreGraphics;
	protected boolean isCollapseRowSpan;
	protected boolean isIgnoreCellBorder;
	protected boolean isIgnorePageMargins;

	protected int maxRowsPerSheet;

	protected String[] sheetNames = null;

	/**
	 * used for counting the total number of sheets
	 */
	protected int sheetIndex = 0;

	/**
	 * used when indexing the identical sheet generated names with ordering numbers;
	 * contains sheet names as keys and the number of occurences of each sheet name as values
	 */
	protected Map sheetNamesMap = null;
	protected String currentSheetName = null;

	/**
	 *
	 */
	protected JRFont defaultFont = null;

	protected Map formatPatternsMap = null;

	protected OrientationEnum pageOrientation;

	public JRXmlssExporter()
	{
		backcolorStack = new LinkedList();
		backcolor = null;
	}


	/**
	 *
	 */
	public void exportReport() throws JRException
	{
		progressMonitor = (JRExportProgressMonitor)parameters.get(JRExporterParameter.PROGRESS_MONITOR);

		/*   */
		setOffset();

		try
		{
			/*   */
			setExportContext();

			/*   */
			setInput();

			if (!parameters.containsKey(JRExporterParameter.FILTER))
			{
				filter = createFilter(XMLSS_EXPORTER_PROPERTIES_PREFIX);
			}

			/*   */
			if (!isModeBatch)
			{
				setPageRange();
			}

			setParameters();

			nature = new JRXmlssExporterNature(filter, isIgnorePageMargins);
			pageOrientation = jasperPrint.getOrientationValue();

			StringBuffer sb = (StringBuffer)parameters.get(JRExporterParameter.OUTPUT_STRING_BUFFER);
			if (sb != null)
			{
				StringBuffer buffer = exportReportToBuffer();
				sb.append(buffer.toString());
			}
			else
			{
				Writer outWriter = (Writer)parameters.get(JRExporterParameter.OUTPUT_WRITER);
				if (outWriter != null)
				{
					try
					{
						exportReportToStream(outWriter);
					}
					catch (IOException e)
					{
						throw new JRException("Error writing to writer : " + jasperPrint.getName(), e);
					}
				}
				else
				{
					OutputStream os = (OutputStream)parameters.get(JRExporterParameter.OUTPUT_STREAM);
					if (os != null)
					{
						try
						{
							exportReportToStream(new OutputStreamWriter(os, encoding));
						}
						catch (Exception e)
						{
							throw new JRException("Error writing to OutputStream : " + jasperPrint.getName(), e);
						}
					}
					else
					{
						destFile = (File)parameters.get(JRExporterParameter.OUTPUT_FILE);
						if (destFile == null)
						{
							String fileName = (String)parameters.get(JRExporterParameter.OUTPUT_FILE_NAME);
							if (fileName != null)
							{
								destFile = new File(fileName);
							}
							else
							{
								throw new JRException("No output specified for the exporter.");
							}
						}

						exportReportToFile();
					}
				}
			}
		}
		finally
		{
			resetExportContext();
		}

	}


	/**
	*
	*/
	protected void exportReportToFile() throws JRException
	{
		Writer writer = null;
		try
		{
			OutputStream fileOutputStream = new FileOutputStream(destFile);
			writer = new BufferedWriter(new OutputStreamWriter(fileOutputStream, encoding));
			exportReportToStream(writer);
		}
		catch (IOException e)
		{
			throw new JRException("Error writing to file : " + destFile, e);
		}
		finally
		{
			if (writer != null)
			{
				try
				{
					writer.close();
				}
				catch(IOException e)
				{
				}
			}
		}

	}


	/**
	*
	*/
	protected StringBuffer exportReportToBuffer() throws JRException
	{
		StringWriter buffer = new StringWriter();
		try
		{
			exportReportToStream(buffer);
		}
		catch (IOException e)
		{
			throw new JRException("Error while exporting report to buffer", e);
		}
		return buffer.getBuffer();
	}


	/**
	 *
	 */
	protected void exportReportToStream(Writer writer) throws JRException, IOException
	{
		tempBodyWriter = new StringWriter();
		tempStyleWriter = new StringWriter();

		styleCache = new XmlssStyleCache(tempStyleWriter, fontMap);

		sheetNamesMap = new HashMap();
		sheetNamesMap.put("Page", Integer.valueOf(0)); // in order to skip first sheet name that would have no index

		for(reportIndex = 0; reportIndex < jasperPrintList.size(); reportIndex++)
		{
			setJasperPrint((JasperPrint)jasperPrintList.get(reportIndex));
			defaultFont = new JRBasePrintText(jasperPrint.getDefaultStyleProvider());

			List pages = jasperPrint.getPages();
			if (pages != null && pages.size() > 0)
			{
				if (isModeBatch)
				{
					startPageIndex = 0;
					endPageIndex = pages.size() - 1;
				}

				if (isOnePagePerSheet)
				{
					for(int pageIndex = startPageIndex; pageIndex <= endPageIndex; pageIndex++)
					{
						if (Thread.interrupted())
						{
							throw new JRException("Current thread interrupted.");
						}

						JRPrintPage page = (JRPrintPage)pages.get(pageIndex);

						if (sheetNames != null && sheetIndex < sheetNames.length)
						{
							tempBodyWriter.write("<Worksheet ss:Name=\""+getSheetName(sheetNames[sheetIndex])+"\">\n");
						}
						else
						{
							tempBodyWriter.write("<Worksheet ss:Name=\""+getSheetName("Page")+"\">\n");
						}

						// we need to count all sheets generated for all exported documents
						sheetIndex++;

						tempBodyWriter.write("<Table>\n");

						exportPage(page, null, 0, null, true);

						tempBodyWriter.write("</Table>\n");

						closeWorksheet();
					}
				}
				else
				{
					// Create the sheet before looping.
					if (sheetNames != null && sheetIndex < sheetNames.length)
					{
						tempBodyWriter.write("<Worksheet ss:Name=\""+getSheetName(sheetNames[sheetIndex])+"\">\n");
					}
					else
					{
						tempBodyWriter.write("<Worksheet ss:Name=\""+getSheetName(jasperPrint.getName())+"\">\n");
					}
					tempBodyWriter.write("<Table>\n");

					// we need to count all sheets generated for all exported documents
					sheetIndex++;

					/*
					 * Make a pass and calculate the X cuts for all pages on this sheet.
					 * The Y cuts can be calculated as each page is exported.
					 */
					CutsInfo xCuts =
						JRGridLayout.calculateXCuts(
							nature, pages, startPageIndex, endPageIndex,
							jasperPrint.getPageWidth(), globalOffsetX
							);
					//clear the filter's internal cache that might have built up
					if (filter instanceof ResetableExporterFilter)
					{
						((ResetableExporterFilter)filter).reset();
					}
					int startRow = 0;

					for(int pageIndex = startPageIndex; pageIndex <= endPageIndex; pageIndex++)
					{
						if (Thread.interrupted())
						{
							throw new JRException("Current thread interrupted.");
						}
						JRPrintPage page = (JRPrintPage)pages.get(pageIndex);

						startRow = exportPage(page, xCuts, startRow, null, pageIndex == startPageIndex);
					}

//					if (isRemoveEmptySpaceBetweenColumns)
//					{
//						//FIXME: to remove empty columns when isRemoveEmptySpaceBetweenColumns
//						//removeEmptyColumns(xCuts);
//					}
					tempBodyWriter.write("</Table>\n");
					closeWorksheet();
				}
			}

		}

		tempBodyWriter.flush();
		tempStyleWriter.flush();

		tempBodyWriter.close();
		tempStyleWriter.close();


		/*   */
		XmlssContentBuilder xmlssContentBuilder =
			new XmlssContentBuilder(
				writer,
				tempStyleWriter,
				tempBodyWriter
				);
		xmlssContentBuilder.build();

	}

	/**
	 *
	 * @return the number of rows added.
	 */
	protected int exportPage(JRPrintPage page, CutsInfo xCuts, int startRow, JRPrintElementIndex frameIndex, boolean isNewSheet) throws JRException
	{
		try
		{
			JRGridLayout layout =
				new JRGridLayout(
					nature,
					page.getElements(),
					jasperPrint.getPageWidth(),
					jasperPrint.getPageHeight(),
					globalOffsetX,
					globalOffsetY,
					xCuts
					);

			JRExporterGridCell grid[][] = layout.getGrid();

			boolean createXCuts = (xCuts == null);

			if (createXCuts)
			{
				xCuts = layout.getXCuts();
			}

			CutsInfo yCuts = layout.getYCuts();

			int skippedRows = 0;
			int rowIndex = startRow;

			XmlssTableBuilder tableBuilder = frameIndex == null
			? new XmlssTableBuilder(reportIndex, pageIndex, tempBodyWriter, tempStyleWriter)
			: new XmlssTableBuilder(frameIndex.toString(), tempBodyWriter, tempStyleWriter);

			if(isNewSheet)
			{
				buildColumns(xCuts, tableBuilder);
			}

			for(int y = 0; y < grid.length; y++)
			{
				rowIndex = y - skippedRows + startRow;

				//if number of rows is too large a new sheet is created and populated with remaining rows
				if(maxRowsPerSheet > 0 && rowIndex >= maxRowsPerSheet)
				{
					tableBuilder.buildTableFooter();
					closeWorksheet();
					tempBodyWriter.write("<Worksheet ss:Name=\""+getSheetName(currentSheetName)+"\">\n");
					tableBuilder.buildTableHeader();
					buildColumns(xCuts, tableBuilder);
					startRow = 0;
					rowIndex = 0;
					skippedRows = y;
				}

				if (
					yCuts.isCutNotEmpty(y)
					|| ((!isRemoveEmptySpaceBetweenRows || yCuts.isCutSpanned(y))
					&& !isCollapseRowSpan)
					)
				{
					JRExporterGridCell[] gridRow = grid[y];

					int emptyCellColSpan = 0;
					int emptyCellRowSpan = 0;
					int emptyCellWidth = 0;
					int rowHeight = isCollapseRowSpan ? JRGridLayout.getMaxRowHeight(gridRow) : JRGridLayout.getRowHeight(gridRow);

					tableBuilder.buildRowHeader(rowIndex, rowHeight);

					int emptyCols = 0;
					for(int colIndex = 0; colIndex < gridRow.length; colIndex++)
					{
						emptyCols += (isRemoveEmptySpaceBetweenColumns && (!(xCuts.isCutNotEmpty(colIndex) || xCuts.isCutSpanned(colIndex))) ? 1 : 0);

						JRExporterGridCell gridCell = gridRow[colIndex];
						if(gridCell.getWrapper() != null)
						{
							if (emptyCellColSpan > 0)
							{
								tableBuilder.buildEmptyCell(emptyCellColSpan, emptyCellRowSpan);
								emptyCellColSpan = 0;
								emptyCellWidth = 0;
							}

							JRPrintElement element = gridCell.getWrapper().getElement();

//							if (element instanceof JRPrintLine)
//							{
//								//exportLine((JRPrintLine)element, gridCell, colIndex, rowIndex);
//							}
//							else if (element instanceof JRPrintRectangle)
//							{
//								//exportRectangle((JRPrintRectangle)element, gridCell, colIndex, rowIndex);
//							}
//							else if (element instanceof JRPrintEllipse)
//							{
//								//exportRectangle((JRPrintEllipse)element, gridCell, colIndex, rowIndex);
//							}
//							else if (element instanceof JRPrintImage)
//							{
//								//exportImage((JRPrintImage) element, gridCell, colIndex, rowIndex, emptyCols);
//							}
							if (element instanceof JRPrintText)
							{
								exportText(tableBuilder, (JRPrintText)element, gridCell);
							}
//							else if (element instanceof JRPrintFrame)
//							{
//								//exportFrame(tableBuilder, (JRPrintFrame)element, gridCell);
//							}

							colIndex += gridCell.getColSpan() - 1;
						}
						else
						{
							emptyCellColSpan++;
							emptyCellWidth += gridCell.getWidth();
						}
					}

					if (emptyCellColSpan > 0)
					{
						tableBuilder.buildEmptyCell(emptyCellColSpan, emptyCellRowSpan);
						emptyCellColSpan = 0;
						emptyCellWidth = 0;
					}

					tableBuilder.buildRowFooter();
					//increment row index to return proper value
					++rowIndex;
				}
				else
				{
					skippedRows++;
				}
			}

//			if (createXCuts && isRemoveEmptySpaceBetweenColumns)
//			{
//				//FIXME: to remove empty columns when isRemoveEmptySpaceBetweenColumns
////				removeEmptyColumns(xCuts);
//			}

			if (progressMonitor != null)
			{
				progressMonitor.afterPageExport();
			}

			// Return the number of rows added
			return rowIndex;
		}
		catch (IOException e)
		{
			throw new JRException(e);
		}
	}

	/**
	 *
	 */
	protected void exportText(XmlssTableBuilder tableBuilder, JRPrintText text, JRExporterGridCell gridCell) throws IOException
	{
		JRStyledText styledText = getStyledText(text);
		String pattern = getConvertedPattern(getTextValue(text,styledText.getText()));
		int colspan = gridCell.getColSpan(), rowspan = gridCell.getRowSpan();

		String formula = text.getPropertiesMap().getProperty(JRAbstractExporter.PROPERTY_CELL_FORMULA);

		//FIXME: transfer the font properties to the cell style
		tableBuilder.buildCellHeader(styleCache.getCellStyle(text, gridCell.getBackcolor(), pattern, isFontSizeFixEnabled, defaultFont, fontMap),
				colspan,
				rowspan,
				getHyperlinkURL(text),
				text.getHyperlinkTooltip(),
				formula
				);


		int textLength = 0;

		if (styledText != null)
		{
			textLength = styledText.length();
		}

		tempBodyWriter.write(" <ss:Data ss:Type=\""+getType(getTextValue(text,styledText.getText()))+"\" xmlns=\"http://www.w3.org/TR/REC-html40\">");

		//FIXME: when the font properties will be transfered to the cell style, uncomment this
//		if(JRCommonText.MARKUP_NONE.equals(text.getMarkup()))
//		{
//			tempBodyWriter.write((JRStringUtil.xmlEncode(styledText.getText())).replaceAll("\n", "&#10;"));
//		}
//		else if (textLength > 0)
		if (textLength > 0)
		{
			exportStyledText(styledText);
		}

		tempBodyWriter.write(" </ss:Data>\n");

		tableBuilder.buildCellFooter();
	}


	/**
	 *
	 */
	protected void exportStyledText(JRStyledText styledText) throws IOException
	{
		String text = styledText.getText();
		int runLimit = 0;

		AttributedCharacterIterator iterator = styledText.getAttributedString().getIterator();

		while(runLimit < styledText.length() && (runLimit = iterator.getRunLimit()) <= styledText.length())
		{
			exportStyledTextRun(iterator.getAttributes(), text.substring(iterator.getIndex(), runLimit));

			iterator.setIndex(runLimit);
		}
	}


	/**
	 *
	 */
	protected void exportStyledTextRun(Map attributes, String text) throws IOException
	{
		boolean hasFont = false;
		boolean isBold = false;
		boolean isItalic = false;
		boolean isStrikethrough = false;
		boolean isUnderline = false;
		boolean isSubscript = false;
		boolean isSuperscript = false;

		if (TextAttribute.WEIGHT_BOLD.equals(attributes.get(TextAttribute.WEIGHT)))
		{
			isBold = true;
			tempBodyWriter.write("<B>");
		}
		if (TextAttribute.POSTURE_OBLIQUE.equals(attributes.get(TextAttribute.POSTURE)))
		{
			isItalic = true;
			tempBodyWriter.write("<I>");
		}
		if (TextAttribute.UNDERLINE_ON.equals(attributes.get(TextAttribute.UNDERLINE)))
		{
			isUnderline = true;
			tempBodyWriter.write("<U>");
		}
		if (TextAttribute.STRIKETHROUGH_ON.equals(attributes.get(TextAttribute.STRIKETHROUGH)))
		{
			isStrikethrough = true;
			tempBodyWriter.write("<S>");
		}

		if (TextAttribute.SUPERSCRIPT_SUPER.equals(attributes.get(TextAttribute.SUPERSCRIPT)))
		{
			isSuperscript = true;
			tempBodyWriter.write("<Sup>");
		}
		else if (TextAttribute.SUPERSCRIPT_SUB.equals(attributes.get(TextAttribute.SUPERSCRIPT)))
		{
			isSubscript = true;
			tempBodyWriter.write("<Sub>");
		}

//		String fontFamilyAttr = (String)attributes.get(TextAttribute.FAMILY);
//		String fontFamily = (fontMap != null && fontMap.containsKey(fontFamilyAttr))
//			? (String) fontMap.get(fontFamilyAttr)
//			: fontFamilyAttr;
//
//		if(fontFamily != null)
//		{
//			hasFont = true;
//			startFontTag();
//			tempBodyWriter.write(" x:Family=\"" + fontFamily+"\"");
//		}

		Font font = (Font)attributes.get(TextAttribute.FONT);
		if(font != null)
		{
			String fontFace = font.getFontName();
			if(fontFace != null)
			{
				if(!hasFont)
				{
					hasFont = true;
					startFontTag();
				}
				tempBodyWriter.write(" html:Face=\"" + fontFace +"\"");
			}

			int size = font.getSize();
			if(size > 0)
			{
				if(!hasFont)
				{
					hasFont = true;
					startFontTag();
				}
				tempBodyWriter.write(" html:Size=\"" + size +"\"");
			}
		}
		else
		{
			//FIXMEXMLSS: how to distinguish between font name and font family; 
			// now it's possible thanks to FONT_NAME attribute;
			// see other exporters for details about font mapping
			String fontFamilyAttr = (String)attributes.get(TextAttribute.FAMILY);
			String fontFamily = (fontMap != null && fontMap.containsKey(fontFamilyAttr))
				? (String) fontMap.get(fontFamilyAttr)
				: fontFamilyAttr;
			if(fontFamily != null)
			{
				if(!hasFont)
				{
					hasFont = true;
					startFontTag();
				}
				tempBodyWriter.write(" html:Face=\"" + fontFamily +"\"");
			}

			Number size = (Number)attributes.get(TextAttribute.SIZE);
			if(size != null)
			{
				if(! hasFont)
				{
					hasFont = true;
					startFontTag();
				}
				tempBodyWriter.write(" html:Size=\"" + size +"\"");
			}
		}

		Color forecolor = (Color)attributes.get(TextAttribute.FOREGROUND);
		if(forecolor != null && !forecolor.equals(Color.BLACK))
		{
			if(!hasFont)
			{
				hasFont =true;
				startFontTag();
			}
			tempBodyWriter.write(" html:Color=\"#" + JRColorUtil.getColorHexa(forecolor)+"\"");
		}

		if(hasFont)
		{
			tempBodyWriter.write(">");
		}

		if (text != null)
		{
			tempBodyWriter.write((JRStringUtil.xmlEncode(text)).replaceAll("\n", "&#10;"));
		}

		if(hasFont)
		{
			tempBodyWriter.write("</Font>");
		}

		if(isSubscript)
		{
			tempBodyWriter.write("</Sub>");
		}

		if(isSuperscript)
		{
			tempBodyWriter.write("</Sup>");
		}

		if(isStrikethrough)
		{
			tempBodyWriter.write("</S>");
		}

		if(isUnderline)
		{
			tempBodyWriter.write("</U>");
		}

		if(isItalic)
		{
			tempBodyWriter.write("</I>");
		}

		if(isBold)
		{
			tempBodyWriter.write("</B>");
		}
	}

	/**
	 *
	 */
	protected JRPrintElementIndex getElementIndex(JRExporterGridCell gridCell)
	{
		JRPrintElementIndex imageIndex =
			new JRPrintElementIndex(
					reportIndex,
					pageIndex,
					gridCell.getWrapper().getAddress()
					);
		return imageIndex;
	}


	/**
	 *
	 */
	public static JRPrintElementIndex getPrintElementIndex(String imageName)
	{
		if (!imageName.startsWith(IMAGE_NAME_PREFIX))
		{
			throw new JRRuntimeException("Invalid image name: " + imageName);
		}

		return JRPrintElementIndex.parsePrintElementIndex(imageName.substring(IMAGE_NAME_PREFIX_LEGTH));
	}


	/**
	 *
	 */
	protected void exportFrame(XmlssTableBuilder tableBuilder, JRPrintFrame frame, JRExporterGridCell gridCell) throws IOException
	{
		tableBuilder.buildCellHeader(styleCache.getCellStyle(frame, gridCell.getBackcolor(), null, isFontSizeFixEnabled, defaultFont, fontMap), gridCell.getColSpan(), gridCell.getRowSpan(), null, null, null);

//		boolean appendBackcolor =
//			frame.getModeValue() == ModeEnum.OPAQUE
//			&& (backcolor == null || frame.getBackcolor().getRGB() != backcolor.getRGB());
//
//		if (appendBackcolor)
//		{
//			setBackcolor(frame.getBackcolor());
//		}
//		try
//		{
////			JRGridLayout layout = gridCell.getLayout();
////			JRPrintElementIndex frameIndex =
////				new JRPrintElementIndex(
////						reportIndex,
////						pageIndex,
////						gridCell.getWrapper().getAddress()
////						);
//			//FIXME: make it functional
////			exportPage(layout, frameIndex);
//		}
//		finally
//		{
//			if (appendBackcolor)
//			{
//				restoreBackcolor();
//			}
//		}

		tableBuilder.buildCellFooter();
	}

	/**
	 *
	 */
	protected void setBackcolor(Color color)
	{
		backcolorStack.addLast(backcolor);

		backcolor = color;
	}


	protected void restoreBackcolor()
	{
		backcolor = (Color) backcolorStack.removeLast();
	}

	protected void writeHyperlink(JRPrintHyperlink link) throws IOException
	{
		String href = getHyperlinkURL(link);
		if(link != null)
		{
			tempBodyWriter.write(" ss:Href=\"");
			tempBodyWriter.write(href);
			tempBodyWriter.write("\"");

			if (link.getHyperlinkTooltip() != null)
			{
				tempBodyWriter.write(" x:HRefScreenTip=\"");
				tempBodyWriter.write(JRStringUtil.xmlEncode(link.getHyperlinkTooltip()));
				tempBodyWriter.write("\"");
			}

			tempBodyWriter.write(">");

		}
	}

	protected String getHyperlinkURL(JRPrintHyperlink link)
	{
		//FIXME: find the appropriate way to write the local anchor and local page hyperlinks
		String href = null;
		JRHyperlinkProducer customHandler = getCustomHandler(link);
		if (customHandler == null)
		{
			switch(link.getHyperlinkTypeValue())
			{
				case REFERENCE :
				{
					if (link.getHyperlinkReference() != null)
					{
						href = link.getHyperlinkReference();
					}
					break;
				}
				case LOCAL_ANCHOR :
				{
					if (link.getHyperlinkAnchor() != null)
					{
						href = "#" + link.getHyperlinkAnchor();
					}
					break;
				}
				case LOCAL_PAGE :
				{
					if (link.getHyperlinkPage() != null)
					{
						href = "#" + JR_PAGE_ANCHOR_PREFIX + reportIndex + "_" + link.getHyperlinkPage().toString();
					}
					break;
				}
				case REMOTE_ANCHOR :
				{
					if (
						link.getHyperlinkReference() != null &&
						link.getHyperlinkAnchor() != null
						)
					{
						href = link.getHyperlinkReference() + "#" + link.getHyperlinkAnchor();
					}
					break;
				}
				case REMOTE_PAGE :
				{
					if (
						link.getHyperlinkReference() != null &&
						link.getHyperlinkPage() != null
						)
					{
						href = link.getHyperlinkReference() + "#" + JR_PAGE_ANCHOR_PREFIX + "0_" + link.getHyperlinkPage().toString();
					}
					break;
				}
				case NONE :
				default :
				{
					break;
				}
			}
		}
		else
		{
			href = customHandler.getHyperlink(link);
		}

		return href;
	}


	protected void setParameters()
	{
		isOnePagePerSheet =
			getBooleanParameter(
				JRXlsAbstractExporterParameter.IS_ONE_PAGE_PER_SHEET,
				JRXlsAbstractExporterParameter.PROPERTY_ONE_PAGE_PER_SHEET,
				false
				);

		isRemoveEmptySpaceBetweenRows =
			getBooleanParameter(
				JRXlsAbstractExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS,
				JRXlsAbstractExporterParameter.PROPERTY_REMOVE_EMPTY_SPACE_BETWEEN_ROWS,
				false
				);

		isRemoveEmptySpaceBetweenColumns =
			getBooleanParameter(
				JRXlsAbstractExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_COLUMNS,
				JRXlsAbstractExporterParameter.PROPERTY_REMOVE_EMPTY_SPACE_BETWEEN_COLUMNS,
				false
				);

		isWhitePageBackground =
			getBooleanParameter(
				JRXlsAbstractExporterParameter.IS_WHITE_PAGE_BACKGROUND,
				JRXlsAbstractExporterParameter.PROPERTY_WHITE_PAGE_BACKGROUND,
				false
				);

		Boolean isAutoDetectCellTypeParameter = (Boolean)parameters.get(JRXlsAbstractExporterParameter.IS_AUTO_DETECT_CELL_TYPE);
		if (isAutoDetectCellTypeParameter != null)
		{
			isAutoDetectCellType = isAutoDetectCellTypeParameter.booleanValue();
		}

		isDetectCellType =
			getBooleanParameter(
				JRXlsAbstractExporterParameter.IS_DETECT_CELL_TYPE,
				JRXlsAbstractExporterParameter.PROPERTY_DETECT_CELL_TYPE,
				false
				);

		isFontSizeFixEnabled =
			getBooleanParameter(
				JRXlsAbstractExporterParameter.IS_FONT_SIZE_FIX_ENABLED,
				JRXlsAbstractExporterParameter.PROPERTY_FONT_SIZE_FIX_ENABLED,
				false
				);

		isIgnoreGraphics =
			getBooleanParameter(
				JRXlsAbstractExporterParameter.IS_IGNORE_GRAPHICS,
				JRXlsAbstractExporterParameter.PROPERTY_IGNORE_GRAPHICS,
				false
				);

		isCollapseRowSpan =
			getBooleanParameter(
				JRXlsAbstractExporterParameter.IS_COLLAPSE_ROW_SPAN,
				JRXlsAbstractExporterParameter.PROPERTY_COLLAPSE_ROW_SPAN,
				false
				);

		isIgnoreCellBorder =
			getBooleanParameter(
				JRXlsAbstractExporterParameter.IS_IGNORE_CELL_BORDER,
				JRXlsAbstractExporterParameter.PROPERTY_IGNORE_CELL_BORDER,
				false
				);

		sheetNames = (String[])parameters.get(JRXlsAbstractExporterParameter.SHEET_NAMES);

		fontMap = (Map) parameters.get(JRExporterParameter.FONT_MAP);
		formatPatternsMap = (Map)getParameter(JRXlsExporterParameter.FORMAT_PATTERNS_MAP);

		maxRowsPerSheet =
			getIntegerParameter(
				JRXlsAbstractExporterParameter.MAXIMUM_ROWS_PER_SHEET,
				JRXlsAbstractExporterParameter.PROPERTY_MAXIMUM_ROWS_PER_SHEET,
				0
				);

		isIgnorePageMargins =
			getBooleanParameter(
				JRExporterParameter.IGNORE_PAGE_MARGINS,
				JRExporterParameter.PROPERTY_IGNORE_PAGE_MARGINS,
				false
				);

		encoding =
			getStringParameterOrDefault(
				JRExporterParameter.CHARACTER_ENCODING,
				JRExporterParameter.PROPERTY_CHARACTER_ENCODING
				);

		setHyperlinkProducerFactory();

	}

	protected void startFontTag() throws IOException
	{
		tempBodyWriter.write("<Font");
	}

	protected void endFontTag() throws IOException
	{
		tempBodyWriter.write("</Font>");
	}
	/**
	 *
	 */
	private String getSheetName(String sheetName)
	{
		currentSheetName = sheetName;

		// sheet names must be unique
		if(!sheetNamesMap.containsKey(sheetName))
		{
			// first time this sheet name is found;
			sheetNamesMap.put(sheetName, Integer.valueOf(1));
			return sheetName;
		}

		int currentIndex = ((Integer)sheetNamesMap.get(sheetName)).intValue() + 1;
		sheetNamesMap.put(sheetName, Integer.valueOf(currentIndex));

		return sheetName + " " + currentIndex;
	}

	protected void buildColumns(CutsInfo xCuts, XmlssTableBuilder tableBuilder)
	{
		for(int col = 0; col < xCuts.size() - 1; col++)
		{
			if (!isRemoveEmptySpaceBetweenColumns || (xCuts.isCutNotEmpty(col) || xCuts.isCutSpanned(col)))
			{
				int width = xCuts.getCut(col + 1) - xCuts.getCut(col);
				try {
					tableBuilder.buildColumnTag(col+1, width);
				} catch (IOException e) {
					throw new JRRuntimeException(e);
				}
			}
		}
	}

	String getType(TextValue textValue)
	{
		String type = "String";
		if(textValue instanceof NumberTextValue)
		{
			type = "Number";
		}
		else if(textValue instanceof DateTextValue)
		{
			type = "DateTime";
		}
		else if(textValue instanceof BooleanTextValue)
		{
			type = "Boolean";
		}
		return type;
	}

	/**
	 * This method is intended to modify a given format pattern so to include
	 * only the accepted proprietary format characters. The resulted pattern
	 * will possibly truncate the original pattern
	 * @param pattern
	 * @return pattern converted to accepted proprietary formats
	 */
	String getConvertedPattern(TextValue textValue)
	{
		String pattern = "General";
		if(textValue instanceof NumberTextValue && ((NumberTextValue)textValue).getPattern() != null)
		{
			pattern = ((NumberTextValue)textValue).getPattern();
		}
		else if(textValue instanceof DateTextValue && ((DateTextValue)textValue).getPattern() != null)
		{
			pattern = ((DateTextValue)textValue).getPattern();
		}
		if (formatPatternsMap != null && formatPatternsMap.containsKey(pattern))
		{
			return (String) formatPatternsMap.get(pattern);
		}
		return pattern;
	}

	private void closeWorksheet() throws IOException
	{
		tempBodyWriter.write("<x:WorksheetOptions>\n");
		tempBodyWriter.write(" <x:PageSetup>\n");
		tempBodyWriter.write("  <x:Layout x:Orientation=\"" +PAGE_LAYOUT[pageOrientation.getValue()]+"\"/>\n");
		tempBodyWriter.write(" </x:PageSetup>\n");
		tempBodyWriter.write("</x:WorksheetOptions>\n");
		tempBodyWriter.write("</Worksheet>\n");
	}

	/**
	 *
	 */
	protected String getExporterKey()
	{
		return XMLSS_EXPORTER_KEY;
	}
}

