/*********************************************************************
*
* Copyright (C) 2002 Andrew Khan
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
***************************************************************************/
package jxl.biff;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import jxl.common.Logger;
import jxl.WorkbookSettings;
import jxl.format.Format;
import jxl.read.biff.Record;
/**
* A non-built in format record
*/
public class FormatRecord extends WritableRecordData
implements DisplayFormat, Format
{
/**
* The logger
*/
public static Logger logger = Logger.getLogger(FormatRecord.class);
/**
* Initialized flag
*/
private boolean initialized;
/**
* The raw data
*/
private byte[] data;
/**
* The index code
*/
private int indexCode;
/**
* The formatting string
*/
private String formatString;
/**
* Indicates whether this is a date formatting record
*/
private boolean date;
/**
* Indicates whether this a number formatting record
*/
private boolean number;
/**
* The format object
*/
private java.text.Format format;
/**
* The date strings to look for
*/
private static String[] dateStrings = new String[]
{
"dd",
"mm",
"yy",
"hh",
"ss",
"m/",
"/d"
};
// Type to distinguish between biff7 and biff8
private static class BiffType
{
}
public static final BiffType biff8 = new BiffType();
public static final BiffType biff7 = new BiffType();
/**
* Constructor invoked when copying sheets
*
* @param fmt the format string
* @param refno the index code
*/
FormatRecord(String fmt, int refno)
{
super(Type.FORMAT);
formatString = fmt;
indexCode = refno;
initialized = true;
}
/**
* Constructor used by writable formats
*/
protected FormatRecord()
{
super(Type.FORMAT);
initialized = false;
}
/**
* Copy constructor - can be invoked by public access
*
* @param fr the format to copy
*/
protected FormatRecord(FormatRecord fr)
{
super(Type.FORMAT);
initialized = false;
formatString = fr.formatString;
date = fr.date;
number = fr.number;
// format = (java.text.Format) fr.format.clone();
}
/**
* Constructs this object from the raw data. Used when reading in a
* format record
*
* @param t the raw data
* @param ws the workbook settings
* @param biffType biff type dummy overload
*/
public FormatRecord(Record t, WorkbookSettings ws, BiffType biffType)
{
super(t);
byte[] data = getRecord().getData();
indexCode = IntegerHelper.getInt(data[0], data[1]);
initialized = true;
if (biffType == biff8)
{
int numchars = IntegerHelper.getInt(data[2], data[3]);
if (data[4] == 0)
{
formatString = StringHelper.getString(data, numchars, 5, ws);
}
else
{
formatString = StringHelper.getUnicodeString(data, numchars, 5);
}
}
else
{
int numchars = data[2];
byte[] chars = new byte[numchars];
System.arraycopy(data, 3, chars, 0, chars.length);
formatString = new String(chars);
}
date = false;
number = false;
// First see if this is a date format
for (int i = 0 ; i < dateStrings.length; i++)
{
String dateString = dateStrings[i];
if (formatString.indexOf(dateString) != -1 ||
formatString.indexOf(dateString.toUpperCase()) != -1)
{
date = true;
break;
}
}
// See if this is number format - look for the # or 0 characters
if (!date)
{
if (formatString.indexOf('#') != -1 ||
formatString.indexOf('0') != -1 )
{
number = true;
}
}
}
/**
* Used to get the data when writing out the format record
*
* @return the raw data
*/
public byte[] getData()
{
data = new byte[formatString.length() * 2 + 3 + 2];
IntegerHelper.getTwoBytes(indexCode, data, 0);
IntegerHelper.getTwoBytes(formatString.length(), data, 2);
data[4] = (byte) 1; // unicode indicator
StringHelper.getUnicodeBytes(formatString, data, 5);
return data;
}
/**
* Gets the format index of this record
*
* @return the format index of this record
*/
public int getFormatIndex()
{
return indexCode;
}
/**
* Accessor to see whether this object is initialized or not.
*
* @return TRUE if this font record has been initialized, FALSE otherwise
*/
public boolean isInitialized()
{
return initialized;
}
/**
* Sets the index of this record. Called from the FormattingRecords
* object
*
* @param pos the position of this font in the workbooks font list
*/
public void initialize(int pos)
{
indexCode = pos;
initialized = true;
}
/**
* Replaces all instances of search with replace in the input. Used for
* replacing microsoft number formatting characters with java equivalents
*
* @param input the format string
* @param search the Excel character to be replaced
* @param replace the java equivalent
* @return the input string with the specified substring replaced
*/
protected final String replace(String input, String search, String replace)
{
String fmtstr = input;
int pos = fmtstr.indexOf(search);
while (pos != -1)
{
StringBuffer tmp = new StringBuffer(fmtstr.substring(0, pos));
tmp.append(replace);
tmp.append(fmtstr.substring(pos + search.length()));
fmtstr = tmp.toString();
pos = fmtstr.indexOf(search);
}
return fmtstr;
}
/**
* Called by the immediate subclass to set the string
* once the Java-Excel replacements have been done
*
* @param s the format string
*/
protected final void setFormatString(String s)
{
formatString = s;
}
/**
* Sees if this format is a date format
*
* @return TRUE if this format is a date
*/
public final boolean isDate()
{
return date;
}
/**
* Sees if this format is a number format
*
* @return TRUE if this format is a number
*/
public final boolean isNumber()
{
return number;
}
/**
* Gets the java equivalent number format for the formatString
*
* @return The java equivalent of the number format for this object
*/
public final NumberFormat getNumberFormat()
{
if (format != null && format instanceof NumberFormat)
{
return (NumberFormat) format;
}
try
{
String fs = formatString;
// Replace the Excel formatting characters with java equivalents
fs = replace(fs, "E+", "E");
fs = replace(fs, "_)", "");
fs = replace(fs, "_", "");
fs = replace(fs, "[Red]", "");
fs = replace(fs, "\\", "");
format = new DecimalFormat(fs);
}
catch (IllegalArgumentException e)
{
// Something went wrong with the date format - fail silently
// and return a default value
format = new DecimalFormat("#.###");
}
return (NumberFormat) format;
}
/**
* Gets the java equivalent date format for the formatString
*
* @return The java equivalent of the date format for this object
*/
public final DateFormat getDateFormat()
{
if (format != null && format instanceof DateFormat)
{
return (DateFormat) format;
}
String fmt = formatString;
// Replace the AM/PM indicator with an a
int pos = fmt.indexOf("AM/PM");
while (pos != -1)
{
StringBuffer sb = new StringBuffer(fmt.substring(0, pos));
sb.append('a');
sb.append(fmt.substring(pos + 5));
fmt = sb.toString();
pos = fmt.indexOf("AM/PM");
}
// Replace ss.0 with ss.SSS (necessary to always specify milliseconds
// because of NT)
pos = fmt.indexOf("ss.0");
while (pos != -1)
{
StringBuffer sb = new StringBuffer(fmt.substring(0, pos));
sb.append("ss.SSS");
// Keep going until we run out of zeros
pos += 4;
while (pos < fmt.length() && fmt.charAt(pos) == '0')
{
pos++;
}
sb.append(fmt.substring(pos));
fmt = sb.toString();
pos = fmt.indexOf("ss.0");
}
// Filter out the backslashes
StringBuffer sb = new StringBuffer();
for (int i = 0; i < fmt.length(); i++)
{
if (fmt.charAt(i) != '\\')
{
sb.append(fmt.charAt(i));
}
}
fmt = sb.toString();
// If the date format starts with anything inside square brackets then
// filter tham out
if (fmt.charAt(0) == '[')
{
int end = fmt.indexOf(']');
if (end != -1)
{
fmt = fmt.substring(end+1);
}
}
// Get rid of some spurious characters that can creep in
fmt = replace(fmt, ";@", "");
// We need to convert the month indicator m, to upper case when we
// are dealing with dates
char[] formatBytes = fmt.toCharArray();
for (int i = 0; i < formatBytes.length; i++)
{
if (formatBytes[i] == 'm')
{
// Firstly, see if the preceding character is also an m. If so,
// copy that
if (i > 0 && (formatBytes[i - 1] == 'm' || formatBytes[i - 1] == 'M'))
{
formatBytes[i] = formatBytes[i - 1];
}
else
{
// There is no easy way out. We have to deduce whether this an
// minute or a month? See which is closest out of the
// letters H d s or y
// First, h
int minuteDist = Integer.MAX_VALUE;
for (int j = i - 1; j > 0; j--)
{
if (formatBytes[j] == 'h')
{
minuteDist = i - j;
break;
}
}
for (int j = i + 1; j < formatBytes.length; j++)
{
if (formatBytes[j] == 'h')
{
minuteDist = Math.min(minuteDist, j - i);
break;
}
}
for (int j = i - 1; j > 0; j--)
{
if (formatBytes[j] == 'H')
{
minuteDist = i - j;
break;
}
}
for (int j = i + 1; j < formatBytes.length; j++)
{
if (formatBytes[j] == 'H')
{
minuteDist = Math.min(minuteDist, j - i);
break;
}
}
// Now repeat for s
for (int j = i - 1; j > 0; j--)
{
if (formatBytes[j] == 's')
{
minuteDist = Math.min(minuteDist, i - j);
break;
}
}
for (int j = i + 1; j < formatBytes.length; j++)
{
if (formatBytes[j] == 's')
{
minuteDist = Math.min(minuteDist, j - i);
break;
}
}
// We now have the distance of the closest character which could
// indicate the the m refers to a minute
// Repeat for d and y
int monthDist = Integer.MAX_VALUE;
for (int j = i - 1; j > 0; j--)
{
if (formatBytes[j] == 'd')
{
monthDist = i - j;
break;
}
}
for (int j = i + 1; j < formatBytes.length; j++)
{
if (formatBytes[j] == 'd')
{
monthDist = Math.min(monthDist, j - i);
break;
}
}
// Now repeat for y
for (int j = i - 1; j > 0; j--)
{
if (formatBytes[j] == 'y')
{
monthDist = Math.min(monthDist, i - j);
break;
}
}
for (int j = i + 1; j < formatBytes.length; j++)
{
if (formatBytes[j] == 'y')
{
monthDist = Math.min(monthDist, j - i);
break;
}
}
if (monthDist < minuteDist)
{
// The month indicator is closer, so convert to a capital M
formatBytes[i] = Character.toUpperCase(formatBytes[i]);
}
else if ((monthDist == minuteDist) &&
(monthDist != Integer.MAX_VALUE))
{
// They are equidistant. As a tie-breaker, take the formatting
// character which precedes the m
char ind = formatBytes[i - monthDist];
if (ind == 'y' || ind == 'd')
{
// The preceding item indicates a month measure, so convert
formatBytes[i] = Character.toUpperCase(formatBytes[i]);
}
}
}
}
}
try
{
this.format = new SimpleDateFormat(new String(formatBytes));
}
catch (IllegalArgumentException e)
{
// There was a spurious character - fail silently
this.format = new SimpleDateFormat("dd MM yyyy hh:mm:ss");
}
return (DateFormat) this.format;
}
/**
* Gets the index code, for use as a hash value
*
* @return the ifmt code for this cell
*/
public int getIndexCode()
{
return indexCode;
}
/**
* Gets the formatting string.
*
* @return the excel format string
*/
public String getFormatString()
{
return formatString;
}
/**
* Indicates whether this formula is a built in
*
* @return FALSE
*/
public boolean isBuiltIn()
{
return false;
}
/**
* Standard hash code method
* @return the hash code value for this object
*/
public int hashCode()
{
return formatString.hashCode();
}
/**
* Standard equals method. This compares the contents of two
* format records, and not their indexCodes, which are ignored
*
* @param o the object to compare
* @return TRUE if the two objects are equal, FALSE otherwise
*/
public boolean equals(Object o)
{
if (o == this)
{
return true;
}
if (!(o instanceof FormatRecord))
{
return false;
}
FormatRecord fr = (FormatRecord) o;
// Initialized format comparison
if (initialized && fr.initialized)
{
// Must be either a number or a date format
if (date != fr.date ||
number != fr.number)
{
return false;
}
return formatString.equals(fr.formatString);
}
// Uninitialized format comparison
return formatString.equals(fr.formatString);
}
}
|