<commit_before>/*******************************************************************************
 * Copyright (c) 2004, 2009 Actuate Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * Contributors:
 *  Actuate Corporation  - initial API and implementation
 *******************************************************************************/

package org.eclipse.birt.report.engine.emitter.excel;

import java.awt.Color;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.eclipse.birt.report.engine.content.IHyperlinkAction;
import org.eclipse.birt.report.engine.content.IReportContent;
import org.eclipse.birt.report.engine.css.engine.value.css.CSSConstants;
import org.eclipse.birt.report.engine.emitter.XMLEncodeUtil;
import org.eclipse.birt.report.engine.emitter.XMLWriter;
import org.eclipse.birt.report.engine.emitter.excel.layout.ExcelContext;
import org.eclipse.birt.report.engine.emitter.excel.layout.ExcelLayoutEngine;
import org.eclipse.birt.report.model.api.ReportDesignHandle;
import org.eclipse.birt.report.model.api.core.IModuleModel;

public class ExcelXmlWriter implements IExcelWriter
{

	private boolean isRTLSheet = false; // bidi_acgc added
	public static final int rightToLeftisTrue = 1; // bidi_acgc added
	private final XMLWriterXLS writer = new XMLWriterXLS( );

	public XMLWriterXLS getWriter( )
	{
		return writer;
	}

	private String pageHeader, pageFooter, orientation;
	private int sheetIndex = 1;

	static class XLSEncodeUtil extends XMLEncodeUtil
	{

		protected static final char[] XLS_TEXT_ENCODE = new char[]{'&', '<',
				'\r', '\n'};

		static String encodeXLSText( String s )
		{
			char[] chars = s.toCharArray( );
			int length = chars.length;
			int index = testEscape( chars, XLS_TEXT_ENCODE );
			if ( index >= length )
			{
				return s;
			}

			StringBuilder sb = new StringBuilder( 2 * length );
			sb.append( chars, 0, index );

			while ( index < length )
			{
				char c = chars[index++];
				if ( Character.isHighSurrogate( c ) )
				{
					index += decodeSurrogate( c, chars, index, sb );
				}
				else if ( isValidCodePoint( c ) )
				{
					if ( c == '&' )
					{
						sb.append( "&amp;" );
					}
					else if ( c == '<' )
					{
						sb.append( "&lt;" );
					}
					else if ( c == '\r' )
					{
						if ( index < length )
						{
							char nc = chars[index];
							if ( nc == '\n' )
							{
								index++;
							}
						}
						sb.append( "&#10;" );
					}
					else if ( c == '\n' )
					{
						sb.append( "&#10;" );
					}
					else
					{
						sb.append( c );
					}
				}
				else
				{
					logger.log( Level.WARNING, MESSAGE_INVALID_CHARACTER,
							Integer.valueOf( c ) );
				}
			}
			return sb.toString( );
		}
	}

	public class XMLWriterXLS extends XMLWriter
	{
		protected String encodeText( String text )
		{
			return XLSEncodeUtil.encodeXLSText( text );
		}
	}

	protected static Logger logger = Logger.getLogger( ExcelXmlWriter.class
			.getName( ) );

	ExcelContext context = null;

	public ExcelXmlWriter( OutputStream out , ExcelContext context )
	{
		this( out, "UTF-8" , context);
	}

	public ExcelXmlWriter( OutputStream out )
	{
		writer.open( out, "UTF-8" );
	}


	public ExcelXmlWriter( OutputStream out, String encoding, ExcelContext context )
	{
		this.context = context;
		writer.open( out, encoding );
	}

	/**
	 * @author bidi_acgc
	 * @param isRTLSheet:
	 *            represents the direction of the excel sheet.
	 */
	public ExcelXmlWriter( OutputStream out, boolean isRTLSheet )
	{
		this.isRTLSheet = isRTLSheet;
		writer.open( out, "UTF-8" );
	}

	/**
	 * @author bidi_acgc
	 * @param orientation
	 * @param pageFooter
	 * @param pageHeader
	 * @param isRTLSheet
	 *            : represents the direction of the excel sheet.
	 */
	public ExcelXmlWriter( OutputStream out, ExcelContext context,
			boolean isRTLSheet )
	{
		this( out, "UTF-8", context );
		this.isRTLSheet = isRTLSheet;
	}

	/**
	 * @author bidi_acgc
	 * @param isRTLSheet:
	 *            represents the direction of the excel sheet.
	 */
	public ExcelXmlWriter( OutputStream out, String encoding,
			ExcelContext context, boolean isRTLSheet )
	{
		this.context = context;
		this.isRTLSheet = isRTLSheet;
		writer.open( out, encoding );
	}

	private void writeDocumentProperties( IReportContent reportContent )
	{
		if ( reportContent == null )
		{
			return;
		}
		ReportDesignHandle reportDesign = reportContent.getDesign( )
		.getReportDesign( );
		writer.openTag( "DocumentProperties" );
		writer.attribute( "xmlns", "urn:schemas-microsoft-com:office:office" );
		writer.openTag( "Author" );
		writer
		.text( reportDesign
				.getStringProperty( IModuleModel.AUTHOR_PROP ) );
		writer.closeTag( "Author" );
		writer.openTag( "Title" );
		writer.text( reportDesign.getStringProperty( IModuleModel.TITLE_PROP ) );
		writer.closeTag( "Title" );
		writer.openTag( "Description" );
		writer.text( reportDesign
				.getStringProperty( IModuleModel.DESCRIPTION_PROP ) );
		writer.closeTag( "Description" );
		writer.closeTag( "DocumentProperties" );
	}

	// If possible, we can pass a format according the data type
	private void writeText( Data d )
	{
		int type = d.getDataType( );
		StyleEntry style = d.getStyle( );
		writeText( type, d.getValue( ), style );
	}

	private void writeText( int type, Object value,
			StyleEntry style )
	{
		String txt = ExcelUtil.format( value, type );
		writer.openTag( "Data" );
		if ( type == SheetData.NUMBER )
		{
			if ( ExcelUtil.isNaN( value ) || ExcelUtil.isBigNumber( value )
					|| ExcelUtil.isInfinity( value ) )
			{
				writer.attribute( "ss:Type", "String" );
			}
			else
			{
				writer.attribute( "ss:Type", "Number" );
			}
		}
		else if ( type == SheetData.DATE )
		{
			writer.attribute( "ss:Type", "DateTime" );
		}
		else
		{
			writer.attribute( "ss:Type", "String" );
		}

		if ( style != null )
		{
			String textTransform = (String) style
					.getProperty( StyleConstant.TEXT_TRANSFORM );
			if ( CSSConstants.CSS_CAPITALIZE_VALUE.equalsIgnoreCase( textTransform ) )
			{
				txt = capitalize( txt );
			}
			else if ( CSSConstants.CSS_UPPERCASE_VALUE.equalsIgnoreCase( textTransform ) )
			{
				txt = txt.toUpperCase( );
			}
			else if ( CSSConstants.CSS_LOWERCASE_VALUE.equalsIgnoreCase( textTransform ) )
			{
				txt = txt.toLowerCase( );
			}
		}

		writer.text( txt );

		writer.closeTag( "Data" );
	}

	private String capitalize( String text )
	{
		boolean capitalizeNextChar = true;
		char[] array = text.toCharArray( );
		for ( int i = 0; i < array.length; i++ )
		{
			char c = text.charAt( i );
			if ( c == ' ' || c == '\n' || c == '\r' )
				capitalizeNextChar = true;
			else if ( capitalizeNextChar )
			{
				array[i] = Character.toUpperCase( array[i] );
				capitalizeNextChar = false;
			}
		}
		return new String( array );
	}

	public void startRow( double rowHeight )
	{
		writer.openTag( "Row" );
		writer.attribute( "ss:AutoFitHeight", 0 );
		if ( rowHeight > 0 )
		{
			writer.attribute( "ss:Height", rowHeight );
		}
	}

	public void endRow( )
	{
		writer.closeTag( "Row" );
	}

	private void startCell( int cellIndex, int colspan, int rowspan,
			int styleId, HyperlinkDef hyperLink ,BookmarkDef linkedBookmark)
	{
		writer.openTag( "Cell" );
		writer.attribute( "ss:Index", cellIndex );
		if ( styleId > 0 )
		{
			writer.attribute( "ss:StyleID", styleId );
		}

		if ( hyperLink != null )
		{
			String urlAddress = hyperLink.getUrl( );
			if ( hyperLink.getType( ) == IHyperlinkAction.ACTION_BOOKMARK )
			{
				if ( linkedBookmark != null )
					urlAddress = "#" + linkedBookmark.getValidName( );
				else
				{
					logger.log( Level.WARNING, "The bookmark: {" + urlAddress
							+ "} is not defined!" );
				}
			}
			if ( urlAddress != null && urlAddress.length( ) >= 255 )
			{
				logger.log( Level.WARNING, "The URL: {" + urlAddress
						+ "} is too long!" );
				urlAddress = urlAddress.substring( 0, 254 );
			}
			writer.attribute( "ss:HRef", urlAddress );
		}
		if ( colspan > 0 )
		{
			writer.attribute( "ss:MergeAcross", colspan );
		}
		if ( rowspan > 0 )
		{
			writer.attribute( "ss:MergeDown", rowspan );
		}
	}

	public void writeDefaultCell( Data d )
	{
		writer.openTag( "Cell" );

		if ( d.getStyleId( ) != 0 )
		{
			writer.attribute( "ss:StyleID", d.getStyleId( ) );
		}

		writeText( d );
		writer.closeTag( "Cell" );
	}

	public void outputData( SheetData sheetData )
	{
		StyleEntry style = sheetData.getStyle( );
		int column = sheetData.span.getCol( );
		int colSpan = sheetData.span.getColSpan( );
		int rowSpan = sheetData.getRowSpan( );
		int styleId = sheetData.styleId;
		int type = sheetData.getDataType( );
		if ( type == SheetData.IMAGE )
		{
			outputData( Data.STRING, ExcelLayoutEngine.EMPTY, sheetData
					.getStyle( ), sheetData.getSpan( ).getCol( ), sheetData
					.getSpan( ).getColSpan( ), sheetData.getRowSpan( ),
					sheetData.getStyleId( ), null, null );
		}
		else
		{
			Data d = (Data) sheetData;
			Object value = d.getValue( );
			HyperlinkDef hyperLink = d.hyperLink;
			BookmarkDef linkedBookmark = d.getLinkedBookmark( );
			outputData( type, value, style, column, colSpan, rowSpan, styleId,
					hyperLink, linkedBookmark );
		}
	}

	public void outputData( int col, int row, int type, Object value )
	{
		outputData( type, value, null, col, 0, 0, -1, null, null );
	}

	private void outputData( int type, Object value,
			StyleEntry style, int column, int colSpan, int rowSpan,
			int styleId, HyperlinkDef hyperLink, BookmarkDef linkedBookmark )
	{
		startCell( column, colSpan, rowSpan, styleId, hyperLink, linkedBookmark );
		writeText( type, value, style );
		if(hyperLink != null && hyperLink.getToolTip( ) != null)
		{
			writeComments(hyperLink);
		}
		endCell( );
	}

	protected void writeComments(HyperlinkDef linkDef)
	{
		String toolTip = linkDef.getToolTip( );
		writer.openTag( "Comment" );
		writer.openTag( "ss:Data" );
		writer.attribute( "xmlns", "http://www.w3.org/TR/REC-html40" );
		writer.openTag( "Font" );
		//		writer.attribute( "html:Face", "Tahoma" );
		//		writer.attribute( "x:CharSet", "1" );
		//		writer.attribute( "html:Size", "8" );
		//		writer.attribute( "html:Color", "#000000" );
		writer.text( toolTip );
		writer.closeTag( "Font" );
		writer.closeTag( "ss:Data" );
		writer.closeTag( "Comment" );
	}

	private void endCell( )
	{
		writer.closeTag( "Cell" );
	}

	private void writeAlignment( String horizontal, String vertical,
			String direction, boolean wrapText )
	{
		writer.openTag( "Alignment" );

		if ( isValid( horizontal ) )
		{
			writer.attribute( "ss:Horizontal", horizontal );
		}

		if ( isValid( vertical ) )
		{
			writer.attribute( "ss:Vertical", vertical );
		}

		if ( isValid( direction ) )
		{
			if ( CSSConstants.CSS_RTL_VALUE.equals( direction ) )
				writer.attribute( "ss:ReadingOrder", "RightToLeft" );
			else
				writer.attribute( "ss:ReadingOrder", "LeftToRight" );
		}
		if(wrapText)
		{
			writer.attribute( "ss:WrapText", "1" );
		}

		writer.closeTag( "Alignment" );
	}

	private void writeBorder( String position, String lineStyle, String weight,
			Color color )
	{
		writer.openTag( "Border" );
		writer.attribute( "ss:Position", position );
		if ( isValid( lineStyle ) )
		{
			writer.attribute( "ss:LineStyle", lineStyle );
		}

		if ( isValid( weight ) )
		{
			writer.attribute( "ss:Weight", weight );
		}

		if ( color != null )
		{
			writer.attribute( "ss:Color", toString( color ) );
		}

		writer.closeTag( "Border" );
	}

	private void writeFont( String fontName, Integer size, Boolean bold,
			Boolean italic, Boolean strikeThrough, Boolean underline,
			Color color )
	{
		writer.openTag( "Font" );

		if ( isValid( fontName ) )
		{
			writer.attribute( "ss:FontName", fontName );
		}

		if ( size != null )
		{
			writer.attribute( "ss:Size", size );
		}

		if ( bold != null && bold )
		{
			writer.attribute( "ss:Bold", 1 );
		}

		if ( italic != null && italic )
		{
			writer.attribute( "ss:Italic", 1 );
		}

		if ( strikeThrough != null && strikeThrough )
		{
			writer.attribute( "ss:StrikeThrough", 1 );
		}

		if ( underline != null && underline )
		{
			writer.attribute( "ss:Underline", "Single" );
		}

		if ( color != null )
		{
			writer.attribute( "ss:Color", toString( color ) );
		}

		writer.closeTag( "Font" );
	}

	private void writeBackGroudColor( StyleEntry style )
	{
		Color bgColor = (Color) style
				.getProperty( StyleConstant.BACKGROUND_COLOR_PROP );
		if ( bgColor != null )
		{
			writer.openTag( "Interior" );
			writer.attribute( "ss:Color", bgColor );
			writer.attribute( "ss:Pattern", "Solid" );
			writer.closeTag( "Interior" );
		}
	}

	private boolean isValid( String value )
	{
		return !StyleEntry.isNull( value );
	}

	private void declareStyle( StyleEntry style, int id )
	{
		boolean wrapText = context.getWrappingText( );
		String whiteSpace = (String) style
				.getProperty( StyleConstant.WHITE_SPACE );
		if ( CSSConstants.CSS_NOWRAP_VALUE.equals( whiteSpace ) )
		{
			wrapText = false;
		}

		writer.openTag( "Style" );
		writer.attribute( "ss:ID", id );
		if ( style.getName( ) == StyleEntry.ENTRYNAME_HYPERLINK )<commit_msg>Fix problem in excel emitter:Color should be converted to string in ExcelXmlWriter.<commit_after>/*******************************************************************************
 * Copyright (c) 2004, 2009 Actuate Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * Contributors:
 *  Actuate Corporation  - initial API and implementation
 *******************************************************************************/

package org.eclipse.birt.report.engine.emitter.excel;

import java.awt.Color;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.eclipse.birt.report.engine.content.IHyperlinkAction;
import org.eclipse.birt.report.engine.content.IReportContent;
import org.eclipse.birt.report.engine.css.engine.value.css.CSSConstants;
import org.eclipse.birt.report.engine.emitter.XMLEncodeUtil;
import org.eclipse.birt.report.engine.emitter.XMLWriter;
import org.eclipse.birt.report.engine.emitter.excel.layout.ExcelContext;
import org.eclipse.birt.report.engine.emitter.excel.layout.ExcelLayoutEngine;
import org.eclipse.birt.report.model.api.ReportDesignHandle;
import org.eclipse.birt.report.model.api.core.IModuleModel;

public class ExcelXmlWriter implements IExcelWriter
{

	private boolean isRTLSheet = false; // bidi_acgc added
	public static final int rightToLeftisTrue = 1; // bidi_acgc added
	private final XMLWriterXLS writer = new XMLWriterXLS( );

	public XMLWriterXLS getWriter( )
	{
		return writer;
	}

	private String pageHeader, pageFooter, orientation;
	private int sheetIndex = 1;

	static class XLSEncodeUtil extends XMLEncodeUtil
	{

		protected static final char[] XLS_TEXT_ENCODE = new char[]{'&', '<',
				'\r', '\n'};

		static String encodeXLSText( String s )
		{
			char[] chars = s.toCharArray( );
			int length = chars.length;
			int index = testEscape( chars, XLS_TEXT_ENCODE );
			if ( index >= length )
			{
				return s;
			}

			StringBuilder sb = new StringBuilder( 2 * length );
			sb.append( chars, 0, index );

			while ( index < length )
			{
				char c = chars[index++];
				if ( Character.isHighSurrogate( c ) )
				{
					index += decodeSurrogate( c, chars, index, sb );
				}
				else if ( isValidCodePoint( c ) )
				{
					if ( c == '&' )
					{
						sb.append( "&amp;" );
					}
					else if ( c == '<' )
					{
						sb.append( "&lt;" );
					}
					else if ( c == '\r' )
					{
						if ( index < length )
						{
							char nc = chars[index];
							if ( nc == '\n' )
							{
								index++;
							}
						}
						sb.append( "&#10;" );
					}
					else if ( c == '\n' )
					{
						sb.append( "&#10;" );
					}
					else
					{
						sb.append( c );
					}
				}
				else
				{
					logger.log( Level.WARNING, MESSAGE_INVALID_CHARACTER,
							Integer.valueOf( c ) );
				}
			}
			return sb.toString( );
		}
	}

	public class XMLWriterXLS extends XMLWriter
	{
		protected String encodeText( String text )
		{
			return XLSEncodeUtil.encodeXLSText( text );
		}
	}

	protected static Logger logger = Logger.getLogger( ExcelXmlWriter.class
			.getName( ) );

	ExcelContext context = null;

	public ExcelXmlWriter( OutputStream out , ExcelContext context )
	{
		this( out, "UTF-8" , context);
	}

	public ExcelXmlWriter( OutputStream out )
	{
		writer.open( out, "UTF-8" );
	}


	public ExcelXmlWriter( OutputStream out, String encoding, ExcelContext context )
	{
		this.context = context;
		writer.open( out, encoding );
	}

	/**
	 * @author bidi_acgc
	 * @param isRTLSheet:
	 *            represents the direction of the excel sheet.
	 */
	public ExcelXmlWriter( OutputStream out, boolean isRTLSheet )
	{
		this.isRTLSheet = isRTLSheet;
		writer.open( out, "UTF-8" );
	}

	/**
	 * @author bidi_acgc
	 * @param orientation
	 * @param pageFooter
	 * @param pageHeader
	 * @param isRTLSheet
	 *            : represents the direction of the excel sheet.
	 */
	public ExcelXmlWriter( OutputStream out, ExcelContext context,
			boolean isRTLSheet )
	{
		this( out, "UTF-8", context );
		this.isRTLSheet = isRTLSheet;
	}

	/**
	 * @author bidi_acgc
	 * @param isRTLSheet:
	 *            represents the direction of the excel sheet.
	 */
	public ExcelXmlWriter( OutputStream out, String encoding,
			ExcelContext context, boolean isRTLSheet )
	{
		this.context = context;
		this.isRTLSheet = isRTLSheet;
		writer.open( out, encoding );
	}

	private void writeDocumentProperties( IReportContent reportContent )
	{
		if ( reportContent == null )
		{
			return;
		}
		ReportDesignHandle reportDesign = reportContent.getDesign( )
		.getReportDesign( );
		writer.openTag( "DocumentProperties" );
		writer.attribute( "xmlns", "urn:schemas-microsoft-com:office:office" );
		writer.openTag( "Author" );
		writer
		.text( reportDesign
				.getStringProperty( IModuleModel.AUTHOR_PROP ) );
		writer.closeTag( "Author" );
		writer.openTag( "Title" );
		writer.text( reportDesign.getStringProperty( IModuleModel.TITLE_PROP ) );
		writer.closeTag( "Title" );
		writer.openTag( "Description" );
		writer.text( reportDesign
				.getStringProperty( IModuleModel.DESCRIPTION_PROP ) );
		writer.closeTag( "Description" );
		writer.closeTag( "DocumentProperties" );
	}

	// If possible, we can pass a format according the data type
	private void writeText( Data d )
	{
		int type = d.getDataType( );
		StyleEntry style = d.getStyle( );
		writeText( type, d.getValue( ), style );
	}

	private void writeText( int type, Object value,
			StyleEntry style )
	{
		String txt = ExcelUtil.format( value, type );
		writer.openTag( "Data" );
		if ( type == SheetData.NUMBER )
		{
			if ( ExcelUtil.isNaN( value ) || ExcelUtil.isBigNumber( value )
					|| ExcelUtil.isInfinity( value ) )
			{
				writer.attribute( "ss:Type", "String" );
			}
			else
			{
				writer.attribute( "ss:Type", "Number" );
			}
		}
		else if ( type == SheetData.DATE )
		{
			writer.attribute( "ss:Type", "DateTime" );
		}
		else
		{
			writer.attribute( "ss:Type", "String" );
		}

		if ( style != null )
		{
			String textTransform = (String) style
					.getProperty( StyleConstant.TEXT_TRANSFORM );
			if ( CSSConstants.CSS_CAPITALIZE_VALUE.equalsIgnoreCase( textTransform ) )
			{
				txt = capitalize( txt );
			}
			else if ( CSSConstants.CSS_UPPERCASE_VALUE.equalsIgnoreCase( textTransform ) )
			{
				txt = txt.toUpperCase( );
			}
			else if ( CSSConstants.CSS_LOWERCASE_VALUE.equalsIgnoreCase( textTransform ) )
			{
				txt = txt.toLowerCase( );
			}
		}

		writer.text( txt );

		writer.closeTag( "Data" );
	}

	private String capitalize( String text )
	{
		boolean capitalizeNextChar = true;
		char[] array = text.toCharArray( );
		for ( int i = 0; i < array.length; i++ )
		{
			char c = text.charAt( i );
			if ( c == ' ' || c == '\n' || c == '\r' )
				capitalizeNextChar = true;
			else if ( capitalizeNextChar )
			{
				array[i] = Character.toUpperCase( array[i] );
				capitalizeNextChar = false;
			}
		}
		return new String( array );
	}

	public void startRow( double rowHeight )
	{
		writer.openTag( "Row" );
		writer.attribute( "ss:AutoFitHeight", 0 );
		if ( rowHeight > 0 )
		{
			writer.attribute( "ss:Height", rowHeight );
		}
	}

	public void endRow( )
	{
		writer.closeTag( "Row" );
	}

	private void startCell( int cellIndex, int colspan, int rowspan,
			int styleId, HyperlinkDef hyperLink ,BookmarkDef linkedBookmark)
	{
		writer.openTag( "Cell" );
		writer.attribute( "ss:Index", cellIndex );
		if ( styleId > 0 )
		{
			writer.attribute( "ss:StyleID", styleId );
		}

		if ( hyperLink != null )
		{
			String urlAddress = hyperLink.getUrl( );
			if ( hyperLink.getType( ) == IHyperlinkAction.ACTION_BOOKMARK )
			{
				if ( linkedBookmark != null )
					urlAddress = "#" + linkedBookmark.getValidName( );
				else
				{
					logger.log( Level.WARNING, "The bookmark: {" + urlAddress
							+ "} is not defined!" );
				}
			}
			if ( urlAddress != null && urlAddress.length( ) >= 255 )
			{
				logger.log( Level.WARNING, "The URL: {" + urlAddress
						+ "} is too long!" );
				urlAddress = urlAddress.substring( 0, 254 );
			}
			writer.attribute( "ss:HRef", urlAddress );
		}
		if ( colspan > 0 )
		{
			writer.attribute( "ss:MergeAcross", colspan );
		}
		if ( rowspan > 0 )
		{
			writer.attribute( "ss:MergeDown", rowspan );
		}
	}

	public void writeDefaultCell( Data d )
	{
		writer.openTag( "Cell" );

		if ( d.getStyleId( ) != 0 )
		{
			writer.attribute( "ss:StyleID", d.getStyleId( ) );
		}

		writeText( d );
		writer.closeTag( "Cell" );
	}

	public void outputData( SheetData sheetData )
	{
		StyleEntry style = sheetData.getStyle( );
		int column = sheetData.span.getCol( );
		int colSpan = sheetData.span.getColSpan( );
		int rowSpan = sheetData.getRowSpan( );
		int styleId = sheetData.styleId;
		int type = sheetData.getDataType( );
		if ( type == SheetData.IMAGE )
		{
			outputData( Data.STRING, ExcelLayoutEngine.EMPTY, sheetData
					.getStyle( ), sheetData.getSpan( ).getCol( ), sheetData
					.getSpan( ).getColSpan( ), sheetData.getRowSpan( ),
					sheetData.getStyleId( ), null, null );
		}
		else
		{
			Data d = (Data) sheetData;
			Object value = d.getValue( );
			HyperlinkDef hyperLink = d.hyperLink;
			BookmarkDef linkedBookmark = d.getLinkedBookmark( );
			outputData( type, value, style, column, colSpan, rowSpan, styleId,
					hyperLink, linkedBookmark );
		}
	}

	public void outputData( int col, int row, int type, Object value )
	{
		outputData( type, value, null, col, 0, 0, -1, null, null );
	}

	private void outputData( int type, Object value,
			StyleEntry style, int column, int colSpan, int rowSpan,
			int styleId, HyperlinkDef hyperLink, BookmarkDef linkedBookmark )
	{
		startCell( column, colSpan, rowSpan, styleId, hyperLink, linkedBookmark );
		writeText( type, value, style );
		if(hyperLink != null && hyperLink.getToolTip( ) != null)
		{
			writeComments(hyperLink);
		}
		endCell( );
	}

	protected void writeComments(HyperlinkDef linkDef)
	{
		String toolTip = linkDef.getToolTip( );
		writer.openTag( "Comment" );
		writer.openTag( "ss:Data" );
		writer.attribute( "xmlns", "http://www.w3.org/TR/REC-html40" );
		writer.openTag( "Font" );
		//		writer.attribute( "html:Face", "Tahoma" );
		//		writer.attribute( "x:CharSet", "1" );
		//		writer.attribute( "html:Size", "8" );
		//		writer.attribute( "html:Color", "#000000" );
		writer.text( toolTip );
		writer.closeTag( "Font" );
		writer.closeTag( "ss:Data" );
		writer.closeTag( "Comment" );
	}

	private void endCell( )
	{
		writer.closeTag( "Cell" );
	}

	private void writeAlignment( String horizontal, String vertical,
			String direction, boolean wrapText )
	{
		writer.openTag( "Alignment" );

		if ( isValid( horizontal ) )
		{
			writer.attribute( "ss:Horizontal", horizontal );
		}

		if ( isValid( vertical ) )
		{
			writer.attribute( "ss:Vertical", vertical );
		}

		if ( isValid( direction ) )
		{
			if ( CSSConstants.CSS_RTL_VALUE.equals( direction ) )
				writer.attribute( "ss:ReadingOrder", "RightToLeft" );
			else
				writer.attribute( "ss:ReadingOrder", "LeftToRight" );
		}
		if(wrapText)
		{
			writer.attribute( "ss:WrapText", "1" );
		}

		writer.closeTag( "Alignment" );
	}

	private void writeBorder( String position, String lineStyle, String weight,
			Color color )
	{
		writer.openTag( "Border" );
		writer.attribute( "ss:Position", position );
		if ( isValid( lineStyle ) )
		{
			writer.attribute( "ss:LineStyle", lineStyle );
		}

		if ( isValid( weight ) )
		{
			writer.attribute( "ss:Weight", weight );
		}

		if ( color != null )
		{
			writer.attribute( "ss:Color", toString( color ) );
		}

		writer.closeTag( "Border" );
	}

	private void writeFont( String fontName, Integer size, Boolean bold,
			Boolean italic, Boolean strikeThrough, Boolean underline,
			Color color )
	{
		writer.openTag( "Font" );

		if ( isValid( fontName ) )
		{
			writer.attribute( "ss:FontName", fontName );
		}

		if ( size != null )
		{
			writer.attribute( "ss:Size", size );
		}

		if ( bold != null && bold )
		{
			writer.attribute( "ss:Bold", 1 );
		}

		if ( italic != null && italic )
		{
			writer.attribute( "ss:Italic", 1 );
		}

		if ( strikeThrough != null && strikeThrough )
		{
			writer.attribute( "ss:StrikeThrough", 1 );
		}

		if ( underline != null && underline )
		{
			writer.attribute( "ss:Underline", "Single" );
		}

		if ( color != null )
		{
			writer.attribute( "ss:Color", toString( color ) );
		}

		writer.closeTag( "Font" );
	}

	private void writeBackGroudColor( StyleEntry style )
	{
		Color bgColor = (Color) style
				.getProperty( StyleConstant.BACKGROUND_COLOR_PROP );
		if ( bgColor != null )
		{
			writer.openTag( "Interior" );
			writer.attribute( "ss:Color", toString( bgColor ) );
			writer.attribute( "ss:Pattern", "Solid" );
			writer.closeTag( "Interior" );
		}
	}

	private boolean isValid( String value )
	{
		return !StyleEntry.isNull( value );
	}

	private void declareStyle( StyleEntry style, int id )
	{
		boolean wrapText = context.getWrappingText( );
		String whiteSpace = (String) style
				.getProperty( StyleConstant.WHITE_SPACE );
		if ( CSSConstants.CSS_NOWRAP_VALUE.equals( whiteSpace ) )
		{
			wrapText = false;
		}

		writer.openTag( "Style" );
		writer.attribute( "ss:ID", id );
		if ( style.getName( ) == StyleEntry.ENTRYNAME_HYPERLINK )
{'fraction_non_alphanumeric': 0.14568358651352353, 'fraction_numerical': 0.004149685068543905, 'mean_word_length': 3.2259276655706906, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 4}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4946619', 'n_tokens_mistral': 10580, 'n_tokens_neox': 8937, 'n_words': 2090}