2009-06-09 21:34:58 +00:00
|
|
|
unit xlscommon;
|
|
|
|
|
2013-12-07 13:42:22 +00:00
|
|
|
{ Comments often have links to sections in the
|
|
|
|
OpenOffice Microsoft Excel File Format document }
|
|
|
|
|
2011-05-29 17:21:51 +00:00
|
|
|
{$ifdef fpc}
|
|
|
|
{$mode delphi}
|
|
|
|
{$endif}
|
2009-06-09 21:34:58 +00:00
|
|
|
|
|
|
|
interface
|
|
|
|
|
2011-05-29 17:21:51 +00:00
|
|
|
uses
|
2012-01-23 13:48:39 +00:00
|
|
|
Classes, SysUtils, DateUtils,
|
2011-05-29 17:21:51 +00:00
|
|
|
fpspreadsheet,
|
2011-12-24 23:42:08 +00:00
|
|
|
fpsutils, lconvencoding;
|
2011-05-29 17:21:51 +00:00
|
|
|
|
2009-06-09 21:34:58 +00:00
|
|
|
const
|
2011-12-25 09:03:20 +00:00
|
|
|
{ RECORD IDs which didn't change across versions 2-8 }
|
2014-04-22 23:10:32 +00:00
|
|
|
INT_EXCEL_ID_FONT = $0031;
|
2011-12-25 09:03:20 +00:00
|
|
|
INT_EXCEL_ID_CODEPAGE = $0042;
|
2014-04-25 22:15:26 +00:00
|
|
|
INT_EXCEL_ID_COLINFO = $007D;
|
2012-01-23 13:48:39 +00:00
|
|
|
INT_EXCEL_ID_DATEMODE = $0022;
|
2014-04-23 22:29:32 +00:00
|
|
|
INT_EXCEL_ID_PALETTE = $0092;
|
2014-04-25 22:15:26 +00:00
|
|
|
INT_EXCEL_ID_XF = $00E0;
|
|
|
|
|
2014-05-02 19:55:55 +00:00
|
|
|
{ RECORD IDs which did not change across versions 5-8 }
|
|
|
|
INT_EXCEL_ID_BLANK = $0201;
|
|
|
|
INT_EXCEL_ID_NUMBER = $0203;
|
|
|
|
INT_EXCEL_ID_LABEL = $0204;
|
|
|
|
|
2014-04-25 22:15:26 +00:00
|
|
|
{ FONT record constants }
|
|
|
|
INT_FONT_WEIGHT_NORMAL = $0190;
|
|
|
|
INT_FONT_WEIGHT_BOLD = $02BC;
|
2011-12-25 09:03:20 +00:00
|
|
|
|
2009-06-09 21:34:58 +00:00
|
|
|
{ Formula constants TokenID values }
|
|
|
|
|
2013-12-07 13:42:22 +00:00
|
|
|
{ Binary Operator Tokens 3.6}
|
2009-06-09 21:34:58 +00:00
|
|
|
INT_EXCEL_TOKEN_TADD = $03;
|
|
|
|
INT_EXCEL_TOKEN_TSUB = $04;
|
|
|
|
INT_EXCEL_TOKEN_TMUL = $05;
|
|
|
|
INT_EXCEL_TOKEN_TDIV = $06;
|
2014-04-08 09:48:30 +00:00
|
|
|
INT_EXCEL_TOKEN_TPOWER = $07; // Power Exponentiation ^
|
|
|
|
INT_EXCEL_TOKEN_TCONCAT = $08; // Concatenation &
|
|
|
|
INT_EXCEL_TOKEN_TLT = $09; // Less than <
|
|
|
|
INT_EXCEL_TOKEN_TLE = $0A; // Less than or equal <=
|
|
|
|
INT_EXCEL_TOKEN_TEQ = $0B; // Equal =
|
|
|
|
INT_EXCEL_TOKEN_TGE = $0C; // Greater than or equal >=
|
|
|
|
INT_EXCEL_TOKEN_TGT = $0D; // Greater than >
|
|
|
|
INT_EXCEL_TOKEN_TNE = $0E; // Not equal <>
|
2011-05-30 09:22:08 +00:00
|
|
|
INT_EXCEL_TOKEN_TISECT = $0F; // Cell range intersection
|
|
|
|
INT_EXCEL_TOKEN_TLIST = $10; // Cell range list
|
|
|
|
INT_EXCEL_TOKEN_TRANGE = $11; // Cell range
|
2014-04-08 09:48:30 +00:00
|
|
|
INT_EXCEL_TOKEN_TUPLUS = $12; // Unary plus +
|
|
|
|
INT_EXCEL_TOKEN_TUMINUS = $13; // Unary minus +
|
|
|
|
INT_EXCEL_TOKEN_TPERCENT= $14; // Percent (%, divides operand by 100)
|
2009-06-09 21:34:58 +00:00
|
|
|
|
2013-12-07 13:42:22 +00:00
|
|
|
{ Constant Operand Tokens, 3.8}
|
2014-04-08 09:48:30 +00:00
|
|
|
INT_EXCEL_TOKEN_TMISSARG= $16; //missing operand
|
2013-12-07 13:42:22 +00:00
|
|
|
INT_EXCEL_TOKEN_TSTR = $17; //string
|
|
|
|
INT_EXCEL_TOKEN_TBOOL = $1D; //boolean
|
|
|
|
INT_EXCEL_TOKEN_TINT = $1E; //integer
|
|
|
|
INT_EXCEL_TOKEN_TNUM = $1F; //floating-point
|
2009-06-09 21:34:58 +00:00
|
|
|
|
|
|
|
{ Operand Tokens }
|
2013-12-07 13:42:22 +00:00
|
|
|
// _R: reference; _V: value; _A: array
|
2009-06-09 21:34:58 +00:00
|
|
|
INT_EXCEL_TOKEN_TREFR = $24;
|
|
|
|
INT_EXCEL_TOKEN_TREFV = $44;
|
|
|
|
INT_EXCEL_TOKEN_TREFA = $64;
|
2014-04-08 09:48:30 +00:00
|
|
|
INT_EXCEL_TOKEN_TAREA_R = $25;
|
|
|
|
INT_EXCEL_TOKEN_TAREA_V = $45;
|
|
|
|
INT_EXCEL_TOKEN_TAREA_A = $65;
|
2009-06-09 21:34:58 +00:00
|
|
|
|
|
|
|
{ Function Tokens }
|
2013-12-07 13:42:22 +00:00
|
|
|
// _R: reference; _V: value; _A: array
|
|
|
|
// Offset 0: token; offset 1: index to a built-in sheet function ( ➜ 3.111)
|
2013-02-12 12:13:49 +00:00
|
|
|
INT_EXCEL_TOKEN_FUNC_R = $21;
|
|
|
|
INT_EXCEL_TOKEN_FUNC_V = $41;
|
|
|
|
INT_EXCEL_TOKEN_FUNC_A = $61;
|
|
|
|
|
2013-12-07 13:42:22 +00:00
|
|
|
//VAR: variable number of arguments:
|
2009-06-09 21:34:58 +00:00
|
|
|
INT_EXCEL_TOKEN_FUNCVAR_R = $22;
|
|
|
|
INT_EXCEL_TOKEN_FUNCVAR_V = $42;
|
|
|
|
INT_EXCEL_TOKEN_FUNCVAR_A = $62;
|
|
|
|
|
2013-12-07 13:42:22 +00:00
|
|
|
{ Built-in/worksheet functions }
|
2014-04-08 09:48:30 +00:00
|
|
|
INT_EXCEL_SHEET_FUNC_COUNT = 0;
|
|
|
|
INT_EXCEL_SHEET_FUNC_IF = 1;
|
|
|
|
INT_EXCEL_SHEET_FUNC_ISNA = 2;
|
|
|
|
INT_EXCEL_SHEET_FUNC_ISERROR = 3;
|
|
|
|
INT_EXCEL_SHEET_FUNC_SUM = 4;
|
|
|
|
INT_EXCEL_SHEET_FUNC_AVERAGE = 5;
|
|
|
|
INT_EXCEL_SHEET_FUNC_MIN = 6;
|
|
|
|
INT_EXCEL_SHEET_FUNC_MAX = 7;
|
|
|
|
INT_EXCEL_SHEET_FUNC_ROW = 8;
|
|
|
|
INT_EXCEL_SHEET_FUNC_COLUMN = 9;
|
|
|
|
INT_EXCEL_SHEET_FUNC_STDEV = 12;
|
|
|
|
INT_EXCEL_SHEET_FUNC_SIN = 15;
|
|
|
|
INT_EXCEL_SHEET_FUNC_COS = 16;
|
|
|
|
INT_EXCEL_SHEET_FUNC_TAN = 17;
|
|
|
|
INT_EXCEL_SHEET_FUNC_ATAN = 18;
|
|
|
|
INT_EXCEL_SHEET_FUNC_PI = 19;
|
|
|
|
INT_EXCEL_SHEET_FUNC_SQRT = 20;
|
|
|
|
INT_EXCEL_SHEET_FUNC_EXP = 21;
|
|
|
|
INT_EXCEL_SHEET_FUNC_LN = 22;
|
|
|
|
INT_EXCEL_SHEET_FUNC_LOG10 = 23;
|
|
|
|
INT_EXCEL_SHEET_FUNC_ABS = 24; // $18
|
|
|
|
INT_EXCEL_SHEET_FUNC_INT = 25;
|
|
|
|
INT_EXCEL_SHEET_FUNC_SIGN = 26;
|
|
|
|
INT_EXCEL_SHEET_FUNC_ROUND = 27; // $1B
|
|
|
|
INT_EXCEL_SHEET_FUNC_MID = 31;
|
|
|
|
INT_EXCEL_SHEET_FUNC_VALUE = 33;
|
|
|
|
INT_EXCEL_SHEET_FUNC_TRUE = 34;
|
|
|
|
INT_EXCEL_SHEET_FUNC_FALSE = 35;
|
|
|
|
INT_EXCEL_SHEET_FUNC_AND = 36;
|
|
|
|
INT_EXCEL_SHEET_FUNC_OR = 37;
|
|
|
|
INT_EXCEL_SHEET_FUNC_NOT = 38;
|
|
|
|
INT_EXCEL_SHEET_FUNC_VAR = 46;
|
|
|
|
INT_EXCEL_SHEET_FUNC_PV = 56;
|
|
|
|
INT_EXCEL_SHEET_FUNC_FV = 57;
|
|
|
|
INT_EXCEL_SHEET_FUNC_NPER = 58;
|
|
|
|
INT_EXCEL_SHEET_FUNC_PMT = 59;
|
|
|
|
INT_EXCEL_SHEET_FUNC_RATE = 60;
|
|
|
|
INT_EXCEL_SHEET_FUNC_RAND = 63;
|
|
|
|
INT_EXCEL_SHEET_FUNC_DATE = 65; // $41
|
|
|
|
INT_EXCEL_SHEET_FUNC_TIME = 66; // $42
|
|
|
|
INT_EXCEL_SHEET_FUNC_DAY = 67;
|
|
|
|
INT_EXCEL_SHEET_FUNC_MONTH = 68;
|
|
|
|
INT_EXCEL_SHEET_FUNC_YEAR = 69;
|
|
|
|
INT_EXCEL_SHEET_FUNC_WEEKDAY = 70;
|
|
|
|
INT_EXCEL_SHEET_FUNC_HOUR = 71;
|
|
|
|
INT_EXCEL_SHEET_FUNC_MINUTE = 72;
|
|
|
|
INT_EXCEL_SHEET_FUNC_SECOND = 73;
|
|
|
|
INT_EXCEL_SHEET_FUNC_NOW = 74;
|
|
|
|
INT_EXCEL_SHEET_FUNC_ROWS = 76;
|
|
|
|
INT_EXCEL_SHEET_FUNC_COLUMNS = 77;
|
|
|
|
INT_EXCEL_SHEET_FUNC_ASIN = 98;
|
|
|
|
INT_EXCEL_SHEET_FUNC_ACOS = 99;
|
|
|
|
INT_EXCEL_SHEET_FUNC_ISREF = 105;
|
|
|
|
INT_EXCEL_SHEET_FUNC_LOG = 109;
|
|
|
|
INT_EXCEL_SHEET_FUNC_CHAR = 111;
|
|
|
|
INT_EXCEL_SHEET_FUNC_LOWER = 112;
|
|
|
|
INT_EXCEL_SHEET_FUNC_UPPER = 113;
|
|
|
|
INT_EXCEL_SHEET_FUNC_PROPER = 114;
|
|
|
|
INT_EXCEL_SHEET_FUNC_LEFT = 115;
|
|
|
|
INT_EXCEL_SHEET_FUNC_RIGHT = 116;
|
|
|
|
INT_EXCEL_SHEET_FUNC_TRIM = 118;
|
|
|
|
INT_EXCEL_SHEET_FUNC_REPLACE = 119;
|
|
|
|
INT_EXCEL_SHEET_FUNC_SUBSTITUTE = 120;
|
|
|
|
INT_EXCEL_SHEET_FUNC_CODE = 121;
|
|
|
|
INT_EXCEL_SHEET_FUNC_CELL = 125;
|
|
|
|
INT_EXCEL_SHEET_FUNC_ISERR = 126;
|
|
|
|
INT_EXCEL_SHEET_FUNC_ISTEXT = 127;
|
|
|
|
INT_EXCEL_SHEET_FUNC_ISNUMBER = 128;
|
|
|
|
INT_EXCEL_SHEET_FUNC_ISBLANK = 129;
|
|
|
|
INT_EXCEL_SHEET_FUNC_DATEVALUE = 140;
|
|
|
|
INT_EXCEL_SHEET_FUNC_TIMEVALUE = 141;
|
|
|
|
INT_EXCEL_SHEET_FUNC_COUNTA = 169;
|
|
|
|
INT_EXCEL_SHEET_FUNC_PRODUCT = 183;
|
|
|
|
INT_EXCEL_SHEET_FUNC_ISNONTEXT = 190;
|
|
|
|
INT_EXCEL_SHEET_FUNC_STDEVP = 193;
|
|
|
|
INT_EXCEL_SHEET_FUNC_VARP = 194;
|
|
|
|
INT_EXCEL_SHEET_FUNC_ISLOGICAL = 198;
|
|
|
|
INT_EXCEL_SHEET_FUNC_TODAY = 221; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_MEDIAN = 227; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_SINH = 229; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_COSH = 230; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_TANH = 231; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_ASINH = 232; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_ACOSH = 233; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_ATANH = 234; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_INFO = 244; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_AVEDEV = 269; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_BETADIST = 270; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_BETAINV = 272; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_BINOMDIST = 273; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_CHIDIST = 274; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_CHIINV = 275; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_PERMUT = 299; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_POISSON = 300; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_SUMSQ = 321; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_RADIANS = 342; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_DEGREES = 343; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_SUMIF = 345; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_COUNTIF = 346; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_COUNTBLANK = 347; // not available in BIFF2
|
|
|
|
INT_EXCEL_SHEET_FUNC_DATEDIF = 351; // not available in BIFF2
|
2009-06-09 21:34:58 +00:00
|
|
|
|
2011-05-30 10:03:04 +00:00
|
|
|
{ Control Tokens, Special Tokens }
|
|
|
|
// 01H tExp Matrix formula or shared formula
|
|
|
|
// 02H tTbl Multiple operation table
|
|
|
|
// 15H tParen Parentheses
|
|
|
|
// 18H tNlr Natural language reference (BIFF8)
|
|
|
|
INT_EXCEL_TOKEN_TATTR = $19; // tAttr Special attribute
|
|
|
|
// 1AH tSheet Start of external sheet reference (BIFF2-BIFF4)
|
|
|
|
// 1BH tEndSheet End of external sheet reference (BIFF2-BIFF4)
|
|
|
|
|
2011-12-25 09:03:20 +00:00
|
|
|
{ CODEPAGE record constants }
|
|
|
|
WORD_ASCII = 367;
|
|
|
|
WORD_UTF_16 = 1200; // BIFF 8
|
|
|
|
WORD_CP_1250_Latin2 = 1250;
|
|
|
|
WORD_CP_1251_Cyrillic = 1251;
|
|
|
|
WORD_CP_1252_Latin1 = 1252; // BIFF4-BIFF5
|
|
|
|
WORD_CP_1253_Greek = 1253;
|
|
|
|
WORD_CP_1254_Turkish = 1254;
|
|
|
|
WORD_CP_1255_Hebrew = 1255;
|
|
|
|
WORD_CP_1256_Arabic = 1256;
|
|
|
|
WORD_CP_1257_Baltic = 1257;
|
|
|
|
WORD_CP_1258_Vietnamese = 1258;
|
|
|
|
WORD_CP_1258_Latin1_BIFF2_3 = 32769; // BIFF2-BIFF3
|
|
|
|
|
2013-12-07 13:42:22 +00:00
|
|
|
{ DATEMODE record, 5.28 }
|
|
|
|
DATEMODE_1900_BASE=1; //1/1/1900 minus 1 day in FPC TDateTime
|
|
|
|
DATEMODE_1904_BASE=1462; //1/1/1904 in FPC TDateTime
|
|
|
|
|
2014-05-02 18:49:05 +00:00
|
|
|
{ FORMAT record constants for BIFF5-BIFF8}
|
2013-12-22 14:02:04 +00:00
|
|
|
// Subset of the built-in formats for US Excel,
|
|
|
|
// including those needed for date/time output
|
|
|
|
FORMAT_GENERAL = 0; //general/default format
|
|
|
|
FORMAT_FIXED_0_DECIMALS = 1; //fixed, 0 decimals
|
|
|
|
FORMAT_FIXED_2_DECIMALS = 2; //fixed, 2 decimals
|
|
|
|
FORMAT_FIXED_THOUSANDS_0_DECIMALS = 3; //fixed, w/ thousand separator, 0 decs
|
|
|
|
FORMAT_FIXED_THOUSANDS_2_DECIMALS = 4; //fixed, w/ thousand separator, 2 decs
|
|
|
|
FORMAT_CURRENCY_0_DECIMALS = 5; //currency (with currency symbol), 0 decs
|
|
|
|
FORMAT_CURRENCY_2_DECIMALS = 7; //currency (with currency symbol), 2 decs
|
|
|
|
FORMAT_PERCENT_0_DECIMALS = 9; //percent, 0 decimals
|
|
|
|
FORMAT_PERCENT_2_DECIMALS = 10; //percent, 2 decimals
|
|
|
|
FORMAT_EXP_2_DECIMALS = 11; //exponent, 2 decimals
|
|
|
|
FORMAT_SCI_1_DECIMAL = 48; //scientific, 1 decimal
|
|
|
|
FORMAT_SHORT_DATE = 14; //short date
|
|
|
|
FORMAT_DATE_DM = 16; //date D-MMM
|
|
|
|
FORMAT_DATE_MY = 17; //date MMM-YYYY
|
|
|
|
FORMAT_SHORT_TIME_AM = 18; //short time H:MM with AM
|
|
|
|
FORMAT_LONG_TIME_AM = 19; //long time H:MM:SS with AM
|
|
|
|
FORMAT_SHORT_TIME = 20; //short time H:MM
|
|
|
|
FORMAT_LONG_TIME = 21; //long time H:MM:SS
|
|
|
|
FORMAT_SHORT_DATETIME = 22; //short date+time
|
|
|
|
FORMAT_TIME_MS = 45; //time MM:SS
|
|
|
|
FORMAT_TIME_MSZ = 47; //time MM:SS.0
|
|
|
|
FORMAT_TIME_INTERVAL = 46; //time [hh]:mm:ss, hh can be >24
|
2013-12-07 13:42:22 +00:00
|
|
|
|
2014-04-25 22:15:26 +00:00
|
|
|
{ XF substructures }
|
|
|
|
|
|
|
|
{ XF_TYPE_PROT - XF Type and Cell protection (3 Bits) - BIFF3-BIFF8 }
|
|
|
|
MASK_XF_TYPE_PROT_LOCKED = $1;
|
|
|
|
MASK_XF_TYPE_PROT_FORMULA_HIDDEN = $2;
|
|
|
|
MASK_XF_TYPE_PROT_STYLE_XF = $4; // 0 = CELL XF
|
|
|
|
|
|
|
|
{ XF_USED_ATTRIB - Attributes from parent Style XF (6 Bits) - BIFF3-BIFF8
|
|
|
|
|
|
|
|
- In a CELL XF a cleared bit means that the parent attribute is used,
|
|
|
|
while a set bit indicates that the data in this XF is used
|
|
|
|
- In a STYLE XF a cleared bit means that the data in this XF is used,
|
|
|
|
while a set bit indicates that the attribute should be ignored }
|
|
|
|
|
|
|
|
MASK_XF_USED_ATTRIB_NUMBER_FORMAT = $01;
|
|
|
|
MASK_XF_USED_ATTRIB_FONT = $02;
|
|
|
|
MASK_XF_USED_ATTRIB_TEXT = $04;
|
|
|
|
MASK_XF_USED_ATTRIB_BORDER_LINES = $08;
|
|
|
|
MASK_XF_USED_ATTRIB_BACKGROUND = $10;
|
|
|
|
MASK_XF_USED_ATTRIB_CELL_PROTECTION = $20;
|
|
|
|
{ the following values do not agree with the documentation !!!
|
|
|
|
MASK_XF_USED_ATTRIB_NUMBER_FORMAT = $04;
|
|
|
|
MASK_XF_USED_ATTRIB_FONT = $08;
|
|
|
|
MASK_XF_USED_ATTRIB_TEXT = $10;
|
|
|
|
MASK_XF_USED_ATTRIB_BORDER_LINES = $20;
|
|
|
|
MASK_XF_USED_ATTRIB_BACKGROUND = $40;
|
|
|
|
MASK_XF_USED_ATTRIB_CELL_PROTECTION = $80; }
|
|
|
|
|
|
|
|
{ XF record constants }
|
|
|
|
MASK_XF_TYPE_PROT = $0007;
|
|
|
|
MASK_XF_TYPE_PROT_PARENT = $FFF0;
|
|
|
|
|
|
|
|
MASK_XF_HOR_ALIGN = $07;
|
|
|
|
MASK_XF_VERT_ALIGN = $70;
|
|
|
|
MASK_XF_TEXTWRAP = $08;
|
|
|
|
|
|
|
|
{ XF HORIZONTAL ALIGN }
|
|
|
|
MASK_XF_HOR_ALIGN_LEFT = $01;
|
|
|
|
MASK_XF_HOR_ALIGN_CENTER = $02;
|
|
|
|
MASK_XF_HOR_ALIGN_RIGHT = $03;
|
|
|
|
MASK_XF_HOR_ALIGN_FILLED = $04;
|
|
|
|
MASK_XF_HOR_ALIGN_JUSTIFIED = $05; // BIFF4-BIFF8
|
|
|
|
MASK_XF_HOR_ALIGN_CENTERED_SELECTION = $06; // BIFF4-BIFF8
|
|
|
|
MASK_XF_HOR_ALIGN_DISTRIBUTED = $07; // BIFF8
|
|
|
|
|
|
|
|
{ XF_VERT_ALIGN }
|
|
|
|
MASK_XF_VERT_ALIGN_TOP = $00;
|
|
|
|
MASK_XF_VERT_ALIGN_CENTER = $10;
|
|
|
|
MASK_XF_VERT_ALIGN_BOTTOM = $20;
|
|
|
|
MASK_XF_VERT_ALIGN_JUSTIFIED = $30;
|
|
|
|
|
2013-12-07 13:42:22 +00:00
|
|
|
|
2011-05-29 17:21:51 +00:00
|
|
|
type
|
2013-12-07 13:42:22 +00:00
|
|
|
TDateMode=(dm1900,dm1904); //DATEMODE values, 5.28
|
2011-05-29 17:21:51 +00:00
|
|
|
|
2013-12-07 13:42:22 +00:00
|
|
|
// Adjusts Excel float (date, date/time, time) with the file's base date to get a TDateTime
|
|
|
|
function ConvertExcelDateTimeToDateTime
|
|
|
|
(const AExcelDateNum: Double; ADateMode: TDateMode): TDateTime;
|
|
|
|
// Adjusts TDateTime with the file's base date to get
|
|
|
|
// an Excel float value representing a time/date/datetime
|
|
|
|
function ConvertDateTimeToExcelDateTime
|
|
|
|
(const ADateTime: TDateTime; ADateMode: TDateMode): Double;
|
2011-05-29 17:21:51 +00:00
|
|
|
|
2013-12-07 13:42:22 +00:00
|
|
|
type
|
2014-04-25 22:15:26 +00:00
|
|
|
{ Contents of the XF record to be stored in the XFList of the reader }
|
|
|
|
TXFListData = class
|
|
|
|
public
|
|
|
|
FontIndex: Integer;
|
|
|
|
FormatIndex: Integer;
|
|
|
|
HorAlignment: TsHorAlignment;
|
|
|
|
VertAlignment: TsVertAlignment;
|
|
|
|
WordWrap: Boolean;
|
2014-04-27 15:59:16 +00:00
|
|
|
TextRotation: TsTextRotation;
|
2014-04-25 22:15:26 +00:00
|
|
|
Borders: TsCellBorders;
|
|
|
|
BackgroundColor: TsColor;
|
|
|
|
end;
|
|
|
|
|
2014-04-30 21:07:41 +00:00
|
|
|
{ Contents of the format record for BIFF5/8 }
|
|
|
|
TFormatListData = class
|
|
|
|
public
|
|
|
|
Index: Integer;
|
|
|
|
FormatString: widestring;
|
|
|
|
end;
|
|
|
|
|
2013-12-07 13:42:22 +00:00
|
|
|
{ TsSpreadBIFFReader }
|
2011-05-29 17:21:51 +00:00
|
|
|
TsSpreadBIFFReader = class(TsCustomSpreadReader)
|
|
|
|
protected
|
2014-05-01 21:47:41 +00:00
|
|
|
RecordSize: Word;
|
2012-04-29 15:21:12 +00:00
|
|
|
FCodepage: string; // in a format prepared for lconvencoding.ConvertEncoding
|
2013-12-07 13:42:22 +00:00
|
|
|
FDateMode: TDateMode;
|
2014-04-24 21:27:57 +00:00
|
|
|
FPaletteFound: Boolean;
|
2014-04-30 21:07:41 +00:00
|
|
|
FXFList: TFPList; // of TXFListData
|
|
|
|
FFormatList: TFPList; // of TFormatListData
|
|
|
|
procedure ApplyCellFormatting(ARow, ACol: Cardinal; XFIndex: Word); virtual;
|
|
|
|
// Returns the numberformat for a given XF record
|
|
|
|
procedure ExtractNumberFormat(AXFIndex: WORD;
|
|
|
|
out ANumberFormat: TsNumberFormat; out ADecimals: Word;
|
2014-05-02 18:49:05 +00:00
|
|
|
out ANumberFormatStr: String); virtual;
|
2014-04-30 21:07:41 +00:00
|
|
|
// Finds format record for XF record pointed to by cell
|
|
|
|
// Will not return info for built-in formats
|
|
|
|
function FindFormatDataForCell(const AXFIndex: Integer): TFormatListData;
|
|
|
|
// Tries to find if a number cell is actually a date/datetime/time cell and retrieves the value
|
|
|
|
function IsDateTime(Number: Double; ANumberFormat: TsNumberFormat; var ADateTime: TDateTime): Boolean;
|
2014-04-25 22:15:26 +00:00
|
|
|
// Here we can add reading of records which didn't change across BIFF5-8 versions
|
2011-12-24 23:38:24 +00:00
|
|
|
procedure ReadCodePage(AStream: TStream);
|
2014-04-25 22:15:26 +00:00
|
|
|
// Read column info
|
|
|
|
procedure ReadColInfo(const AStream: TStream);
|
2013-12-07 13:42:22 +00:00
|
|
|
// Figures out what the base year for dates is for this file
|
2012-01-23 13:48:39 +00:00
|
|
|
procedure ReadDateMode(AStream: TStream);
|
2014-04-30 21:07:41 +00:00
|
|
|
// Read FORMAT record (cell formatting)
|
|
|
|
procedure ReadFormat(AStream: TStream); virtual;
|
|
|
|
// Read floating point number
|
2014-05-01 21:47:41 +00:00
|
|
|
procedure ReadNumber(AStream: TStream); override;
|
2014-04-23 22:29:32 +00:00
|
|
|
// Read palette
|
|
|
|
procedure ReadPalette(AStream: TStream);
|
2014-04-25 22:15:26 +00:00
|
|
|
// Read the row, column, and XF index at the current stream position
|
2014-05-01 21:47:41 +00:00
|
|
|
procedure ReadRowColXF(AStream: TStream; out ARow, ACol: Cardinal; out AXF: Word); virtual;
|
2014-04-20 14:57:23 +00:00
|
|
|
// Read row info
|
2014-04-23 22:29:32 +00:00
|
|
|
procedure ReadRowInfo(AStream: TStream); virtual;
|
2014-04-21 21:43:43 +00:00
|
|
|
public
|
2014-04-23 22:29:32 +00:00
|
|
|
constructor Create(AWorkbook: TsWorkbook); override;
|
2014-04-25 22:15:26 +00:00
|
|
|
destructor Destroy; override;
|
2011-05-29 17:21:51 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
{ TsSpreadBIFFWriter }
|
|
|
|
|
|
|
|
TsSpreadBIFFWriter = class(TsCustomSpreadWriter)
|
|
|
|
protected
|
2013-12-07 13:42:22 +00:00
|
|
|
FDateMode: TDateMode;
|
2011-05-29 17:33:46 +00:00
|
|
|
FLastRow: Integer;
|
|
|
|
FLastCol: Word;
|
2014-05-02 19:02:24 +00:00
|
|
|
procedure AddDefaultFormats; override;
|
2011-05-29 17:21:51 +00:00
|
|
|
procedure GetLastRowCallback(ACell: PCell; AStream: TStream);
|
|
|
|
function GetLastRowIndex(AWorksheet: TsWorksheet): Integer;
|
|
|
|
procedure GetLastColCallback(ACell: PCell; AStream: TStream);
|
|
|
|
function GetLastColIndex(AWorksheet: TsWorksheet): Word;
|
2014-04-08 09:48:30 +00:00
|
|
|
function FormulaElementKindToExcelTokenID(AElementKind: TFEKind; out ASecondaryID: Word): Word;
|
2014-04-25 22:15:26 +00:00
|
|
|
|
2014-05-02 19:55:55 +00:00
|
|
|
// Write out BLANK cell record
|
|
|
|
procedure WriteBlank(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
ACell: PCell); override;
|
2013-12-07 13:42:22 +00:00
|
|
|
// Write out used codepage for character encoding
|
2011-12-25 09:03:20 +00:00
|
|
|
procedure WriteCodepage(AStream: TStream; AEncoding: TsEncoding);
|
2014-04-25 22:15:26 +00:00
|
|
|
// Writes out column info(s)
|
|
|
|
procedure WriteColInfo(AStream: TStream; ACol: PCol);
|
|
|
|
procedure WriteColInfos(AStream: TStream; ASheet: TsWorksheet);
|
2013-12-07 13:42:22 +00:00
|
|
|
// Writes out DATEMODE record depending on FDateMode
|
|
|
|
procedure WriteDateMode(AStream: TStream);
|
2014-04-30 21:07:41 +00:00
|
|
|
// Writes out a TIME/DATE/TIMETIME
|
|
|
|
procedure WriteDateTime(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
const AValue: TDateTime; ACell: PCell); override;
|
2014-05-02 19:55:55 +00:00
|
|
|
// Writes out a floating point NUMBER record
|
|
|
|
procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
const AValue: Double; ACell: PCell); override;
|
2014-04-23 22:29:32 +00:00
|
|
|
// Writes out a PALETTE record containing all colors defined in the workbook
|
|
|
|
procedure WritePalette(AStream: TStream);
|
2014-05-02 19:55:55 +00:00
|
|
|
// Writes the index of the XF record used in the given cell
|
|
|
|
procedure WriteXFIndex(AStream: TStream; ACell: PCell);
|
2014-05-02 18:49:05 +00:00
|
|
|
|
2013-12-07 13:42:22 +00:00
|
|
|
public
|
2014-04-23 22:29:32 +00:00
|
|
|
constructor Create(AWorkbook: TsWorkbook); override;
|
2014-05-02 18:49:05 +00:00
|
|
|
destructor Destroy; override;
|
2011-05-29 17:21:51 +00:00
|
|
|
end;
|
|
|
|
|
2013-12-22 14:02:04 +00:00
|
|
|
function IsExpNumberFormat(s: String; out Decimals: Word): Boolean;
|
|
|
|
function IsFixedNumberFormat(s: String; out Decimals: Word): Boolean;
|
|
|
|
function IsPercentNumberFormat(s: String; out Decimals: Word): Boolean;
|
|
|
|
function IsThousandSepNumberFormat(s: String; out Decimals: Word): Boolean;
|
|
|
|
|
|
|
|
function IsDateFormat(s: String): Boolean;
|
|
|
|
function IsTimeFormat(s: String; out isLong, isAMPM, isMillisec: Boolean): Boolean;
|
|
|
|
|
|
|
|
|
2009-06-09 21:34:58 +00:00
|
|
|
implementation
|
|
|
|
|
2013-12-07 13:42:22 +00:00
|
|
|
function ConvertExcelDateTimeToDateTime(
|
|
|
|
const AExcelDateNum: Double; ADateMode: TDateMode): TDateTime;
|
|
|
|
begin
|
|
|
|
// Time only:
|
|
|
|
if (AExcelDateNum<1) and (AExcelDateNum>=0) then
|
|
|
|
begin
|
|
|
|
Result:=AExcelDateNum;
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
case ADateMode of
|
|
|
|
dm1900:
|
|
|
|
begin
|
|
|
|
// Check for Lotus 1-2-3 bug with 1900 leap year
|
|
|
|
if AExcelDateNum=61.0 then
|
|
|
|
// 29 feb does not exist, change to 28
|
|
|
|
// Spell out that we remove a day for ehm "clarity".
|
|
|
|
result:=61.0-1.0+DATEMODE_1900_BASE-1.0
|
|
|
|
else
|
|
|
|
result:=AExcelDateNum+DATEMODE_1900_BASE-1.0;
|
|
|
|
end;
|
|
|
|
dm1904:
|
|
|
|
result:=AExcelDateNum+DATEMODE_1904_BASE;
|
|
|
|
else
|
|
|
|
raise Exception.CreateFmt('ConvertExcelDateTimeToDateTime: unknown datemode %d. Please correct fpspreadsheet source code. ', [ADateMode]);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function ConvertDateTimeToExcelDateTime(const ADateTime: TDateTime;
|
|
|
|
ADateMode: TDateMode): Double;
|
|
|
|
begin
|
|
|
|
// Time only:
|
|
|
|
if (ADateTime<1) and (ADateTime>=0) then
|
|
|
|
begin
|
|
|
|
Result:=ADateTime;
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
case ADateMode of
|
|
|
|
dm1900:
|
|
|
|
result:=ADateTime-DATEMODE_1900_BASE+1.0;
|
|
|
|
dm1904:
|
|
|
|
result:=ADateTime-DATEMODE_1904_BASE;
|
|
|
|
else
|
|
|
|
raise Exception.CreateFmt('ConvertDateTimeToExcelDateTime: unknown datemode %d. Please correct fpspreadsheet source code. ', [ADateMode]);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2014-04-25 22:15:26 +00:00
|
|
|
|
2011-12-24 23:38:24 +00:00
|
|
|
{ TsSpreadBIFFReader }
|
|
|
|
|
2014-04-23 22:29:32 +00:00
|
|
|
constructor TsSpreadBIFFReader.Create(AWorkbook: TsWorkbook);
|
2012-01-23 13:48:39 +00:00
|
|
|
begin
|
2014-04-23 22:29:32 +00:00
|
|
|
inherited Create(AWorkbook);
|
2014-04-25 22:15:26 +00:00
|
|
|
FXFList := TFPList.Create;
|
2014-04-30 21:07:41 +00:00
|
|
|
FFormatList := TFPList.Create;
|
2013-12-07 13:42:22 +00:00
|
|
|
// Initial base date in case it won't be read from file
|
|
|
|
FDateMode := dm1900;
|
2012-01-23 13:48:39 +00:00
|
|
|
end;
|
2014-04-25 22:15:26 +00:00
|
|
|
|
|
|
|
destructor TsSpreadBIFFReader.Destroy;
|
|
|
|
var
|
|
|
|
j: integer;
|
|
|
|
begin
|
|
|
|
for j := FXFList.Count-1 downto 0 do TObject(FXFList[j]).Free;
|
|
|
|
FXFList.Free;
|
2014-04-30 21:07:41 +00:00
|
|
|
for j := FFormatList.Count-1 downto 0 do TObject(FFormatList[j]).Free;
|
|
|
|
FFormatList.Free;
|
2014-04-25 22:15:26 +00:00
|
|
|
inherited Destroy;
|
|
|
|
end;
|
|
|
|
|
2014-05-02 18:49:05 +00:00
|
|
|
{ Applies the XF formatting referred to by XFIndex to the specified cell }
|
2014-04-25 22:15:26 +00:00
|
|
|
procedure TsSpreadBIFFReader.ApplyCellFormatting(ARow, ACol: Cardinal;
|
2014-04-30 21:07:41 +00:00
|
|
|
XFIndex: Word);
|
2014-04-25 22:15:26 +00:00
|
|
|
var
|
|
|
|
lCell: PCell;
|
|
|
|
XFData: TXFListData;
|
2014-04-20 21:51:12 +00:00
|
|
|
begin
|
2014-04-25 22:15:26 +00:00
|
|
|
lCell := FWorksheet.GetCell(ARow, ACol);
|
|
|
|
if Assigned(lCell) then begin
|
|
|
|
XFData := TXFListData(FXFList.Items[XFIndex]);
|
|
|
|
|
|
|
|
// Font
|
|
|
|
if XFData.FontIndex = 1 then
|
|
|
|
Include(lCell^.UsedFormattingFields, uffBold)
|
|
|
|
else
|
|
|
|
if XFData.FontIndex > 1 then
|
|
|
|
Include(lCell^.UsedFormattingFields, uffFont);
|
|
|
|
lCell^.FontIndex := XFData.FontIndex;
|
|
|
|
|
|
|
|
// Alignment
|
|
|
|
lCell^.HorAlignment := XFData.HorAlignment;
|
|
|
|
lCell^.VertAlignment := XFData.VertAlignment;
|
|
|
|
|
|
|
|
// Word wrap
|
|
|
|
if XFData.WordWrap then
|
|
|
|
Include(lCell^.UsedFormattingFields, uffWordWrap)
|
|
|
|
else
|
|
|
|
Exclude(lCell^.UsedFormattingFields, uffWordWrap);
|
|
|
|
|
2014-04-27 15:59:16 +00:00
|
|
|
// Text rotation
|
|
|
|
if XFData.TextRotation > trHorizontal then
|
|
|
|
Include(lCell^.UsedFormattingFields, uffTextRotation)
|
|
|
|
else
|
|
|
|
Exclude(lCell^.UsedFormattingFields, uffTextRotation);
|
|
|
|
lCell^.TextRotation := XFData.TextRotation;
|
|
|
|
|
2014-04-25 22:15:26 +00:00
|
|
|
// Borders
|
|
|
|
if XFData.Borders <> [] then begin
|
|
|
|
Include(lCell^.UsedFormattingFields, uffBorder);
|
|
|
|
lCell^.Border := XFData.Borders;
|
|
|
|
end else
|
|
|
|
Exclude(lCell^.UsedFormattingFields, uffBorder);
|
|
|
|
|
|
|
|
// Background color
|
|
|
|
if XFData.BackgroundColor <> 0 then begin
|
|
|
|
Include(lCell^.UsedFormattingFields, uffBackgroundColor);
|
|
|
|
lCell^.BackgroundColor := XFData.BackgroundColor;
|
|
|
|
end;
|
2014-04-20 21:51:12 +00:00
|
|
|
end;
|
|
|
|
end;
|
2014-04-25 22:15:26 +00:00
|
|
|
|
2014-05-02 18:49:05 +00:00
|
|
|
{ Extracts number format data from an XF record index by AXFIndex.
|
|
|
|
Valid for BIFF5-BIFF8. Needs to be overridden for BIFF2 }
|
2014-04-30 21:07:41 +00:00
|
|
|
procedure TsSpreadBIFFReader.ExtractNumberFormat(AXFIndex: WORD;
|
|
|
|
out ANumberFormat: TsNumberFormat; out ADecimals: Word;
|
|
|
|
out ANumberFormatStr: String);
|
|
|
|
const
|
|
|
|
{ see ➜ 5.49 }
|
|
|
|
NOT_USED = nfGeneral;
|
|
|
|
fmts: array[1..58] of TsNumberFormat = (
|
|
|
|
nfFixed, nfFixed, nfFixedTh, nfFixedTh, nfFixedTh, // 1..5
|
|
|
|
nfFixedTh, nfFixedTh, nfFixedTh, nfPercentage, nfPercentage, // 6..10
|
|
|
|
nfExp, NOT_USED, NOT_USED, nfShortDate, nfShortDate, // 11..15
|
|
|
|
nfFmtDateTime, nfFmtDateTime, nfShortTimeAM, nfLongTimeAM, nfShortTime, // 16..20
|
|
|
|
nfLongTime, nfShortDateTime, NOT_USED, NOT_USED, NOT_USED, // 21..25
|
|
|
|
NOT_USED, NOT_USED, NOT_USED, NOT_USED, NOT_USED, // 26..30
|
|
|
|
NOT_USED, NOT_USED, NOT_USED, NOT_USED, NOT_USED, // 31..35
|
|
|
|
NOT_USED, nfFixedTh, nfFixedTh, nfFixedTh, nfFixedTh, // 36..40
|
|
|
|
nfFixedTh, nfFixedTh, nfFixedTh, nfFixedTh, nfFmtDateTime, // 41..45
|
|
|
|
nfTimeInterval, nfFmtDateTime, nfSci, NOT_USED, NOT_USED, // 46..50
|
|
|
|
NOT_USED, NOT_USED, NOT_USED, NOT_USED, NOT_USED, // 51..55
|
|
|
|
NOT_USED, NOT_USED, NOT_USED // 56..58
|
|
|
|
);
|
|
|
|
decs: array[1..58] of word = (
|
|
|
|
0, 2, 0, 2, 0, 0, 2, 2, 0, 2, // 1..10
|
|
|
|
2, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 11..20
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 21..30
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 2, 2, // 31..40
|
|
|
|
0, 0, 2, 2, 0, 3, 0, 1, 0, 0, // 41..50 #48 is "scientific", use "exponential" instead
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0); // 51..58
|
|
|
|
var
|
|
|
|
lFormatData: TFormatListData;
|
|
|
|
lXFData: TXFListData;
|
|
|
|
isAMPM: Boolean;
|
|
|
|
isLongTime: Boolean;
|
|
|
|
isMilliSec: Boolean;
|
|
|
|
t,d: Boolean;
|
|
|
|
begin
|
|
|
|
ANumberFormat := nfGeneral;
|
|
|
|
ANumberFormatStr := '';
|
|
|
|
ADecimals := 0;
|
|
|
|
|
|
|
|
lFormatData := FindFormatDataForCell(AXFIndex);
|
|
|
|
{Record FORMAT, BIFF 5/8 (5.49):
|
|
|
|
Offset Size Contents
|
|
|
|
0 2 Format index used in other records
|
|
|
|
}
|
|
|
|
|
|
|
|
if lFormatData = nil then begin
|
|
|
|
// no custom format, so first test for default formats
|
2014-05-02 18:49:05 +00:00
|
|
|
lXFData := TXFListData(FXFList.Items[AXFIndex]);
|
2014-04-30 21:07:41 +00:00
|
|
|
case lXFData.FormatIndex of
|
|
|
|
FORMAT_DATE_DM:
|
|
|
|
begin ANumberFormat := nfFmtDateTime; ANumberFormatStr := 'DM'; end;
|
|
|
|
FORMAT_DATE_MY:
|
|
|
|
begin ANumberFormat := nfFmtDateTime; ANumberFormatStr := 'MY'; end;
|
|
|
|
FORMAT_TIME_MS:
|
|
|
|
begin ANumberFormat := nfFmtDateTime; ANumberFormatStr := 'MS'; end;
|
|
|
|
FORMAT_TIME_MSZ:
|
|
|
|
begin ANumberFormat := nfFmtDateTime; ANumberFormatStr := 'MSZ'; end;
|
|
|
|
else
|
|
|
|
if (lXFData.FormatIndex > 0) and (lXFData.FormatIndex <= 58) then begin
|
|
|
|
ANumberFormat := fmts[lXFData.FormatIndex];
|
|
|
|
ADecimals := decs[lXFData.FormatIndex];
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end else
|
|
|
|
// Check custom formats if they have "/" in format string (this can fail for
|
|
|
|
// custom text formats)
|
|
|
|
if IsPercentNumberFormat(lFormatData.FormatString, ADecimals) then
|
|
|
|
ANumberFormat := nfPercentage
|
|
|
|
else
|
|
|
|
if IsExpNumberFormat(lFormatData.Formatstring, ADecimals) then
|
|
|
|
ANumberFormat := nfExp
|
|
|
|
else
|
|
|
|
if IsThousandSepNumberFormat(lFormatData.FormatString, ADecimals) then
|
|
|
|
ANumberFormat := nfFixedTh
|
|
|
|
else
|
|
|
|
if IsFixedNumberFormat(lFormatData.FormatString, ADecimals) then
|
|
|
|
ANumberFormat := nfFixed
|
|
|
|
else begin
|
|
|
|
t := IsTimeFormat(lFormatData.FormatString, isLongTime, isAMPM, isMilliSec);
|
|
|
|
d := IsDateFormat(lFormatData.FormatString);
|
|
|
|
if d and t then
|
|
|
|
ANumberFormat := nfShortDateTime
|
|
|
|
else
|
|
|
|
if d then
|
|
|
|
ANumberFormat := nfShortDate
|
|
|
|
else
|
|
|
|
if t then begin
|
|
|
|
if isAMPM then begin
|
|
|
|
if isLongTime then
|
|
|
|
ANumberFormat := nfLongTimeAM
|
|
|
|
else
|
|
|
|
ANumberFormat := nfShortTimeAM;
|
|
|
|
end else begin
|
|
|
|
if isLongTime then
|
|
|
|
ANumberFormat := nfLongTime
|
|
|
|
else
|
|
|
|
ANumberFormat := nfShortTime;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{ Determines the format data (for numerical formatting) which belong to a given
|
|
|
|
XF record.
|
|
|
|
Does not return data for built-in formats. }
|
|
|
|
function TsSpreadBIFFReader.FindFormatDataForCell(const AXFIndex: Integer
|
|
|
|
): TFormatListData;
|
|
|
|
var
|
|
|
|
lXFData: TXFListData;
|
|
|
|
i: Integer;
|
|
|
|
begin
|
|
|
|
lXFData := TXFListData(FXFList.Items[AXFIndex]);
|
|
|
|
for i := 0 to FFormatList.Count-1 do begin
|
|
|
|
Result := TFormatListData(FFormatList.Items[i]);
|
|
|
|
if Result.Index = lXFData.FormatIndex then Exit;
|
|
|
|
end;
|
|
|
|
Result := nil;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{ Convert the number to a date/time and return that if it is }
|
|
|
|
function TsSpreadBIFFReader.IsDateTime(Number: Double;
|
|
|
|
ANumberFormat: TsNumberFormat; var ADateTime: TDateTime): boolean;
|
|
|
|
begin
|
|
|
|
if ANumberFormat in [
|
|
|
|
nfShortDateTime, nfFmtDateTime, nfShortDate,
|
|
|
|
nfShortTime, nfLongTime, nfShortTimeAM, nfLongTimeAM] then
|
|
|
|
begin
|
|
|
|
ADateTime := ConvertExcelDateTimeToDateTime(Number, FDateMode);
|
|
|
|
Result := true;
|
|
|
|
end else
|
|
|
|
if ANumberFormat = nfTimeInterval then begin
|
|
|
|
ADateTime := Number;
|
|
|
|
Result := true;
|
|
|
|
end else
|
|
|
|
begin
|
|
|
|
ADateTime := 0;
|
|
|
|
Result := false;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2011-12-25 09:03:20 +00:00
|
|
|
// In BIFF 8 it seams to always use the UTF-16 codepage
|
2011-12-24 23:38:24 +00:00
|
|
|
procedure TsSpreadBIFFReader.ReadCodePage(AStream: TStream);
|
|
|
|
var
|
|
|
|
lCodePage: Word;
|
|
|
|
begin
|
|
|
|
{ Codepage }
|
|
|
|
lCodePage := WordLEToN(AStream.ReadWord());
|
|
|
|
|
|
|
|
case lCodePage of
|
|
|
|
// 016FH = 367 = ASCII
|
|
|
|
// 01B5H = 437 = IBM PC CP-437 (US)
|
|
|
|
//02D0H = 720 = IBM PC CP-720 (OEM Arabic)
|
|
|
|
//02E1H = 737 = IBM PC CP-737 (Greek)
|
|
|
|
//0307H = 775 = IBM PC CP-775 (Baltic)
|
|
|
|
//0352H = 850 = IBM PC CP-850 (Latin I)
|
2012-04-29 15:21:12 +00:00
|
|
|
$0352: FCodepage := 'cp850';
|
2011-12-24 23:38:24 +00:00
|
|
|
//0354H = 852 = IBM PC CP-852 (Latin II (Central European))
|
2012-04-29 15:21:12 +00:00
|
|
|
$0354: FCodepage := 'cp852';
|
2011-12-24 23:38:24 +00:00
|
|
|
//0357H = 855 = IBM PC CP-855 (Cyrillic)
|
2012-04-29 15:21:12 +00:00
|
|
|
$0357: FCodepage := 'cp855';
|
2011-12-24 23:38:24 +00:00
|
|
|
//0359H = 857 = IBM PC CP-857 (Turkish)
|
|
|
|
$0359: FCodepage := 'cp857';
|
2012-04-29 15:21:12 +00:00
|
|
|
//035AH = 858 = IBM PC CP-858 (Multilingual Latin I with Euro)
|
|
|
|
//035CH = 860 = IBM PC CP-860 (Portuguese)
|
|
|
|
//035DH = 861 = IBM PC CP-861 (Icelandic)
|
|
|
|
//035EH = 862 = IBM PC CP-862 (Hebrew)
|
|
|
|
//035FH = 863 = IBM PC CP-863 (Canadian (French))
|
|
|
|
//0360H = 864 = IBM PC CP-864 (Arabic)
|
|
|
|
//0361H = 865 = IBM PC CP-865 (Nordic)
|
|
|
|
//0362H = 866 = IBM PC CP-866 (Cyrillic (Russian))
|
|
|
|
//0365H = 869 = IBM PC CP-869 (Greek (Modern))
|
|
|
|
//036AH = 874 = Windows CP-874 (Thai)
|
|
|
|
//03A4H = 932 = Windows CP-932 (Japanese Shift-JIS)
|
|
|
|
//03A8H = 936 = Windows CP-936 (Chinese Simplified GBK)
|
|
|
|
//03B5H = 949 = Windows CP-949 (Korean (Wansung))
|
|
|
|
//03B6H = 950 = Windows CP-950 (Chinese Traditional BIG5)
|
|
|
|
//04B0H = 1200 = UTF-16 (BIFF8)
|
|
|
|
$04B0: FCodepage := 'utf-16';
|
|
|
|
//04E2H = 1250 = Windows CP-1250 (Latin II) (Central European)
|
|
|
|
//04E3H = 1251 = Windows CP-1251 (Cyrillic)
|
|
|
|
//04E4H = 1252 = Windows CP-1252 (Latin I) (BIFF4-BIFF5)
|
|
|
|
//04E5H = 1253 = Windows CP-1253 (Greek)
|
2011-12-24 23:38:24 +00:00
|
|
|
//04E6H = 1254 = Windows CP-1254 (Turkish)
|
|
|
|
$04E6: FCodepage := 'cp1254';
|
2012-04-29 15:21:12 +00:00
|
|
|
//04E7H = 1255 = Windows CP-1255 (Hebrew)
|
|
|
|
//04E8H = 1256 = Windows CP-1256 (Arabic)
|
|
|
|
//04E9H = 1257 = Windows CP-1257 (Baltic)
|
|
|
|
//04EAH = 1258 = Windows CP-1258 (Vietnamese)
|
|
|
|
//0551H = 1361 = Windows CP-1361 (Korean (Johab))
|
|
|
|
//2710H = 10000 = Apple Roman
|
|
|
|
//8000H = 32768 = Apple Roman
|
|
|
|
//8001H = 32769 = Windows CP-1252 (Latin I) (BIFF2-BIFF3)
|
2011-12-24 23:38:24 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2014-05-02 18:49:05 +00:00
|
|
|
{ Read column info (column width) from the stream.
|
|
|
|
Valid for BIFF3-BIFF8.
|
|
|
|
For BIFF2 use the records COLWIDTH and COLUMNDEFAULT. }
|
2014-04-25 22:15:26 +00:00
|
|
|
procedure TsSpreadBiffReader.ReadColInfo(const AStream: TStream);
|
|
|
|
var
|
|
|
|
c, c1, c2: Cardinal;
|
|
|
|
w: Word;
|
|
|
|
col: TCol;
|
|
|
|
begin
|
|
|
|
// read column start and end index of column range
|
|
|
|
c1 := WordLEToN(AStream.ReadWord);
|
|
|
|
c2 := WordLEToN(AStream.ReadWord);
|
|
|
|
// read col width in 1/256 of the width of "0" character
|
|
|
|
w := WordLEToN(AStream.ReadWord);
|
|
|
|
// calculate width in units of "characters"
|
|
|
|
col.Width := w / 256;
|
|
|
|
// assign width to columns
|
|
|
|
for c := c1 to c2 do
|
|
|
|
FWorksheet.WriteColInfo(c, col);
|
|
|
|
end;
|
|
|
|
|
2012-01-23 13:48:39 +00:00
|
|
|
procedure TsSpreadBIFFReader.ReadDateMode(AStream: TStream);
|
|
|
|
var
|
|
|
|
lBaseMode: Word;
|
|
|
|
begin
|
|
|
|
//5.28 DATEMODE
|
|
|
|
//BIFF2 BIFF3 BIFF4 BIFF5 BIFF8
|
|
|
|
//0022H 0022H 0022H 0022H 0022H
|
|
|
|
//This record specifies the base date for displaying date values. All dates are stored as count of days past this base date. In
|
|
|
|
//BIFF2-BIFF4 this record is part of the Calculation Settings Block (➜4.3). In BIFF5-BIFF8 it is stored in the Workbook
|
|
|
|
//Globals Substream.
|
|
|
|
//Record DATEMODE, BIFF2-BIFF8:
|
|
|
|
//Offset Size Contents
|
|
|
|
//0 2 0 = Base date is 1899-Dec-31 (the cell value 1 represents 1900-Jan-01)
|
|
|
|
// 1 = Base date is 1904-Jan-01 (the cell value 1 represents 1904-Jan-02)
|
|
|
|
lBaseMode := WordLEtoN(AStream.ReadWord);
|
2013-12-07 13:42:22 +00:00
|
|
|
case lBaseMode of
|
|
|
|
0: FDateMode := dm1900;
|
|
|
|
1: FDateMode := dm1904;
|
|
|
|
else raise Exception.CreateFmt('Error reading file. Got unknown date mode number %d.',[lBaseMode]);
|
|
|
|
end;
|
2012-01-23 13:48:39 +00:00
|
|
|
end;
|
|
|
|
|
2014-04-30 21:07:41 +00:00
|
|
|
// Read the FORMAT record for formatting numerical data
|
|
|
|
procedure TsSpreadBIFFReader.ReadFormat(AStream: TStream);
|
|
|
|
begin
|
|
|
|
// to be overridden
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Reads a floating point number and seeks the number format
|
|
|
|
// NOTE: This procedure is valid after BIFF 3.
|
|
|
|
procedure TsSpreadBIFFReader.ReadNumber(AStream: TStream);
|
|
|
|
var
|
|
|
|
ARow, ACol: Cardinal;
|
|
|
|
XF: WORD;
|
2014-05-02 18:49:05 +00:00
|
|
|
value: Double;
|
|
|
|
dt: TDateTime;
|
2014-04-30 21:07:41 +00:00
|
|
|
nf: TsNumberFormat;
|
|
|
|
nd: word;
|
|
|
|
nfs: String;
|
|
|
|
begin
|
|
|
|
ReadRowColXF(AStream,ARow,ACol,XF);
|
|
|
|
|
|
|
|
{ IEE 754 floating-point value }
|
2014-05-02 18:49:05 +00:00
|
|
|
AStream.ReadBuffer(value, 8);
|
2014-04-30 21:07:41 +00:00
|
|
|
|
|
|
|
{Find out what cell type, set content type and value}
|
|
|
|
ExtractNumberFormat(XF, nf, nd, nfs);
|
2014-05-02 18:49:05 +00:00
|
|
|
if IsDateTime(value, nf, dt) then
|
|
|
|
FWorksheet.WriteDateTime(ARow, ACol, dt, nf, nfs)
|
2014-04-30 21:07:41 +00:00
|
|
|
else
|
2014-05-02 18:49:05 +00:00
|
|
|
FWorksheet.WriteNumber(ARow, ACol, value, nf, nd);
|
2014-04-30 21:07:41 +00:00
|
|
|
|
|
|
|
{ Add attributes to cell }
|
|
|
|
ApplyCellFormatting(ARow, ACol, XF);
|
|
|
|
end;
|
|
|
|
|
2014-04-23 22:29:32 +00:00
|
|
|
// Read the palette
|
|
|
|
procedure TsSpreadBIFFReader.ReadPalette(AStream: TStream);
|
|
|
|
var
|
|
|
|
i, n: Word;
|
2014-04-24 21:27:57 +00:00
|
|
|
pal: Array of TsColorValue;
|
2014-04-23 22:29:32 +00:00
|
|
|
begin
|
|
|
|
n := WordLEToN(AStream.ReadWord) + 8;
|
|
|
|
SetLength(pal, n);
|
|
|
|
for i:=0 to 7 do
|
|
|
|
pal[i] := Workbook.GetPaletteColor(i);
|
|
|
|
for i:=8 to n-1 do
|
|
|
|
pal[i] := DWordLEToN(AStream.ReadDWord);
|
|
|
|
Workbook.UsePalette(@pal[0], n, false);
|
2014-04-24 21:27:57 +00:00
|
|
|
FPaletteFound := true;
|
2014-04-23 22:29:32 +00:00
|
|
|
end;
|
|
|
|
|
2014-04-25 22:15:26 +00:00
|
|
|
// Read the row, column and xf index
|
2014-05-01 21:47:41 +00:00
|
|
|
// NOT VALID for BIFF2
|
2014-04-30 21:07:41 +00:00
|
|
|
procedure TsSpreadBIFFReader.ReadRowColXF(AStream: TStream;
|
|
|
|
out ARow, ACol: Cardinal; out AXF: WORD);
|
2014-04-25 22:15:26 +00:00
|
|
|
begin
|
|
|
|
{ BIFF Record data for row and column}
|
|
|
|
ARow := WordLEToN(AStream.ReadWord);
|
|
|
|
ACol := WordLEToN(AStream.ReadWord);
|
|
|
|
|
|
|
|
{ Index to XF record }
|
|
|
|
AXF := WordLEtoN(AStream.ReadWord);
|
|
|
|
end;
|
|
|
|
|
2014-04-20 14:57:23 +00:00
|
|
|
// Read the part of the ROW record that is common to all BIFF versions
|
2014-04-23 22:29:32 +00:00
|
|
|
procedure TsSpreadBIFFReader.ReadRowInfo(AStream: TStream);
|
2014-04-20 14:57:23 +00:00
|
|
|
type
|
|
|
|
TRowRecord = packed record
|
|
|
|
RowIndex: Word;
|
|
|
|
Col1: Word;
|
|
|
|
Col2: Word;
|
|
|
|
Height: Word;
|
|
|
|
end;
|
|
|
|
var
|
|
|
|
rowrec: TRowRecord;
|
|
|
|
lRow: PRow;
|
|
|
|
h: word;
|
|
|
|
begin
|
|
|
|
AStream.ReadBuffer(rowrec, SizeOf(TRowRecord));
|
|
|
|
h := WordLEToN(rowrec.Height);
|
|
|
|
if h and $8000 = 0 then begin // if this bit were set, rowheight would be default
|
|
|
|
lRow := FWorksheet.GetRow(WordLEToN(rowrec.RowIndex));
|
|
|
|
// Row height is encoded into the 15 remaining bits in units "twips" (1/20 pt)
|
|
|
|
lRow^.Height := TwipsToMillimeters(h and $7FFF);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2011-05-29 17:21:51 +00:00
|
|
|
|
2014-04-23 22:29:32 +00:00
|
|
|
{ TsSpreadBIFFWriter }
|
2011-05-29 17:21:51 +00:00
|
|
|
|
2014-04-23 22:29:32 +00:00
|
|
|
constructor TsSpreadBIFFWriter.Create(AWorkbook: TsWorkbook);
|
2011-05-29 17:21:51 +00:00
|
|
|
begin
|
2014-04-23 22:29:32 +00:00
|
|
|
inherited Create(AWorkbook);
|
|
|
|
// Initial base date in case it won't be set otherwise.
|
|
|
|
// Use 1900 to get a bit more range between 1900..1904.
|
|
|
|
FDateMode := dm1900;
|
2011-05-29 17:21:51 +00:00
|
|
|
end;
|
|
|
|
|
2014-05-02 18:49:05 +00:00
|
|
|
destructor TsSpreadBIFFWriter.Destroy;
|
|
|
|
begin
|
|
|
|
inherited Destroy;
|
|
|
|
end;
|
|
|
|
|
2014-05-02 19:02:24 +00:00
|
|
|
{ These are default style formats which are added as XF fields regardless of
|
|
|
|
being used in the document or not.
|
|
|
|
Currently, only one additional default format is supported ("bold").
|
|
|
|
Here are the changes to be made when extending this list:
|
|
|
|
- SetLength(FFormattingstyles, <number of predefined styles>)
|
|
|
|
}
|
|
|
|
procedure TsSpreadBIFFWriter.AddDefaultFormats();
|
|
|
|
begin
|
|
|
|
SetLength(FFormattingStyles, 1);
|
|
|
|
|
|
|
|
// XF0..XF14: Normal style, Row Outline level 1..7,
|
|
|
|
// Column Outline level 1..7.
|
|
|
|
|
|
|
|
// XF15 - Default cell format, no formatting (4.6.2)
|
|
|
|
FFormattingStyles[0].UsedFormattingFields := [];
|
|
|
|
FFormattingStyles[0].Row := 15;
|
|
|
|
|
|
|
|
NextXFIndex := 15 + Length(FFormattingStyles);
|
|
|
|
// "15" is the index of the last pre-defined xf record
|
|
|
|
end;
|
|
|
|
|
2011-05-30 09:22:08 +00:00
|
|
|
function TsSpreadBIFFWriter.FormulaElementKindToExcelTokenID(
|
2014-04-08 09:48:30 +00:00
|
|
|
AElementKind: TFEKind; out ASecondaryID: Word): Word;
|
|
|
|
const
|
|
|
|
{ Explanation of first index:
|
|
|
|
0 --> primary token (basic operands and operations)
|
|
|
|
1 --> secondary token of a function with a fixed parameter count
|
|
|
|
2 --> secondary token of a function with a variable parameter count }
|
|
|
|
TokenIDs: array[fekCell..fekOpSum, 0..1] of Word = (
|
|
|
|
// Basic operands
|
|
|
|
(0, INT_EXCEL_TOKEN_TREFV), {fekCell}
|
|
|
|
(0, INT_EXCEL_TOKEN_TREFR), {fekCellRef}
|
|
|
|
(0, INT_EXCEL_TOKEN_TAREA_R), {fekCellRange}
|
|
|
|
(0, INT_EXCEL_TOKEN_TNUM), {fekNum}
|
|
|
|
(0, INT_EXCEL_TOKEN_TSTR), {fekString}
|
|
|
|
(0, INT_EXCEL_TOKEN_TBOOL), {fekBool}
|
|
|
|
(0, INT_EXCEL_TOKEN_TMISSARG), {fekMissArg, missing argument}
|
|
|
|
|
|
|
|
// Basic operations
|
|
|
|
(0, INT_EXCEL_TOKEN_TADD), {fekAdd, +}
|
|
|
|
(0, INT_EXCEL_TOKEN_TSUB), {fekSub, -}
|
|
|
|
(0, INT_EXCEL_TOKEN_TDIV), {fekDiv, /}
|
|
|
|
(0, INT_EXCEL_TOKEN_TMUL), {fekMul, *}
|
|
|
|
(0, INT_EXCEL_TOKEN_TPERCENT), {fekPercent, %}
|
|
|
|
(0, INT_EXCEL_TOKEN_TPOWER), {fekPower, ^}
|
|
|
|
(0, INT_EXCEL_TOKEN_TUMINUS), {fekUMinus, -}
|
|
|
|
(0, INT_EXCEL_TOKEN_TUPLUS), {fekUPlus, +}
|
|
|
|
(0, INT_EXCEL_TOKEN_TCONCAT), {fekConcat, &, for strings}
|
|
|
|
(0, INT_EXCEL_TOKEN_TEQ), {fekEqual, =}
|
|
|
|
(0, INT_EXCEL_TOKEN_TGT), {fekGreater, >}
|
|
|
|
(0, INT_EXCEL_TOKEN_TGE), {fekGreaterEqual, >=}
|
|
|
|
(0, INT_EXCEL_TOKEN_TLT), {fekLess <}
|
|
|
|
(0, INT_EXCEL_TOKEN_TLE), {fekLessEqual, <=}
|
|
|
|
(0, INT_EXCEL_TOKEN_TNE), {fekNotEqual, <>}
|
|
|
|
|
|
|
|
// Math functions
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_ABS), {fekABS}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_ACOS), {fekACOS}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_ACOSH), {fekACOSH}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_ASIN), {fekASIN}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_ASINH), {fekASINH}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_ATAN), {fekATAN}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_ATANH), {fekATANH}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_COS), {fekCOS}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_COSH), {fekCOSH}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_DEGREES), {fekDEGREES}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_EXP), {fekEXP}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_INT), {fekINT}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_LN), {fekLN}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_LOG), {fekLOG}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_LOG10), {fekLOG10}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_PI), {fekPI}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_RADIANS), {fekRADIANS}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_RAND), {fekRAND}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_ROUND), {fekROUND}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_SIGN), {fekSIGN}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_SIN), {fekSIN}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_SINH), {fekSINH}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_SQRT), {fekSQRT}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_TAN), {fekTAN}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_TANH), {fekTANH}
|
|
|
|
|
|
|
|
// Date/time functions
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_DATE), {fekDATE}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_DATEDIF), {fekDATEDIF}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_DATEVALUE), {fekDATEVALUE}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_DAY), {fekDAY}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_HOUR), {fekHOUR}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_MINUTE), {fekMINUTE}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_MONTH), {fekMONTH}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_NOW), {fekNOW}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_SECOND), {fekSECOND}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_TIME), {fekTIME}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_TIMEVALUE), {fekTIMEVALUE}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_TODAY), {fekTODAY}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_WEEKDAY), {fekWEEKDAY}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_YEAR), {fekYEAR}
|
|
|
|
|
|
|
|
// Statistical functions
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_AVEDEV), {fekAVEDEV}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_AVERAGE), {fekAVERAGE}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_BETADIST), {fekBETADIST}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_BETAINV), {fekBETAINV}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_BINOMDIST), {fekBINOMDIST}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_CHIDIST), {fekCHIDIST}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_CHIINV), {fekCHIINV}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_COUNT), {fekCOUNT}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_COUNTA), {fekCOUNTA}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_COUNTBLANK),{fekCOUNTBLANK}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_COUNTIF), {fekCOUNTIF}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_MAX), {fekMAX}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_MEDIAN), {fekMEDIAN}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_MIN), {fekMIN}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_PERMUT), {fekPERMUT}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_POISSON), {fekPOISSON}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_PRODUCT), {fekPRODUCT}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_STDEV), {fekSTDEV}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_STDEVP), {fekSTDEVP}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_SUM), {fekSUM}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_SUMIF), {fekSUMIF}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_SUMSQ), {fekSUMSQ}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_VAR), {fekVAR}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_VARP), {fekVARP}
|
|
|
|
|
|
|
|
// Financial functions
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_FV), {fekFV}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_NPER), {fekNPER}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_PV), {fekPV}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_PMT), {fekPMT}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_RATE), {fekRATE}
|
|
|
|
|
|
|
|
// Logical functions
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_AND), {fekAND}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_FALSE), {fekFALSE}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_IF), {fekIF}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_NOT), {fekNOT}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_OR), {fekOR}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_TRUE), {fekTRUE}
|
|
|
|
|
|
|
|
// String functions
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_CHAR), {fekCHAR}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_CODE), {fekCODE}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_LEFT), {fekLEFT}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_LOWER), {fekLOWER}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_MID), {fekMID}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_PROPER), {fekPROPER}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_REPLACE), {fekREPLACE}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_RIGHT), {fekRIGHT}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_SUBSTITUTE),{fekSUBSTITUTE}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_TRIM), {fekTRIM}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_UPPER), {fekUPPER}
|
|
|
|
|
|
|
|
// lookup/reference functions
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_COLUMN), {fekCOLUMN}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_COLUMNS), {fekCOLUMNS}
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_ROW), {fekROW}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_ROWS), {fekROWS}
|
|
|
|
|
|
|
|
// Info functions
|
|
|
|
(2, INT_EXCEL_SHEET_FUNC_CELL), {fekCELLINFO}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_INFO), {fekINFO}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_ISBLANK), {fekIsBLANK}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_ISERR), {fekIsERR}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_ISERROR), {fekIsERROR}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_ISLOGICAL), {fekIsLOGICAL}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_ISNA), {fekIsNA}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_ISNONTEXT), {fekIsNONTEXT}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_ISNUMBER), {fekIsNUMBER}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_ISREF), {fekIsREF}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_ISTEXT), {fekIsTEXT}
|
|
|
|
(1, INT_EXCEL_SHEET_FUNC_VALUE), {fekValue}
|
|
|
|
|
|
|
|
// Other operations
|
|
|
|
(0, INT_EXCEL_TOKEN_TATTR) {fekOpSum}
|
|
|
|
);
|
|
|
|
|
2011-05-30 09:22:08 +00:00
|
|
|
begin
|
2014-04-08 09:48:30 +00:00
|
|
|
case TokenIDs[AElementKind, 0] of
|
|
|
|
0: begin
|
|
|
|
Result := TokenIDs[AElementKind, 1];
|
|
|
|
ASecondaryID := 0;
|
|
|
|
end;
|
|
|
|
1: begin
|
|
|
|
Result := INT_EXCEL_TOKEN_FUNC_V;
|
|
|
|
ASecondaryID := TokenIDs[AElementKind, 1]
|
|
|
|
end;
|
|
|
|
2: begin
|
|
|
|
Result := INT_EXCEL_TOKEN_FUNCVAR_V;
|
|
|
|
ASecondaryID := TokenIDs[AElementKind, 1]
|
|
|
|
end;
|
2011-05-30 09:22:08 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2014-04-23 22:29:32 +00:00
|
|
|
procedure TsSpreadBIFFWriter.GetLastRowCallback(ACell: PCell; AStream: TStream);
|
|
|
|
begin
|
|
|
|
if ACell^.Row > FLastRow then FLastRow := ACell^.Row;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TsSpreadBIFFWriter.GetLastRowIndex(AWorksheet: TsWorksheet): Integer;
|
|
|
|
begin
|
|
|
|
FLastRow := 0;
|
|
|
|
IterateThroughCells(nil, AWorksheet.Cells, GetLastRowCallback);
|
|
|
|
Result := FLastRow;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSpreadBIFFWriter.GetLastColCallback(ACell: PCell; AStream: TStream);
|
|
|
|
begin
|
|
|
|
if ACell^.Col > FLastCol then FLastCol := ACell^.Col;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TsSpreadBIFFWriter.GetLastColIndex(AWorksheet: TsWorksheet): Word;
|
|
|
|
begin
|
|
|
|
FLastCol := 0;
|
|
|
|
IterateThroughCells(nil, AWorksheet.Cells, GetLastColCallback);
|
|
|
|
Result := FLastCol;
|
|
|
|
end;
|
|
|
|
|
2014-05-02 19:55:55 +00:00
|
|
|
{ Writes an empty ("blank") cell. Needed for formatting empty cells.
|
|
|
|
Valid for BIFF5 and BIFF8. Needs to be overridden for BIFF2 which has a
|
|
|
|
different record structure. }
|
|
|
|
procedure TsSpreadBIFFWriter.WriteBlank(AStream: TStream;
|
|
|
|
const ARow, ACol: Cardinal; ACell: PCell);
|
|
|
|
begin
|
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_BLANK));
|
|
|
|
AStream.WriteWord(WordToLE(6));
|
|
|
|
|
|
|
|
{ Row and column index }
|
|
|
|
AStream.WriteWord(WordToLE(ARow));
|
|
|
|
AStream.WriteWord(WordToLE(ACol));
|
|
|
|
|
|
|
|
{ Index to XF record, according to formatting }
|
|
|
|
WriteXFIndex(AStream, ACell);
|
|
|
|
end;
|
|
|
|
|
2011-12-25 09:03:20 +00:00
|
|
|
procedure TsSpreadBIFFWriter.WriteCodepage(AStream: TStream;
|
|
|
|
AEncoding: TsEncoding);
|
|
|
|
var
|
|
|
|
lCodepage: Word;
|
|
|
|
begin
|
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_CODEPAGE));
|
|
|
|
AStream.WriteWord(WordToLE(2));
|
|
|
|
|
|
|
|
{ Codepage }
|
|
|
|
case AEncoding of
|
|
|
|
seLatin2: lCodepage := WORD_CP_1250_Latin2;
|
|
|
|
seCyrillic: lCodepage := WORD_CP_1251_Cyrillic;
|
|
|
|
seGreek: lCodepage := WORD_CP_1253_Greek;
|
|
|
|
seTurkish: lCodepage := WORD_CP_1254_Turkish;
|
|
|
|
seHebrew: lCodepage := WORD_CP_1255_Hebrew;
|
|
|
|
seArabic: lCodepage := WORD_CP_1256_Arabic;
|
|
|
|
else
|
|
|
|
// Default is Latin1
|
|
|
|
lCodepage := WORD_CP_1252_Latin1;
|
|
|
|
end;
|
|
|
|
AStream.WriteWord(WordToLE(lCodepage));
|
|
|
|
end;
|
|
|
|
|
2014-05-02 19:55:55 +00:00
|
|
|
{ Writes column info for the given column. Currently only the colum width is used.
|
|
|
|
Valid for BIFF5 and BIFF8 (BIFF2 uses a different record. }
|
2014-04-25 22:15:26 +00:00
|
|
|
procedure TsSpreadBIFFWriter.WriteColInfo(AStream: TStream; ACol: PCol);
|
|
|
|
var
|
|
|
|
w: Integer;
|
|
|
|
begin
|
|
|
|
if Assigned(ACol) then begin
|
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_COLINFO)); // BIFF record header
|
|
|
|
AStream.WriteWord(WordToLE(12)); // Record size
|
|
|
|
AStream.WriteWord(WordToLE(ACol^.Col)); // start column
|
|
|
|
AStream.WriteWord(WordToLE(ACol^.Col)); // end column
|
|
|
|
{ calculate width to be in units of 1/256 of pixel width of character "0" }
|
|
|
|
w := round(ACol^.Width * 256);
|
|
|
|
AStream.WriteWord(WordToLE(w)); // write width
|
|
|
|
AStream.WriteWord(15); // XF record, ignored
|
|
|
|
AStream.WriteWord(0); // option flags, ignored
|
|
|
|
AStream.WriteWord(0); // "not used"
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2014-05-02 19:55:55 +00:00
|
|
|
{ Writes the column info records for all used columns. }
|
2014-04-25 22:15:26 +00:00
|
|
|
procedure TsSpreadBIFFWriter.WriteColInfos(AStream: TStream;
|
|
|
|
ASheet: TsWorksheet);
|
|
|
|
var
|
|
|
|
j: Integer;
|
|
|
|
col: PCol;
|
|
|
|
begin
|
|
|
|
for j := 0 to ASheet.Cols.Count-1 do begin
|
|
|
|
col := PCol(ASheet.Cols[j]);
|
|
|
|
WriteColInfo(AStream, col);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2013-12-07 13:42:22 +00:00
|
|
|
procedure TsSpreadBIFFWriter.WriteDateMode(AStream: TStream);
|
|
|
|
begin
|
|
|
|
{ BIFF Record header }
|
|
|
|
// todo: check whether this is in the right place. should end up in workbook globals stream
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_DATEMODE));
|
|
|
|
AStream.WriteWord(WordToLE(2));
|
|
|
|
|
|
|
|
case FDateMode of
|
|
|
|
dm1900: AStream.WriteWord(WordToLE(0));
|
|
|
|
dm1904: AStream.WriteWord(WordToLE(1));
|
|
|
|
else raise Exception.CreateFmt('Unknown datemode number %d. Please correct fpspreadsheet code.', [FDateMode]);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2014-05-01 21:47:41 +00:00
|
|
|
{ Writes a date/time/datetime to a Biff NUMBER record, with a date/time format
|
|
|
|
(There is no separate date record type in xls)
|
|
|
|
Valid for all BIFF versions. }
|
2014-04-30 21:07:41 +00:00
|
|
|
procedure TsSpreadBIFFWriter.WriteDateTime(AStream: TStream; const ARow,
|
|
|
|
ACol: Cardinal; const AValue: TDateTime; ACell: PCell);
|
|
|
|
var
|
|
|
|
ExcelDateSerial: double;
|
|
|
|
begin
|
2014-05-02 18:49:05 +00:00
|
|
|
ExcelDateSerial := ConvertDateTimeToExcelDateTime(AValue, FDateMode);
|
2014-04-30 21:07:41 +00:00
|
|
|
// fpspreadsheet must already have set formatting to a date/datetime format, so
|
|
|
|
// this will get written out as a pointer to the relevant XF record.
|
|
|
|
// In the end, dates in xls are just numbers with a format. Pass it on to WriteNumber:
|
|
|
|
WriteNumber(AStream, ARow, ACol, ExcelDateSerial, ACell);
|
|
|
|
end;
|
|
|
|
|
2014-05-02 19:55:55 +00:00
|
|
|
{ Writes a 64-bit floating point NUMBER record.
|
|
|
|
Valid for BIFF5 and BIFF8 (BIFF2 has a different record structure.). }
|
|
|
|
procedure TsSpreadBIFFWriter.WriteNumber(AStream: TStream;
|
|
|
|
const ARow, ACol: Cardinal; const AValue: double; ACell: PCell);
|
|
|
|
begin
|
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_NUMBER));
|
|
|
|
AStream.WriteWord(WordToLE(14));
|
|
|
|
|
|
|
|
{ BIFF Record data }
|
|
|
|
AStream.WriteWord(WordToLE(ARow));
|
|
|
|
AStream.WriteWord(WordToLE(ACol));
|
|
|
|
|
|
|
|
{ Index to XF record }
|
|
|
|
WriteXFIndex(AStream, ACell);
|
|
|
|
|
|
|
|
{ IEE 754 floating-point value }
|
|
|
|
AStream.WriteBuffer(AValue, 8);
|
|
|
|
end;
|
|
|
|
|
2014-04-23 22:29:32 +00:00
|
|
|
procedure TsSpreadBIFFWriter.WritePalette(AStream: TStream);
|
|
|
|
var
|
|
|
|
i, n: Integer;
|
2013-12-07 13:42:22 +00:00
|
|
|
begin
|
2014-04-23 22:29:32 +00:00
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_PALETTE));
|
|
|
|
AStream.WriteWord(WordToLE(2 + 4*56));
|
|
|
|
|
|
|
|
{ Number of colors }
|
|
|
|
AStream.WriteWord(WordToLE(56));
|
|
|
|
|
|
|
|
{ Take the colors from the palette of the Worksheet }
|
|
|
|
{ Skip the first 8 entries - they are hard-coded into Excel }
|
|
|
|
n := Workbook.GetPaletteSize;
|
|
|
|
for i:=8 to 63 do
|
|
|
|
if i < n then
|
|
|
|
AStream.WriteDWord(DWordToLE(Workbook.GetPaletteColor(i)))
|
|
|
|
else
|
|
|
|
AStream.WriteDWord(DWordToLE($FFFFFF));
|
2013-12-07 13:42:22 +00:00
|
|
|
end;
|
|
|
|
|
2013-12-22 14:02:04 +00:00
|
|
|
|
2014-05-02 19:55:55 +00:00
|
|
|
{ Write the index of the XF record, according to formatting of the given cell
|
|
|
|
Valid for BIFF5 and BIFF8.
|
|
|
|
BIFF2 is handled differently. }
|
|
|
|
procedure TsSpreadBIFFWriter.WriteXFIndex(AStream: TStream; ACell: PCell);
|
|
|
|
var
|
|
|
|
lIndex: Integer;
|
|
|
|
lXFIndex: Word;
|
|
|
|
begin
|
|
|
|
// First try the fast methods for default formats
|
|
|
|
if ACell^.UsedFormattingFields = [] then begin
|
|
|
|
AStream.WriteWord(WordToLE(15)); //XF15; see TsSpreadBIFF8Writer.AddDefaultFormats
|
|
|
|
Exit;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// If not, then we need to search in the list of dynamic formats
|
|
|
|
lIndex := FindFormattingInList(ACell);
|
|
|
|
|
|
|
|
// Carefully check the index
|
|
|
|
if (lIndex < 0) or (lIndex > Length(FFormattingStyles)) then
|
|
|
|
raise Exception.Create('[TsSpreadBIFFWriter.WriteXFIndex] Invalid Index, this should not happen!');
|
|
|
|
|
|
|
|
lXFIndex := FFormattingStyles[lIndex].Row;
|
|
|
|
|
|
|
|
AStream.WriteWord(WordToLE(lXFIndex));
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
2013-12-22 14:02:04 +00:00
|
|
|
{ Format checking procedures }
|
|
|
|
|
|
|
|
{ This simple parsing procedure of the Excel format string checks for a fixed
|
|
|
|
float format s, i.e. s can be '0', '0.00', '000', '0,000', and returns the
|
|
|
|
number of decimals, i.e. number of zeros behind the decimal point }
|
|
|
|
function IsFixedNumberFormat(s: String; out Decimals: Word): Boolean;
|
|
|
|
var
|
|
|
|
i: Integer;
|
|
|
|
p: Integer;
|
|
|
|
decs: String;
|
|
|
|
begin
|
|
|
|
Decimals := 0;
|
|
|
|
|
|
|
|
// Check if s is a valid format mask.
|
|
|
|
try
|
|
|
|
FormatFloat(s, 1.0);
|
|
|
|
except
|
|
|
|
on EConvertError do begin
|
|
|
|
Result := false;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// If it is count the zeros - each one is a decimal.
|
|
|
|
if s = '0' then
|
|
|
|
Result := true
|
|
|
|
else begin
|
|
|
|
p := pos('.', s); // position of decimal point;
|
|
|
|
if p = 0 then begin
|
|
|
|
Result := false;
|
|
|
|
end else begin
|
|
|
|
Result := true;
|
|
|
|
for i:= p+1 to Length(s) do
|
|
|
|
if s[i] = '0' then begin
|
|
|
|
inc(Decimals)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
exit; // ignore characters after the last 0
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{ This function checks whether the format string corresponds to a thousand
|
|
|
|
separator format like "#,##0.000' and returns the number of fixed decimals
|
|
|
|
(i.e. zeros after the decimal point) }
|
|
|
|
function IsThousandSepNumberFormat(s: String; out Decimals: Word): Boolean;
|
|
|
|
var
|
|
|
|
i, p: Integer;
|
|
|
|
begin
|
|
|
|
Decimals := 0;
|
|
|
|
|
|
|
|
// Check if s is a valid format string
|
|
|
|
try
|
|
|
|
FormatFloat(s, 1.0);
|
|
|
|
except
|
|
|
|
on EConvertError do begin
|
|
|
|
Result := false;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// If it is look for the thousand separator. If found count decimals.
|
|
|
|
Result := (Pos(',', s) > 0);
|
|
|
|
if Result then begin
|
|
|
|
p := pos('.', s);
|
|
|
|
if p > 0 then
|
|
|
|
for i := p+1 to Length(s) do
|
|
|
|
if s[i] = '0' then
|
|
|
|
inc(Decimals)
|
|
|
|
else
|
|
|
|
exit; // ignore format characters after the last 0
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
{ This function checks whether the format string corresponds to percent
|
|
|
|
formatting and determines the number of decimals }
|
|
|
|
function IsPercentNumberFormat(s: String; out Decimals: Word): Boolean;
|
|
|
|
var
|
|
|
|
i, p: Integer;
|
|
|
|
begin
|
|
|
|
Decimals := 0;
|
|
|
|
// The signature of the percent format is a percent sign at the end of the
|
|
|
|
// format string.
|
|
|
|
Result := (s <> '') and (s[Length(s)] = '%');
|
|
|
|
if Result then begin
|
|
|
|
// Check for a valid format string
|
|
|
|
Delete(s, Length(s), 1);
|
|
|
|
try
|
|
|
|
FormatDateTime(s, 1.0);
|
|
|
|
except
|
|
|
|
on EConvertError do begin
|
|
|
|
Result := false;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
// Count decimals
|
|
|
|
p := pos('.', s);
|
|
|
|
if p > 0 then
|
|
|
|
for i := p+1 to Length(s)-1 do
|
|
|
|
if s[i] = '0' then
|
|
|
|
inc(Decimals)
|
|
|
|
else
|
|
|
|
exit; // ignore characters after last 0
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{ This function checks whether the format string corresponds to exponential
|
2014-04-20 20:31:36 +00:00
|
|
|
formatting and determines the number of decimals }
|
2013-12-22 14:02:04 +00:00
|
|
|
function IsExpNumberFormat(s: String; out Decimals: Word): Boolean;
|
|
|
|
var
|
|
|
|
i, p, pe: Integer;
|
|
|
|
begin
|
2014-04-20 20:31:36 +00:00
|
|
|
Result := false;
|
2013-12-22 14:02:04 +00:00
|
|
|
Decimals := 0;
|
|
|
|
|
2014-04-20 20:31:36 +00:00
|
|
|
if SameText(s, 'General') then
|
|
|
|
exit;
|
|
|
|
|
2013-12-22 14:02:04 +00:00
|
|
|
// Check for a valid format string
|
|
|
|
try
|
|
|
|
FormatDateTime(s, 1.0);
|
|
|
|
except
|
|
|
|
on EConvertError do begin
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Count decimals
|
|
|
|
pe := pos('e', lowercase(s));
|
|
|
|
result := pe > 0;
|
|
|
|
if Result then begin
|
|
|
|
p := pos('.', s);
|
|
|
|
if (p > 0) then begin
|
|
|
|
if p < pe then
|
|
|
|
for i:=1 to pe-1 do
|
|
|
|
if s[i] = '0' then
|
|
|
|
inc(Decimals)
|
|
|
|
else
|
|
|
|
exit; // ignore characters after last 0
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{ IsDateFormat checks if the format string s corresponds to a date format }
|
|
|
|
function IsDateFormat(s: String): Boolean;
|
|
|
|
begin
|
|
|
|
// Day, month, year are separated by a slash
|
|
|
|
Result := (pos('/', s) > 0);
|
|
|
|
if Result then
|
|
|
|
// Check validity of format string
|
|
|
|
try
|
|
|
|
FormatDateTime(s, now);
|
|
|
|
except on EConvertError do
|
|
|
|
Result := false;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{ IsTimeFormat checks if the format string s is a time format. isLong is
|
|
|
|
true if the string contains hours, minutes and seconds (two colons).
|
|
|
|
isAMPM is true if the string contains "AM/PM", "A/P" or "AMPM".
|
|
|
|
isMilliSec is true if the string ends with a "z". }
|
|
|
|
function IsTimeFormat(s: String; out isLong, isAMPM, isMillisec: Boolean): Boolean;
|
|
|
|
var
|
|
|
|
p, i, count: Integer;
|
|
|
|
begin
|
|
|
|
// Time parts are separated by a colon
|
|
|
|
p := pos(':', s);
|
|
|
|
isLong := false;
|
|
|
|
isAMPM := false;
|
|
|
|
result := p > 0;
|
|
|
|
|
|
|
|
if Result then begin
|
|
|
|
count := 1;
|
|
|
|
s := Uppercase(s);
|
|
|
|
|
|
|
|
// If there are is a second colon s is a "long" time format
|
|
|
|
for i:=p+1 to Length(s) do
|
|
|
|
if s[i] = ':' then begin
|
|
|
|
isLong := true;
|
|
|
|
break;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Seek for "AM/PM" etc to detect that specific format
|
|
|
|
isAMPM := (pos('AM/PM', s) > 0) or (pos('A/P', s) > 0) or (pos('AMPM', s) > 0);
|
|
|
|
|
|
|
|
// Look for the "milliseconds" character z
|
|
|
|
isMilliSec := (s[Length(s)] = 'Z');
|
|
|
|
|
|
|
|
// Check validity of format string
|
|
|
|
try
|
|
|
|
FormatDateTime(s, now);
|
|
|
|
except on EConvertError do
|
|
|
|
Result := false;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2009-06-09 21:34:58 +00:00
|
|
|
end.
|
|
|
|
|