You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6429 lines
146 KiB
6429 lines
146 KiB
/* Swinder - Portable library for spreadsheet
|
|
Copyright (C) 2003-2006 Ariya Hidayat <ariya@kde.org>
|
|
Copyright (C) 2006 Marijn Kruisselbrink <m.kruisselbrink@student.tue.nl>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 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
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include "excel.h"
|
|
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
#include <vector>
|
|
#include <string>
|
|
#include <map>
|
|
#include <stdio.h> // memcpy
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "pole.h"
|
|
#include "swinder.h"
|
|
|
|
// Use anonymous namespace to cover following functions
|
|
namespace{
|
|
|
|
static inline unsigned long readU16( const void* p )
|
|
{
|
|
const unsigned char* ptr = (const unsigned char*) p;
|
|
return ptr[0]+(ptr[1]<<8);
|
|
}
|
|
|
|
static inline int readI16( const void* p )
|
|
{
|
|
const unsigned char* ptr = (const unsigned char*) p;
|
|
unsigned int v = ptr[0]+(ptr[1]<<8);
|
|
if(v > 32768) v -= 65536;
|
|
return v;
|
|
}
|
|
|
|
static inline unsigned long readU32( const void* p )
|
|
{
|
|
const unsigned char* ptr = (const unsigned char*) p;
|
|
return ptr[0]+(ptr[1]<<8)+(ptr[2]<<16)+(ptr[3]<<24);
|
|
}
|
|
|
|
typedef double& data_64;
|
|
inline void convert_64 (data_64 convert)
|
|
{
|
|
unsigned char temp;
|
|
unsigned int u_int_temp;
|
|
temp = ((unsigned char*)&convert)[0];
|
|
((unsigned char*)&convert)[0] = ((unsigned char*)&convert)[3];
|
|
((unsigned char*)&convert)[3] = temp;
|
|
temp = ((unsigned char*)&convert)[1];
|
|
((unsigned char*)&convert)[1] = ((unsigned char*)&convert)[2];
|
|
((unsigned char*)&convert)[2] = temp;
|
|
temp = ((unsigned char*)&convert)[4];
|
|
((unsigned char*)&convert)[4] = ((unsigned char*)&convert)[7];
|
|
((unsigned char*)&convert)[7] = temp;
|
|
temp = ((unsigned char*)&convert)[5];
|
|
((unsigned char*)&convert)[5] = ((unsigned char*)&convert)[6];
|
|
((unsigned char*)&convert)[6] = temp;
|
|
|
|
u_int_temp = ((unsigned int *)&convert)[0];
|
|
((unsigned int *)&convert)[0] = ((unsigned int *)&convert)[1];
|
|
((unsigned int *)&convert)[1] = u_int_temp;
|
|
}
|
|
|
|
inline bool isLittleEndian(void)
|
|
{
|
|
long i = 0x44332211;
|
|
unsigned char* a = (unsigned char*) &i;
|
|
return ( *a == 0x11 );
|
|
}
|
|
|
|
|
|
// FIXME check that double is 64 bits
|
|
static inline double readFloat64( const void*p )
|
|
{
|
|
const double* ptr = (const double*) p;
|
|
double num = 0.0;
|
|
num = *ptr;
|
|
|
|
if( !isLittleEndian() )
|
|
convert_64( num );
|
|
|
|
return num;
|
|
}
|
|
|
|
// RK value is special encoded integer or floating-point
|
|
// see any documentation of Excel file format for detail description
|
|
static inline void decodeRK( unsigned rkvalue, bool& isInteger,
|
|
int& intResult, double& floatResult )
|
|
{
|
|
bool div100 = rkvalue & 0x01;
|
|
isInteger = rkvalue & 0x02;
|
|
|
|
if( isInteger )
|
|
{
|
|
// FIXME check that int is 32 bits ?
|
|
intResult = *((int*) &rkvalue) >> 2;
|
|
|
|
// divide by 100, fall to floating-point
|
|
if(div100)
|
|
{
|
|
isInteger = false;
|
|
floatResult = (double)intResult / 100.0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO ensure double takes 8 bytes
|
|
unsigned char* s = (unsigned char*) &rkvalue;
|
|
unsigned char* r = (unsigned char*) &floatResult;
|
|
if( isLittleEndian() )
|
|
{
|
|
r[0] = r[1] = r[2] = r[3] = 0;
|
|
r[4] = s[0] & 0xfc;
|
|
r[5] = s[1]; r[6] = s[2]; r[7] = s[3];
|
|
}
|
|
else
|
|
{
|
|
r[0] = r[1] = r[2] = r[3] = 0;
|
|
r[4] = s[0] & 0xfc;
|
|
r[5] = s[1]; r[6] = s[2]; r[7] = s[3];
|
|
}
|
|
memcpy( &floatResult, r, 8 );
|
|
|
|
if( div100 )
|
|
floatResult *= 0.01;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
namespace Swinder
|
|
{
|
|
std::ostream& operator<<( std::ostream& s, Swinder::UString ustring )
|
|
{
|
|
char* str = ustring.ascii();
|
|
s << str;
|
|
return s;
|
|
}
|
|
|
|
}
|
|
|
|
using namespace Swinder;
|
|
|
|
static Value errorAsValue( int errorCode )
|
|
{
|
|
Value result( Value::Error );
|
|
|
|
switch( errorCode )
|
|
{
|
|
case 0x00: result = Value::errorNULL(); break;
|
|
case 0x07: result = Value::errorDIV0(); break;
|
|
case 0x0f: result = Value::errorVALUE(); break;
|
|
case 0x17: result = Value::errorREF(); break;
|
|
case 0x1d: result = Value::errorNAME(); break;
|
|
case 0x24: result = Value::errorNUM(); break;
|
|
case 0x2A: result = Value::errorNA(); break;
|
|
default: break;
|
|
};
|
|
|
|
return result;
|
|
}
|
|
|
|
//=============================================
|
|
// EString
|
|
//=============================================
|
|
|
|
|
|
class EString::Private
|
|
{
|
|
public:
|
|
bool unicode;
|
|
bool richText;
|
|
UString str;
|
|
unsigned size;
|
|
};
|
|
|
|
EString::EString()
|
|
{
|
|
d = new EString::Private();
|
|
d->unicode = false;
|
|
d->richText = false;
|
|
d->str = UString::null;
|
|
d->size = 0;
|
|
}
|
|
|
|
EString::EString( const EString& es )
|
|
{
|
|
d = new EString::Private();
|
|
operator=( es );
|
|
}
|
|
|
|
EString& EString::operator=( const EString& es )
|
|
{
|
|
d->unicode = es.d->unicode;
|
|
d->richText = es.d->richText;
|
|
d->size = es.d->size;
|
|
d->str = es.d->str;
|
|
return *this;
|
|
}
|
|
|
|
EString::~EString()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
bool EString::unicode() const
|
|
{
|
|
return d->unicode;
|
|
}
|
|
|
|
void EString::setUnicode( bool u )
|
|
{
|
|
d->unicode = u;
|
|
}
|
|
|
|
bool EString::richText() const
|
|
{
|
|
return d->richText;
|
|
}
|
|
|
|
void EString::setRichText( bool r )
|
|
{
|
|
d->richText = r;
|
|
}
|
|
|
|
UString EString::str() const
|
|
{
|
|
return d->str;
|
|
}
|
|
|
|
void EString::setStr( const UString& str )
|
|
{
|
|
d->str = str;
|
|
}
|
|
|
|
unsigned EString::size() const
|
|
{
|
|
return d->size;
|
|
}
|
|
|
|
void EString::setSize( unsigned s )
|
|
{
|
|
d->size = s;
|
|
}
|
|
|
|
// FIXME use maxsize for sanity check
|
|
EString EString::fromUnicodeString( const void* p, bool longString, unsigned /* maxsize */ )
|
|
{
|
|
const unsigned char* data = (const unsigned char*) p;
|
|
UString str = UString::null;
|
|
|
|
unsigned offset = longString ? 2 : 1;
|
|
unsigned len = longString ? readU16( data ): data[0];
|
|
unsigned char flag = data[ offset ];
|
|
offset++; // for flag (1 byte)
|
|
|
|
bool unicode = flag & 0x01;
|
|
bool richText = flag & 0x08;
|
|
unsigned formatRuns = 0;
|
|
|
|
if( richText )
|
|
{
|
|
formatRuns = readU16( data + offset );
|
|
offset += 2;
|
|
}
|
|
|
|
// find out total bytes used in this string
|
|
unsigned size = offset + len; // string data
|
|
if( unicode ) size += len; // because unicode takes 2-bytes char
|
|
if( richText ) size += (formatRuns*4);
|
|
|
|
if( !unicode )
|
|
{
|
|
char* buffer = new char[ len+1 ];
|
|
memcpy( buffer, data + offset, len );
|
|
buffer[ len ] = 0;
|
|
str = UString( buffer );
|
|
delete[] buffer;
|
|
}
|
|
else
|
|
{
|
|
str = UString();
|
|
str.reserve(len);
|
|
for( unsigned k=0; k<len; k++ )
|
|
str.append( readU16( data + offset + k*2 ) );
|
|
}
|
|
|
|
EString result;
|
|
result.setUnicode( unicode );
|
|
result.setRichText( richText );
|
|
result.setSize( size );
|
|
result.setStr( str );
|
|
|
|
return result;
|
|
}
|
|
|
|
// FIXME use maxsize for sanity check
|
|
EString EString::fromByteString( const void* p, bool longString,
|
|
unsigned /* maxsize */ )
|
|
{
|
|
const unsigned char* data = (const unsigned char*) p;
|
|
UString str = UString::null;
|
|
|
|
unsigned offset = longString ? 2 : 1;
|
|
unsigned len = longString ? readU16( data ): data[0];
|
|
|
|
char* buffer = new char[ len+1 ];
|
|
memcpy( buffer, data + offset, len );
|
|
buffer[ len ] = 0;
|
|
str = UString( buffer );
|
|
delete[] buffer;
|
|
|
|
unsigned size = offset + len;
|
|
|
|
EString result;
|
|
result.setUnicode( false );
|
|
result.setRichText( false );
|
|
result.setSize( size );
|
|
result.setStr( str );
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
// why different ? see BoundSheetRecord
|
|
EString EString::fromSheetName( const void* p, unsigned datasize )
|
|
{
|
|
const unsigned char* data = (const unsigned char*) p;
|
|
UString str = UString::null;
|
|
|
|
bool richText = false;
|
|
// unsigned formatRuns = 0;
|
|
|
|
unsigned len = data[0];
|
|
unsigned flag = data[1];
|
|
bool unicode = flag & 1;
|
|
|
|
if( len > datasize-2 ) len = datasize-2;
|
|
if( len == 0 ) return EString();
|
|
|
|
unsigned offset = 2;
|
|
|
|
if( !unicode )
|
|
{
|
|
char* buffer = new char[ len+1 ];
|
|
memcpy( buffer, data + offset, len );
|
|
buffer[ len ] = 0;
|
|
str = UString( buffer );
|
|
delete[] buffer;
|
|
}
|
|
else
|
|
{
|
|
for( unsigned k=0; k<len; k++ )
|
|
{
|
|
unsigned uch = readU16( data + offset + k*2 );
|
|
str.append( UChar(uch) );
|
|
}
|
|
}
|
|
|
|
EString result;
|
|
result.setUnicode( unicode );
|
|
result.setRichText( richText );
|
|
result.setSize( datasize );
|
|
result.setStr( str );
|
|
|
|
return result;
|
|
}
|
|
|
|
//=============================================
|
|
// FormulaToken
|
|
//=============================================
|
|
|
|
class FormulaToken::Private
|
|
{
|
|
public:
|
|
unsigned ver;
|
|
unsigned id;
|
|
std::vector<unsigned char> data;
|
|
};
|
|
|
|
FormulaToken::FormulaToken()
|
|
{
|
|
d = new Private;
|
|
d->ver = Excel97;
|
|
d->id = Unused;
|
|
}
|
|
|
|
FormulaToken::FormulaToken( unsigned t )
|
|
{
|
|
d = new Private;
|
|
d->ver = Excel97;
|
|
d->id = t;
|
|
}
|
|
|
|
FormulaToken::FormulaToken( const FormulaToken& token )
|
|
{
|
|
d = new Private;
|
|
d->ver = token.d->ver;
|
|
d->id = token.id();
|
|
|
|
d->data.resize( token.d->data.size() );
|
|
for( unsigned i = 0; i < d->data.size(); i++ )
|
|
d->data[i] = token.d->data[i];
|
|
}
|
|
|
|
FormulaToken::~FormulaToken()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
unsigned FormulaToken::version() const
|
|
{
|
|
return d->ver;
|
|
}
|
|
|
|
void FormulaToken::setVersion( unsigned v )
|
|
{
|
|
d->ver = v;
|
|
}
|
|
|
|
unsigned FormulaToken::id() const
|
|
{
|
|
return d->id;
|
|
}
|
|
|
|
const char* FormulaToken::idAsString() const
|
|
{
|
|
const char* s = 0;
|
|
|
|
switch( d->id )
|
|
{
|
|
case Matrix: s = "Matrix"; break;
|
|
case Table: s = "Table"; break;
|
|
case Add: s = "Add"; break;
|
|
case Sub: s = "Sub"; break;
|
|
case Mul: s = "Mul"; break;
|
|
case Div: s = "Div"; break;
|
|
case Power: s = "Power"; break;
|
|
case Concat: s = "Concat"; break;
|
|
case LT: s = "LT"; break;
|
|
case LE: s = "LE"; break;
|
|
case EQ: s = "EQ"; break;
|
|
case GE: s = "GE"; break;
|
|
case GT: s = "GT"; break;
|
|
case NE: s = "NE"; break;
|
|
case Intersect: s = "Intersect"; break;
|
|
case List: s = "List"; break;
|
|
case Range: s = "Range"; break;
|
|
case UPlus: s = "UPlus"; break;
|
|
case UMinus: s = "UMinus"; break;
|
|
case Percent: s = "Percent"; break;
|
|
case Paren: s = "Paren"; break;
|
|
case String: s = "String"; break;
|
|
case MissArg: s = "MissArg"; break;
|
|
case ErrorCode: s = "ErrorCode"; break;
|
|
case Bool: s = "Bool"; break;
|
|
case Integer: s = "Integer"; break;
|
|
case Array: s = "Array"; break;
|
|
case Function: s = "Function"; break;
|
|
case FunctionVar: s = "FunctionVar"; break;
|
|
case Name: s = "Name"; break;
|
|
case Ref: s = "Ref"; break;
|
|
case RefErr: s = "RefErr"; break;
|
|
case RefN: s = "RefN"; break;
|
|
case Area: s = "Area"; break;
|
|
case AreaErr: s = "AreaErr"; break;
|
|
case AreaN: s = "AreaN"; break;
|
|
case NameX: s = "NameX"; break;
|
|
case Ref3d: s = "Ref3d"; break;
|
|
case RefErr3d: s = "RefErr3d"; break;
|
|
case Float: s = "Float"; break;
|
|
case Area3d: s = "Area3d"; break;
|
|
case AreaErr3d: s = "AreaErr3d"; break;
|
|
default: s = "Unknown"; break;
|
|
};
|
|
|
|
return s;
|
|
}
|
|
|
|
|
|
unsigned FormulaToken::size() const
|
|
{
|
|
unsigned s = 0; // on most cases no data
|
|
|
|
switch( d->id )
|
|
{
|
|
case Add:
|
|
case Sub:
|
|
case Mul:
|
|
case Div:
|
|
case Power:
|
|
case Concat:
|
|
case LT:
|
|
case LE:
|
|
case EQ:
|
|
case GE:
|
|
case GT:
|
|
case NE:
|
|
case Intersect:
|
|
case List:
|
|
case Range:
|
|
case UPlus:
|
|
case UMinus:
|
|
case Percent:
|
|
case Paren:
|
|
case MissArg:
|
|
s = 0; break;
|
|
|
|
case Attr:
|
|
s = 3; break;
|
|
|
|
case ErrorCode:
|
|
case Bool:
|
|
s = 1; break;
|
|
|
|
case Integer:
|
|
s = 2; break;
|
|
|
|
case Array:
|
|
s = 7; break;
|
|
|
|
case Function:
|
|
s = 2;
|
|
break;
|
|
|
|
case FunctionVar:
|
|
s = 3;
|
|
break;
|
|
|
|
case Matrix:
|
|
case Table:
|
|
s = (d->ver == Excel97) ? 4 : 3;
|
|
break;
|
|
|
|
case Name:
|
|
s = (d->ver == Excel97) ? 4 : 14;
|
|
break;
|
|
|
|
case Ref:
|
|
case RefErr:
|
|
case RefN:
|
|
s = (d->ver == Excel97) ? 4 : 3;
|
|
break;
|
|
|
|
case Area:
|
|
case AreaErr:
|
|
case AreaN:
|
|
s = (d->ver == Excel97) ? 8 : 6;
|
|
break;
|
|
|
|
case NameX:
|
|
s = (d->ver == Excel97) ? 6 : 24;
|
|
break;
|
|
|
|
case Ref3d:
|
|
case RefErr3d:
|
|
s = (d->ver == Excel97) ? 6 : 17;
|
|
break;
|
|
|
|
case Float:
|
|
s = 8; break;
|
|
|
|
case Area3d:
|
|
case AreaErr3d:
|
|
s = (d->ver == Excel97) ? 10 : 20;
|
|
break;
|
|
|
|
default:
|
|
// WARNING this is unhandled case
|
|
break;
|
|
};
|
|
|
|
return s;
|
|
}
|
|
|
|
void FormulaToken::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
d->data.resize( size );
|
|
for( unsigned i = 0; i < size; i++ )
|
|
d->data[i] = data[i];
|
|
}
|
|
|
|
Value FormulaToken::value() const
|
|
{
|
|
// sentinel
|
|
if(d->data.size() == 0)
|
|
return Value::empty();
|
|
|
|
Value result;
|
|
|
|
unsigned char* buf;
|
|
buf = new unsigned char[d->data.size()];
|
|
for( unsigned k=0; k<d->data.size(); k++ )
|
|
buf[k] = d->data[k];
|
|
|
|
// FIXME sanity check: verify size of data
|
|
switch( d->id )
|
|
{
|
|
case ErrorCode:
|
|
result = errorAsValue( buf[0] );
|
|
break;
|
|
|
|
case Bool:
|
|
result = Value( buf[0]!=0 );
|
|
break;
|
|
|
|
case Integer:
|
|
result = Value( (int)readU16( buf ) );
|
|
break;
|
|
|
|
case Float:
|
|
result = Value( readFloat64( buf ) );
|
|
break;
|
|
|
|
case String:
|
|
{
|
|
EString estr = (version()==Excel97) ?
|
|
EString::fromUnicodeString( buf, false, d->data.size() ) :
|
|
EString::fromByteString( buf, false, d->data.size() );
|
|
result = Value( estr.str() );
|
|
}
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
|
|
delete [] buf;
|
|
|
|
return result;
|
|
}
|
|
|
|
unsigned FormulaToken::functionIndex() const
|
|
{
|
|
// FIXME check data size
|
|
unsigned index = 0;
|
|
unsigned char buf[2];
|
|
|
|
if( d->id == Function )
|
|
{
|
|
buf[0] = d->data[0];
|
|
buf[1] = d->data[1];
|
|
index = readU16( buf );
|
|
}
|
|
|
|
if( d->id == FunctionVar )
|
|
{
|
|
buf[0] = d->data[1];
|
|
buf[1] = d->data[2];
|
|
index = readU16( buf );
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
struct FunctionEntry
|
|
{
|
|
const char *name;
|
|
int params;
|
|
};
|
|
|
|
static const FunctionEntry FunctionEntries[] =
|
|
{
|
|
{ "COUNT", 1 }, // 0
|
|
{ "IF", 0 }, // 1
|
|
{ "ISNV", 1 }, // 2
|
|
{ "ISERROR", 1 }, // 3
|
|
{ "SUM", 0 }, // 4
|
|
{ "AVERAGE", 0 }, // 5
|
|
{ "MIN", 0 }, // 6
|
|
{ "MAX", 0 }, // 7
|
|
{ "ROW", 0 }, // 8
|
|
{ "COLUMN", 0 }, // 9
|
|
{ "NOVALUE", 0 }, // 10
|
|
{ "NPV", 0 }, // 11
|
|
{ "STDEV", 0 }, // 12
|
|
{ "DOLLAR", 0 }, // 13
|
|
{ "FIXED", 0 }, // 14
|
|
{ "SIN", 1 }, // 15
|
|
{ "COS", 1 }, // 16
|
|
{ "TAN", 1 }, // 17
|
|
{ "ATAN", 1 }, // 18
|
|
{ "PI", 0 }, // 19
|
|
{ "SQRT", 1 }, // 20
|
|
{ "EXP", 1 }, // 21
|
|
{ "LN", 1 }, // 22
|
|
{ "LOG10", 1 }, // 23
|
|
{ "ABS", 1 }, // 24
|
|
{ "INT", 1 }, // 25
|
|
{ "SIGN", 1 }, // 26
|
|
{ "ROUND", 2 }, // 27
|
|
{ "LOOKUP", 0 }, // 28
|
|
{ "INDEX", 0 }, // 29
|
|
{ "REPT", 2 }, // 30
|
|
{ "MID", 3 }, // 31
|
|
{ "LEN", 1 }, // 32
|
|
{ "VALUE", 1 }, // 33
|
|
{ "TRUE", 0 }, // 34
|
|
{ "FALSE", 0 }, // 35
|
|
{ "AND", 0 }, // 36
|
|
{ "OR", 0 }, // 37
|
|
{ "NOT", 1 }, // 38
|
|
{ "MOD", 2 }, // 39
|
|
{ "DCOUNT", 3 }, // 40
|
|
{ "DSUM", 3 }, // 41
|
|
{ "DAVERAGE", 3 }, // 42
|
|
{ "DMIN", 3 }, // 43
|
|
{ "DMAX", 3 }, // 44
|
|
{ "DSTDEV", 3 }, // 45
|
|
{ "VAR", 0 }, // 46
|
|
{ "DVAR", 3 }, // 47
|
|
{ "TEXT", 2 }, // 48
|
|
{ "LINEST", 0 }, // 49
|
|
{ "TREND", 0 }, // 50
|
|
{ "LOGEST", 0 }, // 51
|
|
{ "GROWTH", 0 }, // 52
|
|
{ "GOTO", 0 }, // 53
|
|
{ "HALT", 0 }, // 54
|
|
{ "Unknown55", 0 }, // 55
|
|
{ "PV", 0 }, // 56
|
|
{ "FV", 0 }, // 57
|
|
{ "NPER", 0 }, // 58
|
|
{ "PMT", 0 }, // 59
|
|
{ "RATE", 0 }, // 60
|
|
{ "MIRR", 3 }, // 61
|
|
{ "IRR", 0 }, // 62
|
|
{ "RAND", 0 }, // 63
|
|
{ "MATCH", 0 }, // 64
|
|
{ "DATE", 3 }, // 65
|
|
{ "TIME", 3 }, // 66
|
|
{ "DAY", 1 }, // 67
|
|
{ "MONTH", 1 }, // 68
|
|
{ "YEAR", 1 }, // 69
|
|
{ "DAYOFWEEK", 0 }, // 70
|
|
{ "HOUR", 1 }, // 71
|
|
{ "MIN", 1 }, // 72
|
|
{ "SEC", 1 }, // 73
|
|
{ "NOW", 0 }, // 74
|
|
{ "AREAS", 1 }, // 75
|
|
{ "ROWS", 1 }, // 76
|
|
{ "COLUMNS", 1 }, // 77
|
|
{ "OFFSET", 0 }, // 78
|
|
{ "ABSREF", 2 }, // 79
|
|
{ "RELREF", 0 }, // 80
|
|
{ "ARGUMENT", 0 }, // 81
|
|
{ "SEARCH", 0 }, // 82
|
|
{ "TRANSPOSE", 1 }, // 83
|
|
{ "ERROR", 0 }, // 84
|
|
{ "STEP", 0 }, // 85
|
|
{ "TYPE", 1 }, // 86
|
|
{ "ECHO", 0 },
|
|
{ "SETNAME", 0 },
|
|
{ "CALLER", 0 },
|
|
{ "DEREF", 0 },
|
|
{ "WINDOWS", 0 },
|
|
{ "SERIES", 4 },
|
|
{ "DOCUMENTS", 0 },
|
|
{ "ACTIVECELL", 0 },
|
|
{ "SELECTION", 0 },
|
|
{ "RESULT", 0 },
|
|
{ "ATAN2", 2 }, // 97
|
|
{ "ASIN", 1 }, // 98
|
|
{ "ACOS", 1 }, // 99
|
|
{ "CHOOSE", 0 }, // 100
|
|
{ "HLOOKUP", 0 }, // 101
|
|
{ "VLOOKUP", 0 }, // 102
|
|
{ "LINKS", 0 },
|
|
{ "INPUT", 0 },
|
|
{ "ISREF", 1 }, // 105
|
|
{ "GETFORMULA", 0 },
|
|
{ "GETNAME", 0 },
|
|
{ "SETVALUE", 0 },
|
|
{ "LOG", 0 }, // 109
|
|
{ "EXEC", 0 },
|
|
{ "CHAR", 1 }, // 111
|
|
{ "LOWER", 1 }, // 112
|
|
{ "UPPER", 1 }, // 113
|
|
{ "PROPER", 1 }, // 114
|
|
{ "LEFT", 0 }, // 115
|
|
{ "RIGHT", 0 }, // 116
|
|
{ "EXACT", 2 }, // 117
|
|
{ "TRIM", 1 }, // 118
|
|
{ "REPLACE", 4 }, // 119
|
|
{ "SUBSTITUTE", 0 }, // 120
|
|
{ "CODE", 1 }, // 121
|
|
{ "NAMES", 0 },
|
|
{ "DIRECTORY", 0 },
|
|
{ "FIND", 0 }, // 124
|
|
{ "CELL", 0 }, // 125
|
|
{ "ISERR", 1 }, // 126
|
|
{ "ISTEXT", 1 }, // 127
|
|
{ "ISNUMBER", 1 }, // 128
|
|
{ "ISBLANK", 1 }, // 129
|
|
{ "T", 1 }, // 130
|
|
{ "N", 1 }, // 131
|
|
{ "FOPEN", 0 },
|
|
{ "FCLOSE", 0 },
|
|
{ "FSIZE", 0 },
|
|
{ "FREADLN", 0 },
|
|
{ "FREAD", 0 },
|
|
{ "FWRITELN", 0 },
|
|
{ "FWRITE", 0 },
|
|
{ "FPOS", 0 },
|
|
{ "DATEVALUE", 1 }, // 140
|
|
{ "TIMEVALUE", 1 }, // 141
|
|
{ "SLN", 3 }, // 142
|
|
{ "SYD", 4 }, // 143
|
|
{ "DDB", 0 }, // 144
|
|
{ "GETDEF", 0 },
|
|
{ "REFTEXT", 0 },
|
|
{ "TEXTREF", 0 },
|
|
{ "INDIRECT", 0 }, // 148
|
|
{ "REGISTER", 0 },
|
|
{ "CALL", 0 },
|
|
{ "ADDBAR", 0 },
|
|
{ "ADDMENU", 0 },
|
|
{ "ADDCOMMAND", 0 },
|
|
{ "ENABLECOMMAND", 0 },
|
|
{ "CHECKCOMMAND", 0 },
|
|
{ "RENAMECOMMAND", 0 },
|
|
{ "SHOWBAR", 0 },
|
|
{ "DELETEMENU", 0 },
|
|
{ "DELETECOMMAND", 0 },
|
|
{ "GETCHARTITEM", 0 },
|
|
{ "DIALOGBOX", 0 },
|
|
{ "CLEAN", 1 }, // 162
|
|
{ "MDETERM", 1 }, // 163
|
|
{ "MINVERSE", 1 }, // 164
|
|
{ "MMULT", 2 }, // 165
|
|
{ "FILES", 0 },
|
|
{ "IPMT", 0 }, // 167
|
|
{ "PPMT", 0 }, // 168
|
|
{ "COUNTA", 0 }, // 169
|
|
{ "CANCELKEY", 1 },
|
|
{ "Unknown171", 0 },
|
|
{ "Unknown172", 0 },
|
|
{ "Unknown173", 0 },
|
|
{ "Unknown174", 0 },
|
|
{ "INITIATE", 0 },
|
|
{ "REQUEST", 0 },
|
|
{ "POKE", 0 },
|
|
{ "EXECUTE", 0 },
|
|
{ "TERMINATE", 0 },
|
|
{ "RESTART", 0 },
|
|
{ "HELP", 0 },
|
|
{ "GETBAR", 0 },
|
|
{ "PRODUCT", 0 }, // 183
|
|
{ "FACT", 1 }, // 184
|
|
{ "GETCELL", 0 },
|
|
{ "GETWORKSPACE", 0 },
|
|
{ "GETWINDOW", 0 },
|
|
{ "GETDOCUMENT", 0 },
|
|
{ "DPRODUCT", 3 }, // 189
|
|
{ "ISNONTEXT", 1 }, // 190
|
|
{ "GETNOTE", 0 },
|
|
{ "NOTE", 0 },
|
|
{ "STDEVP", 0 }, // 193
|
|
{ "VARP", 0 }, // 194
|
|
{ "DSTDEVP", 3 }, // 195
|
|
{ "DVARP", 3 }, // 196
|
|
{ "TRUNC", 0 }, // 197
|
|
{ "ISLOGICAL", 1 }, // 198
|
|
{ "DCOUNTA", 3 }, // 199
|
|
{ "DELETEBAR", 0 },
|
|
{ "UNREGISTER", 0 },
|
|
{ "Unknown202", 0 },
|
|
{ "Unknown203", 0 },
|
|
{ "USDOLLAR", 0 },
|
|
{ "FINDB", 0 },
|
|
{ "SEARCHB", 0 },
|
|
{ "REPLACEB", 0 },
|
|
{ "LEFTB", 0 },
|
|
{ "RIGHTB", 0 },
|
|
{ "MIDB", 0 },
|
|
{ "LENB", 0 },
|
|
{ "ROUNDUP", 2 }, // 212
|
|
{ "ROUNDDOWN", 2 }, // 213
|
|
{ "ASC", 0 },
|
|
{ "DBCS", 0 },
|
|
{ "RANK", 0 }, // 216
|
|
{ "Unknown217", 0 },
|
|
{ "Unknown218", 0 },
|
|
{ "ADDRESS", 0 }, // 219
|
|
{ "GETDIFFDATE360", 0 }, // 220
|
|
{ "CURRENTDATE", 0 }, // 221
|
|
{ "VBD", 0 }, // 222
|
|
{ "Unknown223", 0 },
|
|
{ "Unknown224", 0 },
|
|
{ "Unknown225", 0 },
|
|
{ "Unknown226", 0 },
|
|
{ "MEDIAN", 0 }, // 227
|
|
{ "SUMPRODUCT", 0 }, // 228
|
|
{ "SINH", 1 }, // 229
|
|
{ "COSH", 1 }, // 230
|
|
{ "TANH", 1 }, // 231
|
|
{ "ASINH", 1 }, // 232
|
|
{ "ACOSH", 1 }, // 233
|
|
{ "ATANH", 1 }, // 234
|
|
{ "DGET", 3 }, // 235
|
|
{ "CREATEOBJECT", 0 },
|
|
{ "VOLATILE", 0 },
|
|
{ "LASTERROR", 0 },
|
|
{ "CUSTOMUNDO", 0 },
|
|
{ "CUSTOMREPEAT", 0 },
|
|
{ "FORMULACONVERT", 0 },
|
|
{ "GETLINKINFO", 0 },
|
|
{ "TEXTBOX", 0 },
|
|
{ "INFO", 1 }, // 244
|
|
{ "GROUP", 0 },
|
|
{ "GETOBJECT", 0 },
|
|
{ "DB", 0 }, // 247
|
|
{ "PAUSE", 0 },
|
|
{ "Unknown249", 0 },
|
|
{ "Unknown250", 0 },
|
|
{ "RESUME", 0 },
|
|
{ "FREQUENCY", 2 }, // 252
|
|
{ "ADDTOOLBAR", 0 },
|
|
{ "DELETETOOLBAR", 0 },
|
|
{ "Unknown255", 0 },
|
|
{ "RESETTOOLBAR", 0 },
|
|
{ "EVALUATE", 0 },
|
|
{ "GETTOOLBAR", 0 },
|
|
{ "GETTOOL", 0 },
|
|
{ "SPELLINGCHECK", 0 },
|
|
{ "ERRORTYPE", 1 }, // 261
|
|
{ "APPTITLE", 0 },
|
|
{ "WINDOWTITLE", 0 },
|
|
{ "SAVETOOLBAR", 0 },
|
|
{ "ENABLETOOL", 0 },
|
|
{ "PRESSTOOL", 0 },
|
|
{ "REGISTERID", 0 },
|
|
{ "GETWORKBOOK", 0 },
|
|
{ "AVEDEV", 0 }, // 269
|
|
{ "BETADIST", 0 }, // 270
|
|
{ "GAMMALN", 1 }, // 271
|
|
{ "BETAINV", 0 }, // 272
|
|
{ "BINOMDIST", 4 }, // 273
|
|
{ "CHIDIST", 2 }, // 274
|
|
{ "CHIINV", 2 }, // 275
|
|
{ "COMBIN", 2 }, // 276
|
|
{ "CONFIDENCE", 3 }, // 277
|
|
{ "CRITBINOM", 3 }, // 278
|
|
{ "EVEN", 1 }, // 279
|
|
{ "EXPONDIST", 3 }, // 280
|
|
{ "FDIST", 3 }, // 281
|
|
{ "FINV", 3 }, // 282
|
|
{ "FISHER", 1 }, // 283
|
|
{ "FISHERINV", 1 }, // 284
|
|
{ "FLOOR", 2 }, // 285
|
|
{ "GAMMADIST", 4 }, // 286
|
|
{ "GAMMAINV", 3 }, // 287
|
|
{ "CEIL", 2 }, // 288
|
|
{ "HYPGEOMDIST", 4 }, // 289
|
|
{ "LOGNORMDIST", 3 }, // 290
|
|
{ "LOGINV", 3 }, // 291
|
|
{ "NEGBINOMDIST", 3 }, // 292
|
|
{ "NORMDIST", 4 }, // 293
|
|
{ "NORMSDIST", 1 }, // 294
|
|
{ "NORMINV", 3 }, // 295
|
|
{ "NORMSINV", 1 }, // 296
|
|
{ "STANDARDIZE", 3 }, // 297
|
|
{ "ODD", 1 }, // 298
|
|
{ "PERMUT", 2 }, // 299
|
|
{ "POISSON", 3 }, // 300
|
|
{ "TDIST", 3 }, // 301
|
|
{ "WEIBULL", 4 }, // 302
|
|
{ "SUMXMY2", 2 }, // 303
|
|
{ "SUMX2MY2", 2 }, // 304
|
|
{ "SUMX2DY2", 2 }, // 305
|
|
{ "CHITEST", 2 }, // 306
|
|
{ "CORREL", 2 }, // 307
|
|
{ "COVAR", 2 }, // 308
|
|
{ "FORECAST", 3 }, // 309
|
|
{ "FTEST", 2 }, // 310
|
|
{ "INTERCEPT", 2 }, // 311
|
|
{ "PEARSON", 2 }, // 312
|
|
{ "RSQ", 2 }, // 313
|
|
{ "STEYX", 2 }, // 314
|
|
{ "SLOPE", 2 }, // 315
|
|
{ "TTEST", 4 }, // 316
|
|
{ "PROB", 0 }, // 317
|
|
{ "DEVSQ", 0 }, // 318
|
|
{ "GEOMEAN", 0 }, // 319
|
|
{ "HARMEAN", 0 }, // 320
|
|
{ "SUMSQ", 0 }, // 321
|
|
{ "KURT", 0 }, // 322
|
|
{ "SKEW", 0 }, // 323
|
|
{ "ZTEST", 0 }, // 324
|
|
{ "LARGE", 2 }, // 325
|
|
{ "SMALL", 2 }, // 326
|
|
{ "QUARTILE", 2 }, // 327
|
|
{ "PERCENTILE", 2 }, // 328
|
|
{ "PERCENTRANK", 0 }, // 329
|
|
{ "MODALVALUE", 0 }, // 330
|
|
{ "TRIMMEAN", 2 }, // 331
|
|
{ "TINV", 2 }, // 332
|
|
{ "Unknown333", 0 },
|
|
{ "MOVIECOMMAND", 0 },
|
|
{ "GETMOVIE", 0 },
|
|
{ "CONCATENATE", 0 }, // 336
|
|
{ "POWER", 2 }, // 337
|
|
{ "PIVOTADDDATA", 0 },
|
|
{ "GETPIVOTTABLE", 0 },
|
|
{ "GETPIVOTFIELD", 0 },
|
|
{ "GETPIVOTITEM", 0 },
|
|
{ "RADIANS", 1 }, // 342
|
|
{ "DEGREES", 1 }, // 343
|
|
{ "SUBTOTAL", 0 }, // 344
|
|
{ "SUMIF", 0 }, // 345
|
|
{ "COUNTIF", 2 }, // 346
|
|
{ "COUNTBLANK", 1 }, // 347
|
|
{ "SCENARIOGET", 0 },
|
|
{ "OPTIONSLISTSGET", 0 },
|
|
{ "ISPMT", 4 },
|
|
{ "DATEDIF", 3 },
|
|
{ "DATESTRING", 0 },
|
|
{ "NUMBERSTRING", 0 },
|
|
{ "ROMAN", 0 }, // 354
|
|
{ "OPENDIALOG", 0 },
|
|
{ "SAVEDIALOG", 0 },
|
|
{ "VIEWGET", 0 },
|
|
{ "GETPIVOTDATA", 2 }, // 358
|
|
{ "HYPERLINK", 1 },
|
|
{ "PHONETIC", 0 },
|
|
{ "AVERAGEA", 0 }, // 361
|
|
{ "MAXA", 0 }, // 362
|
|
{ "MINA", 0 }, // 363
|
|
{ "STDEVPA", 0 }, // 364
|
|
{ "VARPA", 0 }, // 365
|
|
{ "STDEVA", 0 }, // 366
|
|
{ "VARA", 0 }, // 367
|
|
};
|
|
|
|
const char* FormulaToken::functionName() const
|
|
{
|
|
if( functionIndex() > 367 ) return 0;
|
|
return FunctionEntries[ functionIndex() ].name;
|
|
}
|
|
|
|
unsigned FormulaToken::functionParams() const
|
|
{
|
|
unsigned params = 0;
|
|
|
|
if( d->id == Function )
|
|
{
|
|
if( functionIndex() > 367 ) return 0;
|
|
params = FunctionEntries[ functionIndex() ].params;
|
|
}
|
|
|
|
if( d->id == FunctionVar )
|
|
{
|
|
params = (unsigned)d->data[0];
|
|
params &= 0x7f;
|
|
}
|
|
|
|
return params;
|
|
}
|
|
|
|
unsigned FormulaToken::attr() const
|
|
{
|
|
unsigned attr = 0;
|
|
if( d->id == Attr )
|
|
{
|
|
attr = (unsigned) d->data[0];
|
|
}
|
|
return attr;
|
|
}
|
|
|
|
unsigned FormulaToken::nameIndex() const
|
|
{
|
|
// FIXME check data size !
|
|
unsigned ni = 0;
|
|
unsigned char buf[2];
|
|
|
|
if( d->id == NameX )
|
|
if( d->ver == Excel97 )
|
|
{
|
|
buf[0] = d->data[2];
|
|
buf[1] = d->data[3];
|
|
ni = readU16( buf );
|
|
}
|
|
|
|
if( d->id == NameX )
|
|
if( d->ver == Excel95 )
|
|
{
|
|
buf[0] = d->data[10];
|
|
buf[1] = d->data[11];
|
|
ni = readU16( buf );
|
|
}
|
|
|
|
return ni;
|
|
}
|
|
|
|
|
|
UString FormulaToken::area( unsigned row, unsigned col ) const
|
|
{
|
|
// sanity check
|
|
if(id() != Area)
|
|
if(id() != Area3d)
|
|
return UString::null;
|
|
|
|
// check data size
|
|
int minsize = 0;
|
|
if((id() == Area3d))
|
|
minsize = (version() == Excel97) ? 10 : 20;
|
|
else if(id() == Area)
|
|
minsize = (version() == Excel97) ? 8 : 6;
|
|
if(d->data.size() < minsize)
|
|
return UString::null;
|
|
|
|
unsigned char buf[2];
|
|
int row1Ref, row2Ref, col1Ref, col2Ref;
|
|
bool row1Relative, col1Relative;
|
|
bool row2Relative, col2Relative;
|
|
|
|
if( version() == Excel97 )
|
|
{
|
|
int ofs = (id() == Area) ? 0 : 2;
|
|
|
|
buf[0] = d->data[ofs];
|
|
buf[1] = d->data[ofs+1];
|
|
row1Ref = readU16( buf );
|
|
|
|
buf[0] = d->data[ofs+2];
|
|
buf[1] = d->data[ofs+3];
|
|
row2Ref = readU16( buf );
|
|
|
|
buf[0] = d->data[ofs+4];
|
|
buf[1] = d->data[ofs+5];
|
|
col1Ref = readU16( buf );
|
|
|
|
buf[0] = d->data[ofs+6];
|
|
buf[1] = d->data[ofs+7];
|
|
col2Ref = readU16( buf );
|
|
|
|
row1Relative = col1Ref & 0x8000;
|
|
col1Relative = col1Ref & 0x4000;
|
|
col1Ref &= 0x3fff;
|
|
|
|
row2Relative = col2Ref & 0x8000;
|
|
col2Relative = col2Ref & 0x4000;
|
|
col2Ref &= 0x3fff;
|
|
}
|
|
else
|
|
{
|
|
int ofs = (id() == Area) ? 0 : 14;
|
|
|
|
buf[0] = d->data[ofs];
|
|
buf[1] = d->data[ofs+1];
|
|
row1Ref = readU16( buf );
|
|
|
|
buf[0] = d->data[ofs+2];
|
|
buf[1] = d->data[ofs+3];
|
|
row2Ref = readU16( buf );
|
|
|
|
buf[0] = d->data[ofs+4];
|
|
buf[1] = 0;
|
|
col1Ref = readU16( buf );
|
|
|
|
buf[0] = d->data[ofs+5];
|
|
buf[1] = 0;
|
|
col2Ref = readU16( buf );
|
|
|
|
row1Relative = row2Ref & 0x8000;
|
|
col1Relative = row2Ref & 0x4000;
|
|
row1Ref &= 0x3fff;
|
|
|
|
row2Relative = row2Ref & 0x8000;
|
|
col2Relative = row2Ref & 0x4000;
|
|
row2Ref &= 0x3fff;
|
|
}
|
|
|
|
UString result;
|
|
|
|
// not critical, just to improve performace
|
|
// see also ref() function below
|
|
result.reserve(40);
|
|
|
|
// normal use
|
|
if( !col1Relative )
|
|
result.append( '$' );
|
|
result.append( Cell::columnLabel( col1Ref ) );
|
|
if( !row1Relative )
|
|
result.append( '$' );
|
|
result.append( UString::number( row1Ref+1 ) );
|
|
result.append( ':' );
|
|
if( !col2Relative )
|
|
result.append( '$' );
|
|
result.append( Cell::columnLabel( col2Ref ) );
|
|
if( !row2Relative )
|
|
result.append( '$' );
|
|
result.append( UString::number( row2Ref+1 ) );
|
|
|
|
return result;
|
|
}
|
|
|
|
UString FormulaToken::ref( unsigned row, unsigned col ) const
|
|
{
|
|
// sanity check
|
|
if(id() != Ref)
|
|
if(id() != Ref3d)
|
|
return UString::null;
|
|
|
|
// FIXME check data size !
|
|
// FIXME handle shared formula
|
|
unsigned char buf[2];
|
|
int rowRef, colRef;
|
|
bool rowRelative, colRelative;
|
|
|
|
if( version() == Excel97 )
|
|
{
|
|
int ofs = (id() == Ref) ? 0 : 2;
|
|
buf[0] = d->data[ofs];
|
|
buf[1] = d->data[ofs+1];
|
|
rowRef = readU16( buf );
|
|
|
|
buf[0] = d->data[ofs+2];
|
|
buf[1] = d->data[ofs+3];
|
|
colRef = readU16( buf );
|
|
|
|
rowRelative = colRef & 0x8000;
|
|
colRelative = colRef & 0x4000;
|
|
colRef &= 0x3fff;
|
|
}
|
|
else
|
|
{
|
|
int ofs = (id() == Ref) ? 0 : 14;
|
|
buf[0] = d->data[ofs];
|
|
buf[1] = d->data[ofs+1];
|
|
rowRef = readU16( buf );
|
|
|
|
buf[0] = d->data[ofs+2];
|
|
buf[1] = 0;
|
|
colRef = readU16( buf );
|
|
|
|
rowRelative = rowRef & 0x8000;
|
|
colRelative = rowRef & 0x4000;
|
|
rowRef &= 0x3fff;
|
|
}
|
|
|
|
UString result;
|
|
|
|
// not critical, just to improve performace
|
|
// absolute column 4294967295, row 4294967295 is "$AATYHWUR$4294967295"
|
|
// (20 characters)
|
|
result.reserve(20);
|
|
|
|
if( !colRelative )
|
|
result.append('$');
|
|
result.append( Cell::columnLabel( colRef ) );
|
|
if( !rowRelative )
|
|
result.append('$');
|
|
result.append( UString::number( rowRef+1 ) );
|
|
|
|
return result;
|
|
}
|
|
|
|
// only when id is Ref3d or Area3d
|
|
unsigned FormulaToken::externSheetRef() const
|
|
{
|
|
if(version() >= Excel97)
|
|
{
|
|
unsigned char buf[2];
|
|
buf[0] = d->data[0];
|
|
buf[1] = d->data[1];
|
|
return readU16(buf);
|
|
}
|
|
else
|
|
{
|
|
unsigned char buf[2];
|
|
buf[0] = d->data[0];
|
|
buf[1] = d->data[1];
|
|
int index = readI16(buf);
|
|
|
|
// negative index means own workbook
|
|
if(index < 0)
|
|
{
|
|
// the real index (absolute value) is one-based
|
|
unsigned ref = -index - 1;
|
|
return ref;
|
|
}
|
|
}
|
|
|
|
return 0; // FIXME is this safe?
|
|
}
|
|
|
|
// only when id is Matrix
|
|
unsigned FormulaToken::refRow() const
|
|
{
|
|
// FIXME check data size !
|
|
unsigned char buf[2];
|
|
|
|
buf[0] = d->data[0];
|
|
buf[1] = d->data[1];
|
|
unsigned ref = readU16(buf);
|
|
|
|
return ref;
|
|
}
|
|
|
|
// only when id is Matrix
|
|
unsigned FormulaToken::refColumn() const
|
|
{
|
|
// FIXME check data size !
|
|
unsigned char buf[2];
|
|
|
|
buf[0] = d->data[2];
|
|
buf[1] = d->data[3];
|
|
unsigned ref = readU16(buf);
|
|
|
|
return ref;
|
|
}
|
|
|
|
|
|
std::ostream& Swinder::operator<<( std::ostream& s, Swinder::FormulaToken token )
|
|
{
|
|
s << std::setw(2) << std::hex << token.id() << std::dec;
|
|
// s << " Size: " << std::dec << token.size();
|
|
s << " ";
|
|
|
|
switch( token.id() )
|
|
{
|
|
case FormulaToken::ErrorCode:
|
|
case FormulaToken::Bool:
|
|
case FormulaToken::Integer:
|
|
case FormulaToken::Float:
|
|
case FormulaToken::String:
|
|
{
|
|
Value v = token.value();
|
|
s << v;
|
|
}
|
|
break;
|
|
|
|
case FormulaToken::Function:
|
|
s << "Function " << token.functionName();
|
|
break;
|
|
|
|
default:
|
|
s << token.idAsString();
|
|
break;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
//=============================================
|
|
// CellInfo
|
|
//=============================================
|
|
|
|
class CellInfo::Private
|
|
{
|
|
public:
|
|
unsigned row;
|
|
unsigned column;
|
|
unsigned xfIndex;
|
|
};
|
|
|
|
CellInfo::CellInfo()
|
|
{
|
|
info = new CellInfo::Private();
|
|
info->row = 0;
|
|
info->column = 0;
|
|
info->xfIndex = 0;
|
|
}
|
|
|
|
CellInfo::~CellInfo()
|
|
{
|
|
delete info;
|
|
}
|
|
|
|
unsigned CellInfo::row() const
|
|
{
|
|
return info->row;
|
|
}
|
|
|
|
void CellInfo::setRow( unsigned r )
|
|
{
|
|
info->row = r;
|
|
}
|
|
|
|
unsigned CellInfo::column() const
|
|
{
|
|
return info->column;
|
|
}
|
|
|
|
void CellInfo::setColumn( unsigned c )
|
|
{
|
|
info->column = c;
|
|
}
|
|
|
|
unsigned CellInfo::xfIndex() const
|
|
{
|
|
return info->xfIndex;
|
|
}
|
|
|
|
void CellInfo::setXfIndex( unsigned i )
|
|
{
|
|
info->xfIndex = i;
|
|
}
|
|
|
|
//=============================================
|
|
// ColumnSpanInfo
|
|
//=============================================
|
|
|
|
class ColumnSpanInfo::Private
|
|
{
|
|
public:
|
|
unsigned firstColumn;
|
|
unsigned lastColumn;
|
|
};
|
|
|
|
ColumnSpanInfo::ColumnSpanInfo()
|
|
{
|
|
spaninfo = new ColumnSpanInfo::Private();
|
|
spaninfo->firstColumn = 0;
|
|
spaninfo->lastColumn = 0;
|
|
}
|
|
|
|
ColumnSpanInfo::~ColumnSpanInfo()
|
|
{
|
|
delete spaninfo;
|
|
}
|
|
|
|
unsigned ColumnSpanInfo::firstColumn() const
|
|
{
|
|
return spaninfo->firstColumn;
|
|
}
|
|
|
|
void ColumnSpanInfo::setFirstColumn( unsigned c )
|
|
{
|
|
spaninfo->firstColumn = c;
|
|
}
|
|
|
|
unsigned ColumnSpanInfo::lastColumn() const
|
|
{
|
|
return spaninfo->lastColumn;
|
|
}
|
|
|
|
void ColumnSpanInfo::setLastColumn( unsigned c )
|
|
{
|
|
spaninfo->lastColumn = c;
|
|
}
|
|
|
|
// ========== base record ==========
|
|
|
|
const unsigned int Record::id = 0; // invalid of-course
|
|
|
|
Record::Record()
|
|
{
|
|
stream_position = 0;
|
|
ver = Excel97;
|
|
}
|
|
|
|
Record::~Record()
|
|
{
|
|
}
|
|
|
|
Record* Record::create( unsigned type )
|
|
{
|
|
Record* record = 0;
|
|
|
|
if( type == BOFRecord::id )
|
|
record = new BOFRecord();
|
|
|
|
else if( type == EOFRecord::id )
|
|
record = new EOFRecord();
|
|
|
|
if( type == BackupRecord::id )
|
|
record = new BackupRecord();
|
|
|
|
if( type == BlankRecord::id )
|
|
record = new BlankRecord();
|
|
|
|
if( type == BoolErrRecord::id )
|
|
record = new BoolErrRecord();
|
|
|
|
if( type == BottomMarginRecord::id )
|
|
record = new BottomMarginRecord();
|
|
|
|
if( type == BoundSheetRecord::id )
|
|
record = new BoundSheetRecord();
|
|
|
|
if( type == CalcModeRecord::id )
|
|
record = new CalcModeRecord();
|
|
|
|
if( type == ColInfoRecord::id )
|
|
record = new ColInfoRecord();
|
|
|
|
if( type == DateModeRecord::id )
|
|
record = new DateModeRecord();
|
|
|
|
if( type == DimensionRecord::id )
|
|
record = new DimensionRecord();
|
|
|
|
if( type == ExternNameRecord::id )
|
|
record = new ExternNameRecord();
|
|
|
|
if( type == ExternSheetRecord::id )
|
|
record = new ExternSheetRecord();
|
|
|
|
else if( type == FilepassRecord::id )
|
|
record = new FilepassRecord();
|
|
|
|
else if( type == FontRecord::id )
|
|
record = new FontRecord();
|
|
|
|
else if( type == FooterRecord::id )
|
|
record = new FooterRecord();
|
|
|
|
else if( type == FormatRecord::id )
|
|
record = new FormatRecord();
|
|
|
|
else if( type == FormulaRecord::id )
|
|
record = new FormulaRecord();
|
|
|
|
else if( type == FormulaRecord::idOld )
|
|
record = new FormulaRecord();
|
|
|
|
else if( type == HeaderRecord::id )
|
|
record = new HeaderRecord();
|
|
|
|
else if( type == LabelRecord::id )
|
|
record = new LabelRecord();
|
|
|
|
else if( type == LabelSSTRecord::id )
|
|
record = new LabelSSTRecord();
|
|
|
|
if( type == LeftMarginRecord::id )
|
|
record = new LeftMarginRecord();
|
|
|
|
else if( type == MergedCellsRecord::id )
|
|
record = new MergedCellsRecord();
|
|
|
|
else if( type == MulBlankRecord::id )
|
|
record = new MulBlankRecord();
|
|
|
|
else if( type == MulRKRecord::id )
|
|
record = new MulRKRecord();
|
|
|
|
if( type == NameRecord::id )
|
|
record = new NameRecord();
|
|
|
|
else if( type == NumberRecord::id )
|
|
record = new NumberRecord();
|
|
|
|
else if( type == PaletteRecord::id )
|
|
record = new PaletteRecord();
|
|
|
|
if( type == RightMarginRecord::id )
|
|
record = new RightMarginRecord();
|
|
|
|
else if( type == RKRecord::id )
|
|
record = new RKRecord();
|
|
|
|
else if( type == RowRecord::id )
|
|
record = new RowRecord();
|
|
|
|
else if( type == RStringRecord::id )
|
|
record = new RStringRecord();
|
|
|
|
else if( type == SSTRecord::id )
|
|
record = new SSTRecord();
|
|
|
|
else if( type == StringRecord::id )
|
|
record = new StringRecord();
|
|
|
|
else if( type == SupbookRecord::id )
|
|
record = new SupbookRecord();
|
|
|
|
else if( type == XFRecord::id )
|
|
record = new XFRecord();
|
|
|
|
else if( type == TopMarginRecord::id )
|
|
record = new TopMarginRecord();
|
|
|
|
return record;
|
|
}
|
|
|
|
void Record::setPosition( unsigned pos )
|
|
{
|
|
stream_position = pos;
|
|
}
|
|
|
|
unsigned Record::position() const
|
|
{
|
|
return stream_position;
|
|
}
|
|
|
|
void Record::setData( unsigned, const unsigned char* )
|
|
{
|
|
}
|
|
|
|
void Record::dump( std::ostream& ) const
|
|
{
|
|
// nothing to dump
|
|
}
|
|
|
|
// ========== BACKUP ==========
|
|
|
|
const unsigned int BackupRecord::id = 0x0040;
|
|
|
|
class BackupRecord::Private
|
|
{
|
|
public:
|
|
bool backup;
|
|
};
|
|
|
|
BackupRecord::BackupRecord():
|
|
Record()
|
|
{
|
|
d = new BackupRecord::Private();
|
|
d->backup = false;
|
|
}
|
|
|
|
BackupRecord::~BackupRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
bool BackupRecord::backup() const
|
|
{
|
|
return d->backup;
|
|
}
|
|
|
|
void BackupRecord::setBackup( bool b )
|
|
{
|
|
d->backup = b;
|
|
}
|
|
|
|
void BackupRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 2 ) return;
|
|
|
|
unsigned flag = readU16( data );
|
|
d->backup = flag != 0;
|
|
}
|
|
|
|
void BackupRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "BACKUP" << std::endl;
|
|
out << " Backup on save : " << (backup() ? "Yes" : "No") << std::endl;
|
|
}
|
|
|
|
// ========== BLANK ==========
|
|
|
|
const unsigned int BlankRecord::id = 0x0201;
|
|
|
|
BlankRecord::BlankRecord():
|
|
Record(), CellInfo()
|
|
{
|
|
}
|
|
|
|
void BlankRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 6 ) return;
|
|
|
|
setRow( readU16( data ) );
|
|
setColumn( readU16( data+2 ) );
|
|
setXfIndex( readU16( data+4 ) );
|
|
}
|
|
|
|
void BlankRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "BLANK" << std::endl;
|
|
out << " Row : " << row() << std::endl;
|
|
out << " Column : " << column() << std::endl;
|
|
out << " XF Index : " << xfIndex() << std::endl;
|
|
}
|
|
|
|
|
|
// ========== BOF ==========
|
|
|
|
const unsigned int BOFRecord::id = 0x0809;
|
|
|
|
// helper class for BOFRecord
|
|
class BOFRecord::Private
|
|
{
|
|
public:
|
|
unsigned version; // 0x0500=Excel95, 0x0600=Excel97, and so on
|
|
unsigned type;
|
|
unsigned build;
|
|
unsigned year;
|
|
unsigned history;
|
|
unsigned rversion;
|
|
};
|
|
|
|
// constructor of BOFRecord
|
|
BOFRecord::BOFRecord():
|
|
Record()
|
|
{
|
|
d = new BOFRecord::Private();
|
|
d->version = 0x600; // BIFF8;
|
|
d->type = 0;
|
|
d->build = 0;
|
|
d->year = 0;
|
|
d->history = 0;
|
|
d->rversion = 0;
|
|
}
|
|
|
|
// destructor of BOFRecord
|
|
BOFRecord::~BOFRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
void BOFRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 4 ) return;
|
|
|
|
d->version = readU16( data );
|
|
d->type = readU16( data+2 );
|
|
if( size > 6 )
|
|
{
|
|
d->build = readU16( data+4 );
|
|
d->year = readU16( data+6);
|
|
if( size > 12 )
|
|
{
|
|
d->history = readU32( data+8 );
|
|
d->rversion = readU32( data+12 );
|
|
}
|
|
}
|
|
}
|
|
|
|
unsigned BOFRecord::version() const
|
|
{
|
|
unsigned ver = UnknownExcel;
|
|
switch( d->version )
|
|
{
|
|
case 0x0500 : ver = Excel95; break;
|
|
case 0x0600 : ver = Excel97; break;
|
|
default: break;
|
|
}
|
|
return ver;
|
|
}
|
|
|
|
const char* BOFRecord::versionAsString() const
|
|
{
|
|
const char *result = "Unknown";
|
|
switch( version() )
|
|
{
|
|
case Excel95 : result = "Excel95"; break;
|
|
case Excel97 : result = "Excel97"; break;
|
|
default: break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
unsigned BOFRecord::type() const
|
|
{
|
|
unsigned result = UnknownType;
|
|
switch( d->type )
|
|
{
|
|
case 0x005 : result = Workbook; break;
|
|
case 0x006 : result = VBModule; break;
|
|
case 0x010 : result = Worksheet; break;
|
|
case 0x020 : result = Chart; break;
|
|
case 0x040 : result = MacroSheet; break;
|
|
case 0x100 : result = Workspace; break;
|
|
default: break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
const char* BOFRecord::typeAsString() const
|
|
{
|
|
const char *result = "Unknown";
|
|
switch( type() )
|
|
{
|
|
case Workbook : result = "Workbook"; break;
|
|
case VBModule : result = "Visual Basic Module"; break;
|
|
case Worksheet : result = "Worksheet"; break;
|
|
case Chart : result = "Chart"; break;
|
|
case MacroSheet : result = "Macro Sheet"; break;
|
|
case Workspace : result = "Workspace File"; break;
|
|
default: break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void BOFRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "BOF" << std::endl;
|
|
out << " Version : 0x" << std::hex << d->version << " (" << versionAsString() << ")" << std::endl;
|
|
out << " Type : 0x" << d->type << " (" << typeAsString() << ")" << std::endl;
|
|
out << " Build : 0x" << d->build << std::endl;
|
|
out << " Year : " << std::dec << d->year << std::endl;
|
|
out << " History : 0x" << std::hex << d->history << std::endl;
|
|
out << " RVersion : 0x" << d->rversion << std::endl;
|
|
out << std::dec;
|
|
}
|
|
|
|
// ========== BOOLERR ==========
|
|
|
|
const unsigned int BoolErrRecord::id = 0x0205;
|
|
|
|
class BoolErrRecord::Private
|
|
{
|
|
public:
|
|
Value value;
|
|
};
|
|
|
|
BoolErrRecord::BoolErrRecord():
|
|
Record(), CellInfo()
|
|
{
|
|
d = new BoolErrRecord::Private();
|
|
d->value = Value( false );
|
|
}
|
|
|
|
BoolErrRecord::~BoolErrRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
void BoolErrRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size != 8 ) return;
|
|
|
|
setRow( readU16( data ) );
|
|
setColumn( readU16( data+2 ) );
|
|
setXfIndex( readU16( data+4 ) );
|
|
|
|
switch( data[7] )
|
|
{
|
|
case 0 :
|
|
d->value = Value( data[6] ? true : false );
|
|
break;
|
|
case 1 :
|
|
d->value = errorAsValue( data[6] );
|
|
break;
|
|
default:
|
|
// bad bad bad
|
|
std::cerr << "Warning: bad BOOLERR record" << std::endl;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Value BoolErrRecord::value() const
|
|
{
|
|
return d->value;
|
|
}
|
|
|
|
void BoolErrRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "BOOLERR" << std::endl;
|
|
out << " Column : " << column() << std::endl;
|
|
out << " Row : " << row() << std::endl;
|
|
out << " XFIndex : " << xfIndex() << std::endl;
|
|
out << " Value : " << value() << std::endl;
|
|
}
|
|
|
|
// ========== BOTTOMMARGIN ==========
|
|
|
|
const unsigned int BottomMarginRecord::id = 0x0029;
|
|
|
|
class BottomMarginRecord::Private
|
|
{
|
|
public:
|
|
double bottomMargin;
|
|
};
|
|
|
|
BottomMarginRecord::BottomMarginRecord():
|
|
Record()
|
|
{
|
|
d = new BottomMarginRecord::Private();
|
|
d->bottomMargin = 1.0;
|
|
}
|
|
|
|
BottomMarginRecord::~BottomMarginRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
double BottomMarginRecord::bottomMargin() const
|
|
{
|
|
return d->bottomMargin;
|
|
}
|
|
|
|
void BottomMarginRecord::setBottomMargin( double m )
|
|
{
|
|
d->bottomMargin = m;
|
|
}
|
|
|
|
void BottomMarginRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 8 ) return;
|
|
setBottomMargin( readFloat64( data ) );
|
|
}
|
|
|
|
void BottomMarginRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "BOTTOMMARGIN" << std::endl;
|
|
out << " Bottom Margin : " << bottomMargin() << " inches" << std::endl;
|
|
}
|
|
|
|
|
|
// ========== BOUNDSHEET ==========
|
|
|
|
const unsigned int BoundSheetRecord::id = 0x0085;
|
|
|
|
// helper class for BoundSheetRecord
|
|
class BoundSheetRecord::Private
|
|
{
|
|
public:
|
|
unsigned type; // 0=Worksheet, 2=Chart, 6=VB Module
|
|
unsigned visibility; // 0=visible, 1=hidden, 2=strong hidden
|
|
UString name;
|
|
unsigned bofPosition;
|
|
};
|
|
|
|
BoundSheetRecord::BoundSheetRecord():
|
|
Record()
|
|
{
|
|
d = new BoundSheetRecord::Private();
|
|
d->type = 0;
|
|
d->visibility = 0;
|
|
d->name = "Sheet";
|
|
}
|
|
|
|
void BoundSheetRecord::setType( unsigned t )
|
|
{
|
|
switch( t )
|
|
{
|
|
case Worksheet: d->type = 0; break;
|
|
case Chart: d->type = 2; break;
|
|
case VBModule: d->type = 6; break;
|
|
default: d->type = 0; break; // fallback
|
|
};
|
|
}
|
|
|
|
unsigned BoundSheetRecord::type() const
|
|
{
|
|
unsigned t = Worksheet;
|
|
switch( d->type )
|
|
{
|
|
case 0: t = Worksheet; break;
|
|
case 2: t = Chart; break;
|
|
case 6: t = VBModule; break;
|
|
default: break;
|
|
};
|
|
return t;
|
|
}
|
|
|
|
const char* BoundSheetRecord::typeAsString() const
|
|
{
|
|
const char *result = "Unknown";
|
|
switch( type() )
|
|
{
|
|
case Worksheet: result = "Worksheet"; break;
|
|
case Chart: result = "Chart"; break;
|
|
case VBModule: result = "Visual Basic Module"; break;
|
|
default: break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void BoundSheetRecord::setVisible( bool v )
|
|
{
|
|
d->visibility = v ? 0 : 1;
|
|
}
|
|
|
|
bool BoundSheetRecord::visible() const
|
|
{
|
|
return d->visibility == 0;
|
|
}
|
|
|
|
void BoundSheetRecord::setSheetName( const UString& n )
|
|
{
|
|
d->name = n;
|
|
}
|
|
|
|
UString BoundSheetRecord::sheetName() const
|
|
{
|
|
return d->name;
|
|
}
|
|
|
|
void BoundSheetRecord::setBofPosition( unsigned pos )
|
|
{
|
|
d->bofPosition = pos;
|
|
}
|
|
|
|
unsigned BoundSheetRecord::bofPosition() const
|
|
{
|
|
return d->bofPosition;
|
|
}
|
|
|
|
BoundSheetRecord::~BoundSheetRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
void BoundSheetRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 6 ) return;
|
|
|
|
d->bofPosition = readU32( data );
|
|
d->visibility = data[4];
|
|
d->type = data[5];
|
|
|
|
/* FIXME: it turned out that sheet name is not normal unicode string
|
|
where the first two bytes specifies string length, but instead
|
|
only the first specifies it.
|
|
the next byte could be correctly interpreted as flag.
|
|
*/
|
|
|
|
UString name = ( version() >= Excel97 ) ?
|
|
EString::fromSheetName( data+6, size-6 ).str() :
|
|
EString::fromByteString( data+6, false, size-6 ).str();
|
|
setSheetName( name );
|
|
}
|
|
|
|
void BoundSheetRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "BOUNDSHEET" << std::endl;
|
|
out << " Name : " << d->name << std::endl;
|
|
out << " Type : " << d->type << " (" << typeAsString() << ")" << std::endl;
|
|
out << " Visibility : " << d->visibility << " (";
|
|
if( visible() ) out << "Visible"; else out << "Hidden"; out << ")" << std::endl;
|
|
out << " BOF pos : " << d->bofPosition << std::endl;
|
|
}
|
|
|
|
// ========== CALCMODE ==========
|
|
|
|
const unsigned int CalcModeRecord::id = 0x000d;
|
|
|
|
class CalcModeRecord::Private
|
|
{
|
|
public:
|
|
bool autoCalc;
|
|
};
|
|
|
|
CalcModeRecord::CalcModeRecord():
|
|
Record()
|
|
{
|
|
d = new CalcModeRecord::Private();
|
|
d->autoCalc = false;
|
|
}
|
|
|
|
CalcModeRecord::~CalcModeRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
bool CalcModeRecord::autoCalc() const
|
|
{
|
|
return d->autoCalc;
|
|
}
|
|
|
|
void CalcModeRecord::setAutoCalc( bool b )
|
|
{
|
|
d->autoCalc = b;
|
|
}
|
|
|
|
void CalcModeRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 2 ) return;
|
|
|
|
unsigned flag = readU16( data );
|
|
d->autoCalc = flag != 0;
|
|
}
|
|
|
|
void CalcModeRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "CALCMODE" << std::endl;
|
|
out << " Auto Calc : " << (autoCalc() ? "Yes" : "No") << std::endl;
|
|
}
|
|
|
|
// ========== COLINFO ==========
|
|
|
|
const unsigned int ColInfoRecord::id = 0x007d;
|
|
|
|
class ColInfoRecord::Private
|
|
{
|
|
public:
|
|
unsigned width;
|
|
unsigned xfIndex;
|
|
bool hidden;
|
|
bool collapsed;
|
|
unsigned outlineLevel;
|
|
};
|
|
|
|
ColInfoRecord::ColInfoRecord():
|
|
Record(), ColumnSpanInfo()
|
|
{
|
|
d = new ColInfoRecord::Private();
|
|
d->width = 2340;
|
|
d->xfIndex = 0;
|
|
d->hidden = false;
|
|
d->collapsed = false;
|
|
d->outlineLevel = 0;
|
|
}
|
|
|
|
ColInfoRecord::~ColInfoRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
// FIXME how to find the real width (in pt/mm/inch) ?
|
|
unsigned ColInfoRecord::width() const
|
|
{
|
|
return d->width;
|
|
}
|
|
|
|
void ColInfoRecord::setWidth( unsigned w )
|
|
{
|
|
d->width = w;
|
|
}
|
|
|
|
unsigned ColInfoRecord::xfIndex() const
|
|
{
|
|
return d->xfIndex;
|
|
}
|
|
|
|
void ColInfoRecord::setXfIndex( unsigned i )
|
|
{
|
|
d->xfIndex = i;
|
|
}
|
|
|
|
bool ColInfoRecord::hidden() const
|
|
{
|
|
return d->hidden;
|
|
}
|
|
|
|
void ColInfoRecord::setHidden( bool h )
|
|
{
|
|
d->hidden = h;
|
|
}
|
|
|
|
bool ColInfoRecord::collapsed() const
|
|
{
|
|
return d->collapsed;
|
|
}
|
|
|
|
void ColInfoRecord::setCollapsed( bool c )
|
|
{
|
|
d->collapsed = c;
|
|
}
|
|
|
|
unsigned ColInfoRecord::outlineLevel() const
|
|
{
|
|
return d->outlineLevel;
|
|
}
|
|
|
|
void ColInfoRecord::setOutlineLevel( unsigned l )
|
|
{
|
|
d->outlineLevel = l;
|
|
}
|
|
|
|
void ColInfoRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 10 ) return;
|
|
|
|
setFirstColumn( readU16( data ) );
|
|
setLastColumn( readU16( data+2 ) );
|
|
setWidth( readU16( data+4 ) );
|
|
setXfIndex( readU16( data+6 ) );
|
|
|
|
unsigned options = readU16( data+8 );
|
|
setHidden ( options & 1 );
|
|
setCollapsed ( options & 0x1000 );
|
|
setOutlineLevel( ( options >> 8 ) & 7 );
|
|
}
|
|
|
|
void ColInfoRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "COLINFO" << std::endl;
|
|
out << " First Column : " << firstColumn() << std::endl;
|
|
out << " Last Column : " << lastColumn() << std::endl;
|
|
out << " Width : " << width() << std::endl;
|
|
out << " XF Index : " << xfIndex() << std::endl;
|
|
out << " Hidden : " << ( hidden() ? "Yes" : "No" ) << std::endl;
|
|
out << " Collapsed : " << ( collapsed() ? "Yes" : "No" ) << std::endl;
|
|
out << " Outline Level : " << outlineLevel() << std::endl;
|
|
}
|
|
|
|
// ========== DATEMODE ==========
|
|
|
|
const unsigned int DateModeRecord::id = 0x0022;
|
|
|
|
class DateModeRecord::Private
|
|
{
|
|
public:
|
|
bool base1904;
|
|
};
|
|
|
|
DateModeRecord::DateModeRecord():
|
|
Record()
|
|
{
|
|
d = new DateModeRecord::Private();
|
|
d->base1904 = false;
|
|
}
|
|
|
|
DateModeRecord::~DateModeRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
bool DateModeRecord::base1904() const
|
|
{
|
|
return d->base1904;
|
|
}
|
|
|
|
void DateModeRecord::setBase1904( bool r )
|
|
{
|
|
d->base1904 = r;
|
|
}
|
|
|
|
void DateModeRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 2 ) return;
|
|
|
|
unsigned flag = readU16( data );
|
|
d->base1904 = flag != 0;
|
|
}
|
|
|
|
void DateModeRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "DATEMODE" << std::endl;
|
|
out << " 1904 base : " << (base1904() ? "Yes" : "No") << std::endl;
|
|
}
|
|
|
|
|
|
// ========== DIMENSION ==========
|
|
|
|
const unsigned int DimensionRecord::id = 0x0200;
|
|
|
|
class DimensionRecord::Private
|
|
{
|
|
public:
|
|
unsigned firstRow;
|
|
unsigned lastRow;
|
|
unsigned firstColumn;
|
|
unsigned lastColumn;
|
|
};
|
|
|
|
DimensionRecord::DimensionRecord():
|
|
Record()
|
|
{
|
|
d = new DimensionRecord::Private;
|
|
d->firstRow = 0;
|
|
d->lastRow = 0;
|
|
d->firstColumn = 0;
|
|
d->lastColumn = 0;
|
|
}
|
|
|
|
DimensionRecord::~DimensionRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
unsigned DimensionRecord::firstRow() const
|
|
{
|
|
return d->firstRow;
|
|
}
|
|
|
|
void DimensionRecord::setFirstRow( unsigned r )
|
|
{
|
|
d->firstRow = r;
|
|
}
|
|
|
|
unsigned DimensionRecord::lastRow() const
|
|
{
|
|
return d->lastRow;
|
|
}
|
|
|
|
void DimensionRecord::setLastRow( unsigned r )
|
|
{
|
|
d->lastRow = r;
|
|
}
|
|
|
|
unsigned DimensionRecord::firstColumn() const
|
|
{
|
|
return d->firstColumn;
|
|
}
|
|
|
|
void DimensionRecord::setFirstColumn( unsigned r )
|
|
{
|
|
d->firstColumn = r;
|
|
}
|
|
|
|
unsigned DimensionRecord::lastColumn() const
|
|
{
|
|
return d->lastColumn;
|
|
}
|
|
|
|
void DimensionRecord::setLastColumn( unsigned r )
|
|
{
|
|
d->lastColumn = r;
|
|
}
|
|
|
|
void DimensionRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 14 ) return;
|
|
|
|
setFirstRow( readU32( data ) );
|
|
setLastRow( readU32( data+4 ) - 1 );
|
|
setFirstColumn( readU16( data + 8 ) );
|
|
setLastColumn( readU16( data + 10 ) - 1 );
|
|
}
|
|
|
|
void DimensionRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "DIMENSION" << std::endl;
|
|
out << " First Row : " << firstRow() << std::endl;
|
|
out << " Last Row : " << lastRow() << std::endl;
|
|
out << " First Column : " << firstColumn() << std::endl;
|
|
out << " Last Column : " << lastColumn() << std::endl;
|
|
}
|
|
|
|
// ========== EOF ==========
|
|
|
|
const unsigned int EOFRecord::id = 0x000a;
|
|
|
|
EOFRecord::EOFRecord():
|
|
Record()
|
|
{
|
|
}
|
|
|
|
EOFRecord::~EOFRecord()
|
|
{
|
|
}
|
|
|
|
void EOFRecord::setData( unsigned, const unsigned char* )
|
|
{
|
|
// no data associated with EOF record
|
|
}
|
|
|
|
void EOFRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "EOF" << std::endl;
|
|
}
|
|
|
|
// ========== EXTERNNAME ==========
|
|
|
|
const unsigned int ExternNameRecord::id = 0x0023;
|
|
|
|
class ExternNameRecord::Private
|
|
{
|
|
public:
|
|
unsigned optionFlags;
|
|
unsigned sheetIndex; // one-based, not zero-based
|
|
UString externName;
|
|
};
|
|
|
|
|
|
ExternNameRecord::ExternNameRecord()
|
|
{
|
|
d = new Private;
|
|
d->optionFlags = 0;
|
|
d->sheetIndex = 0;
|
|
}
|
|
|
|
ExternNameRecord::~ExternNameRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
void ExternNameRecord::setSheetIndex( unsigned sheetIndex )
|
|
{
|
|
d->sheetIndex = sheetIndex;
|
|
}
|
|
|
|
unsigned ExternNameRecord::sheetIndex() const
|
|
{
|
|
return d->sheetIndex;
|
|
}
|
|
|
|
void ExternNameRecord::setExternName( const UString& name )
|
|
{
|
|
d->externName = name;
|
|
}
|
|
|
|
UString ExternNameRecord::externName() const
|
|
{
|
|
return d->externName;
|
|
}
|
|
|
|
void ExternNameRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 6 ) return;
|
|
|
|
if ( version() == Excel97 )
|
|
{
|
|
d->optionFlags = readU16( data );
|
|
d->sheetIndex = readU16( data+2 );
|
|
d->externName = EString::fromUnicodeString( data+6, false, size ).str();
|
|
}
|
|
|
|
if ( version() == Excel95 )
|
|
{
|
|
d->optionFlags = 0;
|
|
d->sheetIndex = 0;
|
|
d->externName = EString::fromByteString( data+6, false, size ).str();
|
|
}
|
|
}
|
|
|
|
void ExternNameRecord::dump( std::ostream& out ) const
|
|
{
|
|
}
|
|
|
|
// ========== EXTERNSHEET ==========
|
|
|
|
const unsigned int ExternSheetRecord::id = 0x0017;
|
|
|
|
class ExternSheetRecord::Private
|
|
{
|
|
public:
|
|
typedef struct
|
|
{
|
|
unsigned index;
|
|
unsigned first;
|
|
unsigned last ;
|
|
} ExternSheetRef;
|
|
std::vector<ExternSheetRef> refs;
|
|
UString refName;
|
|
};
|
|
|
|
ExternSheetRecord::ExternSheetRecord()
|
|
{
|
|
d = new Private;
|
|
}
|
|
|
|
ExternSheetRecord::~ExternSheetRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
unsigned ExternSheetRecord::count() const
|
|
{
|
|
return d->refs.size();
|
|
}
|
|
|
|
unsigned ExternSheetRecord::refIndex(unsigned i) const
|
|
{
|
|
if(i >= d->refs.size()) return 0;
|
|
return d->refs[i].index;
|
|
}
|
|
|
|
unsigned ExternSheetRecord::firstSheet(unsigned i) const
|
|
{
|
|
if(i >= d->refs.size()) return 0;
|
|
return d->refs[i].first;
|
|
}
|
|
|
|
unsigned ExternSheetRecord::lastSheet(unsigned i) const
|
|
{
|
|
if(i >= d->refs.size()) return 0;
|
|
return d->refs[i].last;
|
|
}
|
|
|
|
UString ExternSheetRecord::refName() const
|
|
{
|
|
return d->refName;
|
|
}
|
|
|
|
void ExternSheetRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
d->refs.clear();
|
|
d->refName = UString::null;
|
|
|
|
// sanity
|
|
if(size < 2) return;
|
|
|
|
if(version() >= Excel97)
|
|
{
|
|
// don't really trust this
|
|
unsigned c = readU16(data);
|
|
|
|
unsigned ofs = 2;
|
|
for(unsigned i = 0; i < c; i++, ofs+=6)
|
|
{
|
|
// sanity check
|
|
if(ofs + 6 > size) break;
|
|
|
|
ExternSheetRecord::Private::ExternSheetRef ref;
|
|
ref.index = readU16(data+ofs);
|
|
ref.first = readU16(data+ofs+2);
|
|
ref.last = readU16(data+ofs+4);
|
|
|
|
d->refs.push_back(ref);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
unsigned char dtype = data[1];
|
|
unsigned dlen = (unsigned) data[0];
|
|
|
|
if(dtype == 3)
|
|
{
|
|
UString url;
|
|
url.reserve(dlen);
|
|
for(int i = 0; i < dlen; i++)
|
|
{
|
|
if(i + 2 > size) break;
|
|
char ch = data[i + 2];
|
|
if(ch >= 32)
|
|
url.append(ch);
|
|
}
|
|
d->refName = url;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ExternSheetRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "EXTERNSHEET" << std::endl;
|
|
}
|
|
|
|
// ========== FILEPASS ==========
|
|
|
|
const unsigned int FilepassRecord::id = 0x002f;
|
|
|
|
FilepassRecord::FilepassRecord():
|
|
Record()
|
|
{
|
|
}
|
|
|
|
FilepassRecord::~FilepassRecord()
|
|
{
|
|
}
|
|
|
|
void FilepassRecord::setData( unsigned, const unsigned char* )
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
void FilepassRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "FILEPASS" << std::endl;
|
|
}
|
|
|
|
// ========== FONT ==========
|
|
|
|
const unsigned int FontRecord::id = 0x0031;
|
|
|
|
class FontRecord::Private
|
|
{
|
|
public:
|
|
unsigned height;
|
|
UString fontName;
|
|
unsigned fontFamily;
|
|
unsigned characterSet;
|
|
unsigned colorIndex;
|
|
unsigned boldness;
|
|
bool italic;
|
|
bool strikeout;
|
|
unsigned escapement;
|
|
unsigned underline;
|
|
};
|
|
|
|
FontRecord::FontRecord(): Record()
|
|
{
|
|
d = new FontRecord::Private;
|
|
d->height = 11;
|
|
d->fontName = "Arial";
|
|
d->fontFamily = 0;
|
|
d->characterSet = 0;
|
|
d->colorIndex = 0;
|
|
d->boldness = 400;
|
|
d->italic = false;
|
|
d->strikeout = false;
|
|
d->escapement = Normal;
|
|
d->underline = None;
|
|
}
|
|
|
|
FontRecord::~FontRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
FontRecord::FontRecord( const FontRecord& ef ): Record()
|
|
{
|
|
d = new FontRecord::Private;
|
|
operator=( ef );
|
|
}
|
|
|
|
FontRecord& FontRecord::operator=( const FontRecord& ef )
|
|
{
|
|
d->height = ef.height();
|
|
d->fontName = ef.fontName();
|
|
d->fontFamily = ef.fontFamily();
|
|
d->characterSet = ef.characterSet();
|
|
d->boldness = ef.boldness();
|
|
d->italic = ef.italic();
|
|
d->strikeout = ef.strikeout();
|
|
d->escapement = ef.escapement();
|
|
d->underline = ef.underline();
|
|
d->colorIndex = ef.colorIndex();
|
|
return *this;
|
|
}
|
|
|
|
unsigned FontRecord::height() const
|
|
{
|
|
return d->height;
|
|
}
|
|
|
|
void FontRecord::setHeight( unsigned h )
|
|
{
|
|
d->height = h;
|
|
}
|
|
|
|
UString FontRecord::fontName() const
|
|
{
|
|
return d->fontName;
|
|
}
|
|
|
|
void FontRecord::setFontName( const UString& fn )
|
|
{
|
|
d->fontName = fn;
|
|
}
|
|
|
|
unsigned FontRecord::fontFamily() const
|
|
{
|
|
return d->fontFamily;
|
|
}
|
|
|
|
void FontRecord::setFontFamily( unsigned f )
|
|
{
|
|
d->fontFamily = f;
|
|
}
|
|
|
|
unsigned FontRecord::characterSet() const
|
|
{
|
|
return d->characterSet;
|
|
}
|
|
|
|
void FontRecord::setCharacterSet( unsigned cs )
|
|
{
|
|
d->characterSet = cs;
|
|
}
|
|
|
|
unsigned FontRecord::colorIndex() const
|
|
{
|
|
return d->colorIndex;
|
|
}
|
|
|
|
void FontRecord::setColorIndex( unsigned ci )
|
|
{
|
|
d->colorIndex = ci;
|
|
}
|
|
|
|
unsigned FontRecord::boldness() const
|
|
{
|
|
return d->boldness;
|
|
}
|
|
|
|
void FontRecord::setBoldness( unsigned b )
|
|
{
|
|
d->boldness = b;
|
|
}
|
|
|
|
bool FontRecord::italic() const
|
|
{
|
|
return d->italic;
|
|
}
|
|
|
|
void FontRecord::setItalic( bool i )
|
|
{
|
|
d->italic = i;
|
|
}
|
|
|
|
bool FontRecord::strikeout() const
|
|
{
|
|
return d->strikeout;
|
|
}
|
|
|
|
void FontRecord::setStrikeout( bool s )
|
|
{
|
|
d->strikeout = s;
|
|
}
|
|
|
|
unsigned FontRecord::escapement() const
|
|
{
|
|
return d->escapement;
|
|
}
|
|
|
|
void FontRecord::setEscapement( unsigned s )
|
|
{
|
|
d->escapement = s;
|
|
}
|
|
|
|
unsigned FontRecord::underline() const
|
|
{
|
|
return d->underline;
|
|
}
|
|
|
|
void FontRecord::setUnderline( unsigned u )
|
|
{
|
|
d->underline = u;
|
|
}
|
|
|
|
|
|
void FontRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 14 ) return;
|
|
|
|
setHeight( readU16( data ) );
|
|
unsigned flag = readU16( data+2 );
|
|
setItalic( flag & 2 );
|
|
setStrikeout( flag & 8 );
|
|
setStrikeout( flag & 8 );
|
|
|
|
setColorIndex( readU16( data+4 ) );
|
|
|
|
setBoldness( readU16( data+6 ) );
|
|
setEscapement( readU16( data+8 ) );
|
|
setUnderline( data[10] );
|
|
|
|
setFontFamily( data[11] );
|
|
setCharacterSet( data[12] );
|
|
|
|
UString fn = ( version() >= Excel97 ) ?
|
|
EString::fromSheetName( data+14, size-14 ).str() :
|
|
EString::fromByteString( data+14, false, size-14 ).str();
|
|
setFontName( fn );
|
|
}
|
|
|
|
|
|
void FontRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "FONT" << std::endl;
|
|
out << " Height : " << height() << " twips" << std::endl;
|
|
out << " Font Name : " << fontName() << std::endl;
|
|
out << " Color Index : " << colorIndex() << std::endl;
|
|
out << " Boldness : " << boldness() << std::endl;
|
|
out << " Italic : " << (italic()?"Yes":"No") << std::endl;
|
|
out << " Strikeout : " << (strikeout()?"Yes":"No") << std::endl;
|
|
out << " Escapement : ";
|
|
switch( escapement() )
|
|
{
|
|
case Normal: out << "Normal" << std::endl; break;
|
|
case Subscript: out << "Subscript" << std::endl; break;
|
|
case Superscript: out << "Superscript" << std::endl; break;
|
|
default: out << "Unkown " << escapement() << std::endl; break;
|
|
};
|
|
}
|
|
|
|
// ========== FOOTER ==========
|
|
|
|
const unsigned int FooterRecord::id = 0x0015;
|
|
|
|
class FooterRecord::Private
|
|
{
|
|
public:
|
|
UString footer;
|
|
};
|
|
|
|
FooterRecord::FooterRecord():
|
|
Record()
|
|
{
|
|
d = new FooterRecord::Private();
|
|
}
|
|
|
|
FooterRecord::~FooterRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
UString FooterRecord::footer() const
|
|
{
|
|
return d->footer;
|
|
}
|
|
|
|
void FooterRecord::setFooter( const UString& footer )
|
|
{
|
|
d->footer = footer;
|
|
}
|
|
|
|
void FooterRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 2 ) return;
|
|
|
|
UString footer = ( version() >= Excel97 ) ?
|
|
EString::fromUnicodeString( data, true, size ).str() :
|
|
EString::fromByteString( data, false, size ).str();
|
|
setFooter( footer );
|
|
}
|
|
|
|
void FooterRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "FOOTER" << std::endl;
|
|
out << " Footer : " << footer() << std::endl;
|
|
}
|
|
|
|
// ========== FORMAT ==========
|
|
|
|
const unsigned int FormatRecord::id = 0x041e;
|
|
|
|
class FormatRecord::Private
|
|
{
|
|
public:
|
|
unsigned index;
|
|
UString formatString;
|
|
};
|
|
|
|
FormatRecord::FormatRecord():
|
|
Record()
|
|
{
|
|
d = new FormatRecord::Private;
|
|
d->index = 0;
|
|
}
|
|
|
|
FormatRecord::~FormatRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
FormatRecord::FormatRecord( const FormatRecord& fr ):
|
|
Record()
|
|
{
|
|
d = new FormatRecord::Private;
|
|
operator=( fr );
|
|
}
|
|
|
|
FormatRecord& FormatRecord::operator=( const FormatRecord& fr )
|
|
{
|
|
d->index = fr.index();
|
|
d->formatString = fr.formatString();
|
|
return *this;
|
|
}
|
|
|
|
unsigned FormatRecord::index() const
|
|
{
|
|
return d->index;
|
|
}
|
|
|
|
void FormatRecord::setIndex( unsigned i )
|
|
{
|
|
d->index = i;
|
|
}
|
|
|
|
UString FormatRecord::formatString() const
|
|
{
|
|
return d->formatString;
|
|
}
|
|
|
|
void FormatRecord::setFormatString( const UString& fs )
|
|
{
|
|
d->formatString = fs;
|
|
}
|
|
|
|
void FormatRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 3 ) return;
|
|
|
|
setIndex( readU16( data ) );
|
|
|
|
UString fs = ( version() >= Excel97 ) ?
|
|
EString::fromUnicodeString( data+2, true, size-2 ).str() :
|
|
EString::fromByteString( data+2, false, size-2 ).str();
|
|
setFormatString( fs );
|
|
}
|
|
|
|
void FormatRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "FORMAT" << std::endl;
|
|
out << " Index : " << index() << std::endl;
|
|
out << " Format String : " << formatString() << std::endl;
|
|
}
|
|
|
|
|
|
// ========== FORMULA ==========
|
|
|
|
const unsigned int FormulaRecord::id = 0x0006;
|
|
const unsigned int FormulaRecord::idOld = 0x0206; // BIFF3, BIFF4
|
|
|
|
class FormulaRecord::Private
|
|
{
|
|
public:
|
|
Value result;
|
|
FormulaTokens tokens;
|
|
};
|
|
|
|
FormulaRecord::FormulaRecord():
|
|
Record()
|
|
{
|
|
d = new FormulaRecord::Private();
|
|
}
|
|
|
|
FormulaRecord::~FormulaRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
Value FormulaRecord::result() const
|
|
{
|
|
return d->result;
|
|
}
|
|
|
|
void FormulaRecord::setResult( const Value& r )
|
|
{
|
|
d->result = r;
|
|
}
|
|
|
|
FormulaTokens FormulaRecord::tokens() const
|
|
{
|
|
return d->tokens;
|
|
}
|
|
|
|
void FormulaRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
int formula_ofs = 20;
|
|
|
|
// sanity check
|
|
if( size < formula_ofs ) return;
|
|
|
|
setRow( readU16( data ) );
|
|
setColumn( readU16( data+2 ) );
|
|
|
|
setXfIndex( readU16( data+4 ) );
|
|
if( readU16( data+12 ) != 0xffff )
|
|
{
|
|
// Floating-point
|
|
setResult( Value( readFloat64( data+6 ) ) );
|
|
}
|
|
else
|
|
{
|
|
switch( data[6] )
|
|
{
|
|
case 0: // string, real value in subsequent string record
|
|
setResult( Value( Value::String ) );
|
|
break;
|
|
case 1: // boolean
|
|
setResult( Value( data[8] ? true : false ) );
|
|
break;
|
|
case 2: // error code
|
|
setResult( errorAsValue( data[8] ) );
|
|
break;
|
|
case 3: // empty
|
|
setResult( Value::empty() );
|
|
break;
|
|
default: // fallback
|
|
setResult( Value::empty() );
|
|
break;
|
|
};
|
|
}
|
|
|
|
unsigned formula_len = readU16( data+formula_ofs );
|
|
|
|
// reconstruct all tokens
|
|
d->tokens.clear();
|
|
for( unsigned j = formula_ofs+2; j < size; )
|
|
{
|
|
unsigned ptg = data[j++];
|
|
ptg = ((ptg & 0x40) ? (ptg | 0x20) : ptg) & 0x3F;
|
|
FormulaToken token( ptg );
|
|
token.setVersion( version() );
|
|
|
|
if( token.id() == FormulaToken::String )
|
|
{
|
|
// find bytes taken to represent the string
|
|
EString estr = (version()==Excel97) ?
|
|
EString::fromUnicodeString( data+j, false, formula_len ) :
|
|
EString::fromByteString( data+j, false, formula_len );
|
|
token.setData( estr.size(), data+j );
|
|
j += estr.size();
|
|
}
|
|
else
|
|
{
|
|
// normal, fixed-size token
|
|
if( token.size() > 1 )
|
|
{
|
|
token.setData( token.size(), data+j );
|
|
j += token.size();
|
|
}
|
|
}
|
|
|
|
d->tokens.push_back( token );
|
|
}
|
|
}
|
|
|
|
void FormulaRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "FORMULA" << std::endl;
|
|
out << " Row : " << row() << std::endl;
|
|
out << " Column : " << column() << std::endl;
|
|
out << " XF Index : " << xfIndex() << std::endl;
|
|
out << " Result : " << result() << std::endl;
|
|
|
|
FormulaTokens ts = tokens();
|
|
out << " Tokens : " << ts.size() << std::endl;
|
|
for( unsigned i = 0; i < ts.size(); i++ )
|
|
out << " " << ts[i] << std::endl;
|
|
|
|
}
|
|
|
|
// ========== LABEL ==========
|
|
|
|
const unsigned int LabelRecord::id = 0x0204;
|
|
|
|
class LabelRecord::Private
|
|
{
|
|
public:
|
|
UString label;
|
|
};
|
|
|
|
LabelRecord::LabelRecord():
|
|
Record(), CellInfo()
|
|
{
|
|
d = new LabelRecord::Private();
|
|
d->label = UString::null;
|
|
}
|
|
|
|
LabelRecord::~LabelRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
UString LabelRecord::label() const
|
|
{
|
|
return d->label;
|
|
}
|
|
|
|
void LabelRecord::setLabel( const UString& l )
|
|
{
|
|
d->label = l;
|
|
}
|
|
|
|
void LabelRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 6 ) return;
|
|
|
|
setRow( readU16( data ) );
|
|
setColumn( readU16( data+2 ) );
|
|
setXfIndex( readU16( data+4 ) );
|
|
|
|
UString label = ( version() >= Excel97 ) ?
|
|
EString::fromUnicodeString( data+6, true, size-6 ).str() :
|
|
EString::fromByteString( data+6, true, size-6 ).str();
|
|
setLabel( label );
|
|
}
|
|
|
|
void LabelRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "LABEL" << std::endl;
|
|
out << " Row : " << row() << std::endl;
|
|
out << " Column : " << column() << std::endl;
|
|
out << " XF Index : " << xfIndex() << std::endl;
|
|
out << " Label : " << label() << std::endl;
|
|
}
|
|
|
|
// ========== HEADER ==========
|
|
|
|
const unsigned int HeaderRecord::id = 0x0014;
|
|
|
|
class HeaderRecord::Private
|
|
{
|
|
public:
|
|
UString header;
|
|
};
|
|
|
|
HeaderRecord::HeaderRecord():
|
|
Record()
|
|
{
|
|
d = new HeaderRecord::Private();
|
|
}
|
|
|
|
HeaderRecord::~HeaderRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
UString HeaderRecord::header() const
|
|
{
|
|
return d->header;
|
|
}
|
|
|
|
void HeaderRecord::setHeader( const UString& header )
|
|
{
|
|
d->header = header;
|
|
}
|
|
|
|
void HeaderRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 2 ) return;
|
|
|
|
UString header = ( version() >= Excel97 ) ?
|
|
EString::fromUnicodeString( data, true, size ).str() :
|
|
EString::fromByteString( data, false, size ).str();
|
|
setHeader( header );
|
|
}
|
|
|
|
void HeaderRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "HEADER" << std::endl;
|
|
out << " Header: " << header() << std::endl;
|
|
}
|
|
|
|
// ========== LABELSST ==========
|
|
|
|
const unsigned int LabelSSTRecord::id = 0x00fd;
|
|
|
|
class LabelSSTRecord::Private
|
|
{
|
|
public:
|
|
unsigned sstIndex;
|
|
};
|
|
|
|
LabelSSTRecord::LabelSSTRecord():
|
|
Record(), CellInfo()
|
|
{
|
|
d = new LabelSSTRecord::Private();
|
|
d->sstIndex = 0;
|
|
}
|
|
|
|
LabelSSTRecord::~LabelSSTRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
unsigned LabelSSTRecord::sstIndex() const
|
|
{
|
|
return d->sstIndex;
|
|
}
|
|
|
|
void LabelSSTRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 10 ) return;
|
|
|
|
setRow( readU16( data ) );
|
|
setColumn( readU16( data+2 ) );
|
|
setXfIndex( readU16( data+4 ) );
|
|
|
|
d->sstIndex = readU32( data+6 );
|
|
}
|
|
|
|
void LabelSSTRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "LABELSST" << std::endl;
|
|
out << " Row : " << row() << std::endl;
|
|
out << " Column : " << column() << std::endl;
|
|
out << " XF Index : " << xfIndex() << std::endl;
|
|
out << " SST Index : " << d->sstIndex << std::endl;
|
|
}
|
|
|
|
// ========== LEFTMARGIN ==========
|
|
|
|
const unsigned int LeftMarginRecord::id = 0x0026;
|
|
|
|
class LeftMarginRecord::Private
|
|
{
|
|
public:
|
|
double leftMargin;
|
|
};
|
|
|
|
LeftMarginRecord::LeftMarginRecord():
|
|
Record()
|
|
{
|
|
d = new LeftMarginRecord::Private();
|
|
d->leftMargin = 1.0;
|
|
}
|
|
|
|
LeftMarginRecord::~LeftMarginRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
double LeftMarginRecord::leftMargin() const
|
|
{
|
|
return d->leftMargin;
|
|
}
|
|
|
|
void LeftMarginRecord::setLeftMargin( double m )
|
|
{
|
|
d->leftMargin = m;
|
|
}
|
|
|
|
void LeftMarginRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 8 ) return;
|
|
setLeftMargin( readFloat64( data ) );
|
|
}
|
|
|
|
void LeftMarginRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "LEFTMARGIN" << std::endl;
|
|
out << " Left Margin : " << leftMargin() << " inches" << std::endl;
|
|
}
|
|
|
|
// ========== MERGEDCELLS ==========
|
|
|
|
const unsigned int MergedCellsRecord::id = 0x00e5;
|
|
|
|
class MergedInfo
|
|
{
|
|
public:
|
|
unsigned firstRow, lastRow, firstColumn, lastColumn;
|
|
};
|
|
|
|
class MergedCellsRecord::Private
|
|
{
|
|
public:
|
|
std::vector<MergedInfo> mergedCells;
|
|
};
|
|
|
|
MergedCellsRecord::MergedCellsRecord():
|
|
Record()
|
|
{
|
|
d = new MergedCellsRecord::Private();
|
|
}
|
|
|
|
MergedCellsRecord::~MergedCellsRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
unsigned MergedCellsRecord::count() const
|
|
{
|
|
return d->mergedCells.size();
|
|
}
|
|
|
|
unsigned MergedCellsRecord::firstRow( unsigned i ) const
|
|
{
|
|
if( i >= d->mergedCells.size() ) return 0;
|
|
MergedInfo info = d->mergedCells[ i ];
|
|
return info.firstRow;
|
|
}
|
|
|
|
unsigned MergedCellsRecord::lastRow( unsigned i ) const
|
|
{
|
|
if( i >= d->mergedCells.size() ) return 0;
|
|
MergedInfo info = d->mergedCells[ i ];
|
|
return info.lastRow;
|
|
}
|
|
|
|
unsigned MergedCellsRecord::firstColumn( unsigned i ) const
|
|
{
|
|
if( i >= d->mergedCells.size() ) return 0;
|
|
MergedInfo info = d->mergedCells[ i ];
|
|
return info.firstColumn;
|
|
}
|
|
|
|
unsigned MergedCellsRecord::lastColumn( unsigned i ) const
|
|
{
|
|
if( i >= d->mergedCells.size() ) return 0;
|
|
MergedInfo info = d->mergedCells[ i ];
|
|
return info.lastColumn;
|
|
}
|
|
|
|
void MergedCellsRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 2 ) return;
|
|
|
|
unsigned num = readU16( data );
|
|
|
|
// sanity check
|
|
if( size < 2 + num*4 ) return;
|
|
|
|
unsigned p = 2;
|
|
for( unsigned i = 0; i < num; i++ )
|
|
{
|
|
MergedInfo info;
|
|
info.firstRow = readU16( data + p );
|
|
info.lastRow = readU16( data + p + 2 );
|
|
info.firstColumn = readU16( data + p + 4 );
|
|
info.lastColumn = readU16( data + p + 6 );
|
|
p += 8;
|
|
d->mergedCells.push_back( info );
|
|
}
|
|
}
|
|
|
|
void MergedCellsRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "MERGEDCELLS" << std::endl;
|
|
out << " Count : " << count() << std::endl;
|
|
for( unsigned c = 0; c < count(); c++ )
|
|
{
|
|
out << " Merged Cell #" << c << " : ";
|
|
out << "Column " << firstColumn(c) << "-" << lastColumn(c);
|
|
out << " Row " << firstRow(c) << "-" << lastRow(c);
|
|
out << std::endl;
|
|
}
|
|
}
|
|
|
|
// ========== MULBLANK ==========
|
|
|
|
const unsigned int MulBlankRecord::id = 0x00be;
|
|
|
|
class MulBlankRecord::Private
|
|
{
|
|
public:
|
|
std::vector<unsigned> xfIndexes;
|
|
};
|
|
|
|
MulBlankRecord::MulBlankRecord():
|
|
Record(), CellInfo(), ColumnSpanInfo()
|
|
{
|
|
d = new MulBlankRecord::Private();
|
|
}
|
|
|
|
MulBlankRecord::~MulBlankRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
void MulBlankRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 6 ) return;
|
|
|
|
setRow( readU16( data ) );
|
|
|
|
setFirstColumn( readU16( data+2 ) );
|
|
setLastColumn( readU16( data+size-2 ) );
|
|
|
|
d->xfIndexes.clear();
|
|
for( unsigned i = 4; i < size-2; i+= 2 )
|
|
d->xfIndexes.push_back( readU16( data+i ) );
|
|
|
|
// FIXME sentinel !
|
|
}
|
|
|
|
unsigned MulBlankRecord::xfIndex( unsigned i ) const
|
|
{
|
|
if( i >= d->xfIndexes.size() ) return 0;
|
|
return d->xfIndexes[ i ];
|
|
}
|
|
|
|
void MulBlankRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "MULBLANK" << std::endl;
|
|
out << " Row : " << row() << std::endl;
|
|
out << " First Column : " << firstColumn() << std::endl;
|
|
out << " Last Column : " << lastColumn() << std::endl;
|
|
}
|
|
|
|
// ========== MULRK ==========
|
|
|
|
const unsigned int MulRKRecord::id = 0x00bd;
|
|
|
|
class MulRKRecord::Private
|
|
{
|
|
public:
|
|
std::vector<unsigned> xfIndexes;
|
|
std::vector<bool> isIntegers;
|
|
std::vector<int> intValues;
|
|
std::vector<double> floatValues;
|
|
std::vector<unsigned> rkValues;
|
|
};
|
|
|
|
MulRKRecord::MulRKRecord():
|
|
Record(), CellInfo(), ColumnSpanInfo()
|
|
{
|
|
d = new MulRKRecord::Private();
|
|
}
|
|
|
|
MulRKRecord::~MulRKRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
unsigned MulRKRecord::xfIndex( unsigned i ) const
|
|
{
|
|
if( i >= d->xfIndexes.size() ) return 0;
|
|
return d->xfIndexes[ i ];
|
|
}
|
|
|
|
bool MulRKRecord::isInteger( unsigned i ) const
|
|
{
|
|
if( i >= d->isIntegers.size() ) return true;
|
|
return d->isIntegers[ i ];
|
|
}
|
|
|
|
int MulRKRecord::asInteger( unsigned i ) const
|
|
{
|
|
if( i >= d->intValues.size() ) return 0;
|
|
return d->intValues[ i ];
|
|
}
|
|
|
|
double MulRKRecord::asFloat( unsigned i ) const
|
|
{
|
|
if( i >= d->floatValues.size() ) return 0.0;
|
|
return d->floatValues[ i ];
|
|
}
|
|
|
|
unsigned MulRKRecord::encodedRK( unsigned i ) const
|
|
{
|
|
if( i >= d->rkValues.size() ) return 0;
|
|
return d->rkValues[ i ];
|
|
}
|
|
|
|
void MulRKRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 6 ) return;
|
|
|
|
setRow( readU16( data ) );
|
|
|
|
setFirstColumn( readU16( data+2 ) );
|
|
setLastColumn( readU16( data+size-2 ) );
|
|
|
|
d->xfIndexes.clear();
|
|
d->isIntegers.clear();
|
|
d->intValues.clear();
|
|
d->floatValues.clear();
|
|
for( unsigned i = 4; i < size-2; i+= 6 )
|
|
{
|
|
d->xfIndexes.push_back( readU16( data+i ) );
|
|
unsigned rk = readU32( data+i+2 );
|
|
d->rkValues.push_back( rk );
|
|
bool isInteger = true; int iv = 0; double fv = 0.0;
|
|
decodeRK( rk, isInteger, iv, fv );
|
|
|
|
d->isIntegers.push_back( isInteger );
|
|
d->intValues.push_back( isInteger ? iv : (int)fv );
|
|
d->floatValues.push_back( !isInteger ? fv : (double)iv );
|
|
}
|
|
|
|
// FIXME sentinel !
|
|
}
|
|
|
|
void MulRKRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "MULRK" << std::endl;
|
|
out << " Row : " << row() << std::endl;
|
|
out << " First Column : " << firstColumn() << std::endl;
|
|
out << " Last Column : " << lastColumn() << std::endl;
|
|
for( unsigned c = firstColumn(); c <= lastColumn(); c++ )
|
|
{
|
|
out << " Column " << c << " : " << asFloat( c-firstColumn() );
|
|
out << " Encoded: " << std::hex << encodedRK( c-firstColumn() );
|
|
out << std::endl;
|
|
}
|
|
}
|
|
|
|
// ========== NAME ==========
|
|
|
|
const unsigned int NameRecord::id = 0x0018;
|
|
|
|
class NameRecord::Private
|
|
{
|
|
public:
|
|
unsigned optionFlags;
|
|
UString definedName;
|
|
};
|
|
|
|
|
|
NameRecord::NameRecord()
|
|
{
|
|
d = new Private;
|
|
d->optionFlags = 0;
|
|
}
|
|
|
|
NameRecord::~NameRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
void NameRecord::setDefinedName( const UString& name )
|
|
{
|
|
d->definedName = name;
|
|
}
|
|
|
|
UString NameRecord::definedName() const
|
|
{
|
|
return d->definedName;
|
|
}
|
|
|
|
void NameRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 14 ) return;
|
|
|
|
d->optionFlags = readU16( data );
|
|
unsigned len = data[3];
|
|
|
|
if ( version() == Excel95 )
|
|
{
|
|
char* buffer = new char[ len+1 ];
|
|
memcpy( buffer, data + 14, len );
|
|
buffer[ len ] = 0;
|
|
d->definedName = UString( buffer );
|
|
delete[] buffer;
|
|
}
|
|
|
|
if ( version() == Excel97 )
|
|
{
|
|
UString str = UString();
|
|
for( unsigned k=0; k<len; k++ )
|
|
{
|
|
unsigned uch = readU16( data + 14 + k*2 );
|
|
str.append( UChar(uch) );
|
|
}
|
|
d->definedName = str;
|
|
}
|
|
}
|
|
|
|
void NameRecord::dump( std::ostream& out ) const
|
|
{
|
|
}
|
|
|
|
// ========== Number ==========
|
|
|
|
const unsigned int NumberRecord::id = 0x0203;
|
|
|
|
class NumberRecord::Private
|
|
{
|
|
public:
|
|
double number;
|
|
};
|
|
|
|
NumberRecord::NumberRecord():
|
|
Record(), CellInfo()
|
|
{
|
|
d = new NumberRecord::Private();
|
|
d->number = 0.0;
|
|
}
|
|
|
|
NumberRecord::~NumberRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
double NumberRecord::number() const
|
|
{
|
|
return d->number;
|
|
}
|
|
|
|
void NumberRecord::setNumber( double f )
|
|
{
|
|
d->number = f;
|
|
}
|
|
|
|
// FIXME check that sizeof(double) is 64
|
|
void NumberRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 14 ) return;
|
|
|
|
setRow( readU16( data ) );
|
|
setColumn( readU16( data+2 ) );
|
|
setXfIndex( readU16( data+4 ) );
|
|
setNumber( readFloat64( data+6 ) );
|
|
}
|
|
|
|
void NumberRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "NUMBER" << std::endl;
|
|
out << " Row : " << row() << std::endl;
|
|
out << " Column : " << column() << std::endl;
|
|
out << " XF Index : " << xfIndex() << std::endl;
|
|
out << " Value : " << number() << std::endl;
|
|
}
|
|
|
|
// ========== PALETTE ==========
|
|
|
|
const unsigned int PaletteRecord::id = 0x0092;
|
|
|
|
class PaletteRecord::Private
|
|
{
|
|
public:
|
|
std::vector<Color> colors;
|
|
};
|
|
|
|
PaletteRecord::PaletteRecord():
|
|
Record()
|
|
{
|
|
d = new PaletteRecord::Private();
|
|
}
|
|
|
|
PaletteRecord::~PaletteRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
Color PaletteRecord::color( unsigned i ) const
|
|
{
|
|
return d->colors[ i ];
|
|
}
|
|
|
|
unsigned PaletteRecord::count() const
|
|
{
|
|
return d->colors.size();
|
|
}
|
|
|
|
void PaletteRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 14 ) return;
|
|
|
|
unsigned num = readU16( data );
|
|
|
|
unsigned p = 2;
|
|
for( unsigned i = 0; i < num; i++, p+=4 )
|
|
{
|
|
unsigned red = data[ p ];
|
|
unsigned green = data[ p+1 ];
|
|
unsigned blue = data[ p+2 ];
|
|
d->colors.push_back( Color( red, green, blue ) );
|
|
}
|
|
}
|
|
|
|
void PaletteRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "PALETTE" << std::endl;
|
|
out << " Count : " << count() << std::endl;
|
|
for( unsigned i = 0; i < count(); i++ )
|
|
{
|
|
out << " Color #" << std::setw(2) << i << " : ";
|
|
Color c = color( i );
|
|
out << "R:" << std::setw(3) << c.red;
|
|
out << " G:" << std::setw(3) << c.green;
|
|
out << " B:" << std::setw(3) << c.blue << std::endl;
|
|
}
|
|
}
|
|
|
|
// ========== RIGHTMARGIN ==========
|
|
|
|
const unsigned int RightMarginRecord::id = 0x0027;
|
|
|
|
class RightMarginRecord::Private
|
|
{
|
|
public:
|
|
double rightMargin;
|
|
};
|
|
|
|
RightMarginRecord::RightMarginRecord():
|
|
Record()
|
|
{
|
|
d = new RightMarginRecord::Private();
|
|
d->rightMargin = 1.0;
|
|
}
|
|
|
|
RightMarginRecord::~RightMarginRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
double RightMarginRecord::rightMargin() const
|
|
{
|
|
return d->rightMargin;
|
|
}
|
|
|
|
void RightMarginRecord::setRightMargin( double m )
|
|
{
|
|
d->rightMargin = m;
|
|
}
|
|
|
|
void RightMarginRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 8 ) return;
|
|
setRightMargin( readFloat64( data ) );
|
|
}
|
|
|
|
void RightMarginRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "RIGHTMARGIN" << std::endl;
|
|
out << " Right Margin : " << rightMargin() << " inches " << std::endl;
|
|
}
|
|
|
|
// ========== RK ==========
|
|
|
|
const unsigned int RKRecord::id = 0x027e;
|
|
|
|
class RKRecord::Private
|
|
{
|
|
public:
|
|
bool integer;
|
|
unsigned rk;
|
|
int i;
|
|
double f;
|
|
};
|
|
|
|
RKRecord::RKRecord():
|
|
Record(), CellInfo()
|
|
{
|
|
d = new RKRecord::Private();
|
|
d->integer = true;
|
|
d->rk = 0;
|
|
d->i = 0;
|
|
d->f = 0.0;
|
|
}
|
|
|
|
RKRecord::~RKRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
bool RKRecord::isInteger() const
|
|
{
|
|
return d->integer;
|
|
}
|
|
|
|
bool RKRecord::isFloat() const
|
|
{
|
|
return !d->integer;
|
|
}
|
|
|
|
int RKRecord::asInteger() const
|
|
{
|
|
if( d->integer )
|
|
return d->i;
|
|
else
|
|
return (int)d->f;
|
|
}
|
|
|
|
double RKRecord::asFloat() const
|
|
{
|
|
if( !d->integer )
|
|
return d->f;
|
|
else
|
|
return (double)d->i;
|
|
}
|
|
|
|
void RKRecord::setInteger( int i )
|
|
{
|
|
d->integer = true;
|
|
d->i = i;
|
|
d->f = (double)i;
|
|
}
|
|
|
|
void RKRecord::setFloat( double f )
|
|
{
|
|
d->integer = false;
|
|
d->i = (int)f;
|
|
d->f = f;
|
|
}
|
|
|
|
unsigned RKRecord::encodedRK() const
|
|
{
|
|
return d->rk;
|
|
}
|
|
|
|
// FIXME check sizeof(int) is 32
|
|
// big vs little endian problem
|
|
void RKRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 10 ) return;
|
|
|
|
setRow( readU16( data ) );
|
|
setColumn( readU16( data+2 ) );
|
|
setXfIndex( readU16( data+4 ) );
|
|
|
|
int i = 0; double f = 0.0;
|
|
d->rk = readU32( data+6 );
|
|
decodeRK( d->rk, d->integer, i, f );
|
|
if( d->integer ) setInteger( i );
|
|
else setFloat( f );
|
|
}
|
|
|
|
void RKRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "RK" << std::endl;
|
|
out << " Row : " << row() << std::endl;
|
|
out << " Column : " << column() << std::endl;
|
|
out << " XF Index : " << xfIndex() << std::endl;
|
|
out << " Value : " << asFloat() << std::endl;
|
|
out << " Encoded RK : 0x" << std::hex << encodedRK() << std::endl;
|
|
out << std::dec;
|
|
}
|
|
|
|
// ========== Row ==========
|
|
|
|
const unsigned int RowRecord::id = 0x0208;
|
|
|
|
class RowRecord::Private
|
|
{
|
|
public:
|
|
unsigned row;
|
|
unsigned height;
|
|
unsigned xfIndex;
|
|
bool hidden;
|
|
};
|
|
|
|
RowRecord::RowRecord():
|
|
Record(), ColumnSpanInfo()
|
|
{
|
|
d = new RowRecord::Private();
|
|
d->row = 0;
|
|
d->height = 50;
|
|
d->xfIndex = 0;
|
|
d->hidden = false;
|
|
}
|
|
|
|
RowRecord::~RowRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
unsigned RowRecord::row() const
|
|
{
|
|
return d->row;
|
|
}
|
|
|
|
void RowRecord::setRow( unsigned r )
|
|
{
|
|
d->row = r;
|
|
}
|
|
|
|
unsigned RowRecord::height() const
|
|
{
|
|
return d->height;
|
|
}
|
|
|
|
void RowRecord::setHeight( unsigned h )
|
|
{
|
|
d->height = h;
|
|
}
|
|
|
|
unsigned RowRecord::xfIndex() const
|
|
{
|
|
return d->xfIndex;
|
|
}
|
|
|
|
void RowRecord::setXfIndex( unsigned i )
|
|
{
|
|
d->xfIndex = i;
|
|
}
|
|
|
|
bool RowRecord::hidden() const
|
|
{
|
|
return d->hidden;
|
|
}
|
|
|
|
void RowRecord::setHidden( bool h )
|
|
{
|
|
d->hidden = h;
|
|
}
|
|
|
|
void RowRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 16 ) return;
|
|
|
|
setRow( readU16( data ) );
|
|
setFirstColumn( readU16( data+2 ) );
|
|
setLastColumn( readU16( data+4 ) );
|
|
setHeight( readU16( data+6 ) & 0x7fff );
|
|
setXfIndex( readU16( data+14 ) & 0xfff );
|
|
|
|
unsigned options = readU16( data+12 );
|
|
setHidden ( options & 0x20 );
|
|
}
|
|
|
|
void RowRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "ROW" << std::endl;
|
|
out << " Row : " << row() << std::endl;
|
|
out << " First Column : " << firstColumn() << std::endl;
|
|
out << " Last Column : " << lastColumn() << std::endl;
|
|
out << " Height : " << height() << std::endl;
|
|
out << " XF Index : " << xfIndex() << std::endl;
|
|
out << " Hidden : " << ( hidden() ? "Yes" : "No" ) << std::endl;
|
|
}
|
|
|
|
// ========== RSTRING ==========
|
|
|
|
const unsigned int RStringRecord::id = 0x00d6;
|
|
|
|
class RStringRecord::Private
|
|
{
|
|
public:
|
|
UString label;
|
|
};
|
|
|
|
RStringRecord::RStringRecord():
|
|
Record(), CellInfo()
|
|
{
|
|
d = new RStringRecord::Private();
|
|
d->label = UString::null;
|
|
}
|
|
|
|
RStringRecord::~RStringRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
UString RStringRecord::label() const
|
|
{
|
|
return d->label;
|
|
}
|
|
|
|
void RStringRecord::setLabel( const UString& l )
|
|
{
|
|
d->label = l;
|
|
}
|
|
|
|
// FIXME formatting runs ? in EString perhaps ?
|
|
void RStringRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 6 ) return;
|
|
|
|
setRow( readU16( data ) );
|
|
setColumn( readU16( data+2 ) );
|
|
setXfIndex( readU16( data+4 ) );
|
|
|
|
// FIXME check Excel97
|
|
UString label = ( version() >= Excel97 ) ?
|
|
EString::fromUnicodeString( data+6, true, size-6 ).str() :
|
|
EString::fromByteString( data+6, true, size-6 ).str();
|
|
setLabel( label );
|
|
}
|
|
|
|
void RStringRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "RSTRING" << std::endl;
|
|
out << " Row : " << row() << std::endl;
|
|
out << " Column : " << column() << std::endl;
|
|
out << " XF Index : " << xfIndex() << std::endl;
|
|
out << " Label : " << label() << std::endl;
|
|
}
|
|
|
|
// ========== SST ==========
|
|
|
|
const unsigned int SSTRecord::id = 0x00fc;
|
|
|
|
class SSTRecord::Private
|
|
{
|
|
public:
|
|
unsigned total;
|
|
unsigned count;
|
|
std::vector<UString> strings;
|
|
};
|
|
|
|
SSTRecord::SSTRecord():
|
|
Record()
|
|
{
|
|
d = new SSTRecord::Private();
|
|
d->total = 0;
|
|
d->count = 0;
|
|
}
|
|
|
|
SSTRecord::~SSTRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
UString sstrecord_get_plain_string( const unsigned char* data, unsigned length )
|
|
{
|
|
char* buffer = new char[ length+1 ];
|
|
memcpy( buffer, data, length );
|
|
buffer[ length ] = 0;
|
|
UString str = UString( buffer );
|
|
delete[] buffer;
|
|
return str;
|
|
}
|
|
|
|
void SSTRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 8 ) return;
|
|
|
|
d->total = readU32( data );
|
|
d->count = readU32( data+4 );
|
|
|
|
unsigned offset = 8;
|
|
d->strings.clear();
|
|
|
|
for( unsigned i = 0; i < d->count; i++ )
|
|
{
|
|
// check against size
|
|
if (offset >= size) {
|
|
std::cerr << "Warning: reached end of SST record, but not all strings have been read!" << std::endl;
|
|
break;
|
|
}
|
|
|
|
EString es = EString::fromUnicodeString( data+offset, true, size - offset );
|
|
d->strings.push_back( es.str() );
|
|
offset += es.size();
|
|
}
|
|
|
|
// safety, add null string if we can't read all of the rest
|
|
while( d->strings.size() < d->count )
|
|
d->strings.push_back( UString() );
|
|
|
|
|
|
// sanity check, adjust to safer condition
|
|
if( d->count < d->strings.size() )
|
|
{
|
|
std::cerr << "Warning: mismatch number of string in SST record!" << std::endl;
|
|
d->count = d->strings.size();
|
|
}
|
|
}
|
|
|
|
unsigned SSTRecord::count() const
|
|
{
|
|
return d->count;
|
|
}
|
|
|
|
// why not just string() ? to avoid easy confusion with std::string
|
|
UString SSTRecord::stringAt( unsigned index ) const
|
|
{
|
|
if( index >= count()) return UString::null;
|
|
return d->strings[ index ];
|
|
}
|
|
|
|
void SSTRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "SST" << std::endl;
|
|
out << " Occurences : " << d->total << std::endl;
|
|
out << " Count : " << count() << std::endl;
|
|
for( unsigned i = 0; i < count(); i++ )
|
|
out << " String #" << std::setw(2) << i << " : " <<
|
|
stringAt( i ) << std::endl;
|
|
}
|
|
|
|
// ========== STRING ==========
|
|
|
|
const unsigned int StringRecord::id = 0x0207;
|
|
|
|
class StringRecord::Private
|
|
{
|
|
public:
|
|
UString string;
|
|
};
|
|
|
|
StringRecord::StringRecord():
|
|
Record()
|
|
{
|
|
d = new StringRecord::Private();
|
|
}
|
|
|
|
StringRecord::~StringRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
void StringRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 3 ) return;
|
|
|
|
// TODO simple string for BIFF7
|
|
|
|
EString es = EString::fromUnicodeString( data, true, size );
|
|
d->string = es.str();
|
|
}
|
|
|
|
UString StringRecord::ustring() const
|
|
{
|
|
return d->string;
|
|
}
|
|
|
|
Value StringRecord::value() const
|
|
{
|
|
return Value( d->string );
|
|
}
|
|
|
|
void StringRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "STRING" << std::endl;
|
|
out << " String : " << ustring() << std::endl;
|
|
}
|
|
|
|
// ========== SUPBOOK ==========
|
|
|
|
const unsigned int SupbookRecord::id = 0x01ae;
|
|
|
|
class SupbookRecord::Private
|
|
{
|
|
public:
|
|
SupbookRecord::ReferenceType type;
|
|
};
|
|
|
|
SupbookRecord::SupbookRecord()
|
|
{
|
|
d = new Private;
|
|
d->type = UnknownRef;
|
|
}
|
|
|
|
SupbookRecord::~SupbookRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
SupbookRecord::ReferenceType SupbookRecord::referenceType() const
|
|
{
|
|
return d->type;
|
|
}
|
|
|
|
void SupbookRecord::setReferenceType(SupbookRecord::ReferenceType type)
|
|
{
|
|
d->type = type;
|
|
}
|
|
|
|
void SupbookRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
setReferenceType(UnknownRef);
|
|
|
|
if( version() >= Excel97 )
|
|
{
|
|
// check for add-in function or internal ref first
|
|
if(size == 4)
|
|
{
|
|
unsigned id1 = readU16(data);
|
|
unsigned id2 = readU16(data+2);
|
|
if((id1 == 1) && (id2 == 0x3a01))
|
|
setReferenceType(AddInRef);
|
|
|
|
// check for internal reference
|
|
if((id1 > 0) && (id2 == 0x0401))
|
|
setReferenceType(InternalRef);
|
|
}
|
|
|
|
// check for object (DDE/OLE) link
|
|
if(referenceType() == UnknownRef)
|
|
if(size > 2)
|
|
{
|
|
unsigned id1 = readU16(data);
|
|
if(id1 == 0)
|
|
setReferenceType(ObjectLink);
|
|
}
|
|
|
|
// no match, must be external ref
|
|
if(referenceType() == UnknownRef)
|
|
setReferenceType(ExternalRef);
|
|
}
|
|
}
|
|
|
|
void SupbookRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "SUPBOOK" << std::endl;
|
|
}
|
|
|
|
|
|
// ========== TOPMARGIN ==========
|
|
|
|
const unsigned int TopMarginRecord::id = 0x0028;
|
|
|
|
class TopMarginRecord::Private
|
|
{
|
|
public:
|
|
double topMargin;
|
|
};
|
|
|
|
TopMarginRecord::TopMarginRecord():
|
|
Record()
|
|
{
|
|
d = new TopMarginRecord::Private();
|
|
d->topMargin = 1.0;
|
|
}
|
|
|
|
TopMarginRecord::~TopMarginRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
double TopMarginRecord::topMargin() const
|
|
{
|
|
return d->topMargin;
|
|
}
|
|
|
|
void TopMarginRecord::setTopMargin( double m )
|
|
{
|
|
d->topMargin = m;
|
|
}
|
|
|
|
void TopMarginRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
if( size < 8 ) return;
|
|
setTopMargin( readFloat64( data ) );
|
|
}
|
|
|
|
void TopMarginRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "TOPMARGIN" << std::endl;
|
|
out << " Top Margin : " << topMargin() << " inches " << std::endl;
|
|
}
|
|
|
|
// ========== XF ==========
|
|
|
|
const unsigned int XFRecord::id = 0x00e0;
|
|
|
|
class XFRecord::Private
|
|
{
|
|
public:
|
|
unsigned fontIndex;
|
|
unsigned formatIndex;
|
|
bool locked;
|
|
bool formulaHidden;
|
|
unsigned parentStyle;
|
|
unsigned horizontalAlignment;
|
|
unsigned verticalAlignment;
|
|
bool textWrap;
|
|
unsigned rotationAngle;
|
|
bool stackedLetters;
|
|
unsigned indentLevel;
|
|
bool shrinkContent;
|
|
unsigned leftBorderStyle;
|
|
unsigned leftBorderColor;
|
|
unsigned rightBorderStyle;
|
|
unsigned rightBorderColor;
|
|
unsigned topBorderStyle;
|
|
unsigned topBorderColor;
|
|
unsigned bottomBorderStyle;
|
|
unsigned bottomBorderColor;
|
|
bool diagonalTopLeft;
|
|
bool diagonalBottomLeft;
|
|
unsigned diagonalStyle;
|
|
unsigned diagonalColor;
|
|
unsigned fillPattern;
|
|
unsigned patternForeColor;
|
|
unsigned patternBackColor;
|
|
};
|
|
|
|
XFRecord::XFRecord(): Record()
|
|
{
|
|
d = new XFRecord::Private();
|
|
d->fontIndex = 0;
|
|
d->formatIndex = 0;
|
|
d->locked = false;
|
|
d->formulaHidden = false;
|
|
d->parentStyle = 0;
|
|
d->horizontalAlignment = Left;
|
|
d->verticalAlignment = VCentered;
|
|
d->textWrap = false;
|
|
d->rotationAngle = 0;
|
|
d->stackedLetters = 0;
|
|
d->indentLevel = 0;
|
|
d->shrinkContent = 0;
|
|
d->leftBorderStyle = 0;
|
|
d->leftBorderColor = 0;
|
|
d->rightBorderStyle = 0;
|
|
d->rightBorderColor = 0;
|
|
d->topBorderStyle = 0;
|
|
d->topBorderColor = 0;
|
|
d->bottomBorderStyle = 0;
|
|
d->bottomBorderColor = 0;
|
|
d->diagonalTopLeft = false;
|
|
d->diagonalBottomLeft = false;
|
|
d->diagonalStyle = 0;
|
|
d->diagonalColor = 0;
|
|
d->fillPattern = 0;
|
|
d->patternForeColor = 0;
|
|
d->patternBackColor = 0;
|
|
}
|
|
|
|
XFRecord::~XFRecord()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
XFRecord::XFRecord( const XFRecord& xf ): Record()
|
|
{
|
|
d = new XFRecord::Private();
|
|
operator=( xf );
|
|
}
|
|
|
|
XFRecord& XFRecord::operator=( const XFRecord& xf )
|
|
{
|
|
d->fontIndex = xf.fontIndex();
|
|
d->formatIndex = xf.formatIndex();
|
|
d->locked = xf.locked();
|
|
d->formulaHidden = xf.formulaHidden();
|
|
d->parentStyle = xf.parentStyle();
|
|
d->horizontalAlignment = xf.horizontalAlignment();
|
|
d->verticalAlignment = xf.verticalAlignment();
|
|
d->textWrap = xf.textWrap();
|
|
d->rotationAngle = xf.rotationAngle();
|
|
d->stackedLetters = xf.stackedLetters();
|
|
d->indentLevel = xf.indentLevel();
|
|
d->shrinkContent = xf.shrinkContent();
|
|
d->leftBorderStyle = xf.leftBorderStyle();
|
|
d->leftBorderColor = xf.leftBorderColor();
|
|
d->rightBorderStyle = xf.rightBorderStyle();
|
|
d->rightBorderColor = xf.rightBorderColor();
|
|
d->topBorderStyle = xf.topBorderStyle();
|
|
d->topBorderColor = xf.topBorderColor();
|
|
d->bottomBorderStyle = xf.bottomBorderStyle();
|
|
d->bottomBorderColor = xf.bottomBorderColor();
|
|
d->diagonalTopLeft = xf.diagonalTopLeft();
|
|
d->diagonalBottomLeft = xf.diagonalBottomLeft();
|
|
d->diagonalStyle = xf.diagonalStyle();
|
|
d->diagonalColor = xf.diagonalColor();
|
|
d->fillPattern = xf.fillPattern();
|
|
d->patternForeColor = xf.patternForeColor();
|
|
d->patternBackColor = xf.patternBackColor();
|
|
return *this;
|
|
}
|
|
|
|
unsigned XFRecord::fontIndex() const
|
|
{
|
|
return d->fontIndex;
|
|
}
|
|
|
|
void XFRecord::setFontIndex( unsigned fi )
|
|
{
|
|
d->fontIndex = fi;
|
|
}
|
|
|
|
unsigned XFRecord::formatIndex() const
|
|
{
|
|
return d->formatIndex;
|
|
}
|
|
|
|
void XFRecord::setFormatIndex( unsigned fi )
|
|
{
|
|
d->formatIndex = fi;
|
|
}
|
|
|
|
bool XFRecord::locked() const
|
|
{
|
|
return d->locked;
|
|
}
|
|
|
|
void XFRecord::setLocked( bool l )
|
|
{
|
|
d->locked = l;
|
|
}
|
|
|
|
bool XFRecord::formulaHidden() const
|
|
{
|
|
return d->formulaHidden;
|
|
}
|
|
|
|
void XFRecord::setFormulaHidden( bool f )
|
|
{
|
|
d->formulaHidden = f;
|
|
}
|
|
|
|
unsigned XFRecord::parentStyle() const
|
|
{
|
|
return d->parentStyle;
|
|
}
|
|
|
|
void XFRecord::setParentStyle( unsigned p )
|
|
{
|
|
d->parentStyle = p;
|
|
}
|
|
|
|
unsigned XFRecord::horizontalAlignment() const
|
|
{
|
|
return d->horizontalAlignment;
|
|
}
|
|
|
|
void XFRecord::setHorizontalAlignment( unsigned ha )
|
|
{
|
|
d->horizontalAlignment = ha;
|
|
}
|
|
|
|
const char* XFRecord::horizontalAlignmentAsString() const
|
|
{
|
|
const char *result = "Unknown";
|
|
switch( horizontalAlignment() )
|
|
{
|
|
case General: result = "General"; break;
|
|
case Left: result = "Left"; break;
|
|
case Centered: result = "Centered"; break;
|
|
case Right: result = "Right"; break;
|
|
case Justified: result = "Justified"; break;
|
|
case Filled: result = "Filled"; break;
|
|
default: break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
unsigned XFRecord::verticalAlignment() const
|
|
{
|
|
return d->verticalAlignment;
|
|
}
|
|
|
|
void XFRecord::setVerticalAlignment( unsigned va )
|
|
{
|
|
d->verticalAlignment = va;
|
|
}
|
|
|
|
const char* XFRecord::verticalAlignmentAsString() const
|
|
{
|
|
const char *result = "Unknown";
|
|
switch( verticalAlignment() )
|
|
{
|
|
case Top: result = "Top"; break;
|
|
case VCentered: result = "Centered"; break;
|
|
case Bottom: result = "Bottom"; break;
|
|
case VJustified: result = "Justified"; break;
|
|
case VDistributed: result = "Distributed"; break;
|
|
default: break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool XFRecord::textWrap() const
|
|
{
|
|
return d->textWrap;
|
|
}
|
|
|
|
void XFRecord::setTextWrap( bool wrap )
|
|
{
|
|
d->textWrap = wrap;
|
|
}
|
|
|
|
unsigned XFRecord::rotationAngle() const
|
|
{
|
|
return d->rotationAngle;
|
|
}
|
|
|
|
void XFRecord::setRotationAngle( unsigned angle )
|
|
{
|
|
d->rotationAngle = angle;
|
|
}
|
|
|
|
bool XFRecord::stackedLetters() const
|
|
{
|
|
return d->stackedLetters;
|
|
}
|
|
|
|
void XFRecord::setStackedLetters( bool stacked )
|
|
{
|
|
d->stackedLetters = stacked;
|
|
}
|
|
|
|
unsigned XFRecord::indentLevel() const
|
|
{
|
|
return d->indentLevel;
|
|
}
|
|
|
|
void XFRecord::setIndentLevel( unsigned i )
|
|
{
|
|
d->indentLevel = i;
|
|
}
|
|
|
|
bool XFRecord::shrinkContent() const
|
|
{
|
|
return d->shrinkContent;
|
|
}
|
|
|
|
void XFRecord::setShrinkContent( bool s )
|
|
{
|
|
d->shrinkContent = s;
|
|
}
|
|
|
|
unsigned XFRecord::leftBorderStyle() const
|
|
{
|
|
return d->leftBorderStyle;
|
|
}
|
|
|
|
void XFRecord::setLeftBorderStyle( unsigned style )
|
|
{
|
|
d->leftBorderStyle = style;
|
|
}
|
|
|
|
unsigned XFRecord::leftBorderColor() const
|
|
{
|
|
return d->leftBorderColor;
|
|
}
|
|
|
|
void XFRecord::setLeftBorderColor( unsigned color )
|
|
{
|
|
d->leftBorderColor = color;
|
|
}
|
|
|
|
unsigned XFRecord::rightBorderStyle() const
|
|
{
|
|
return d->rightBorderStyle;
|
|
}
|
|
|
|
void XFRecord::setRightBorderStyle( unsigned style )
|
|
{
|
|
d->rightBorderStyle = style;
|
|
}
|
|
|
|
unsigned XFRecord::rightBorderColor() const
|
|
{
|
|
return d->rightBorderColor;
|
|
}
|
|
|
|
void XFRecord::setRightBorderColor( unsigned color )
|
|
{
|
|
d->rightBorderColor = color;
|
|
}
|
|
|
|
unsigned XFRecord::topBorderStyle() const
|
|
{
|
|
return d->topBorderStyle;
|
|
}
|
|
|
|
void XFRecord::setTopBorderStyle( unsigned style )
|
|
{
|
|
d->topBorderStyle = style;
|
|
}
|
|
|
|
unsigned XFRecord::topBorderColor() const
|
|
{
|
|
return d->topBorderColor;
|
|
}
|
|
|
|
void XFRecord::setTopBorderColor( unsigned color )
|
|
{
|
|
d->topBorderColor = color;
|
|
}
|
|
|
|
unsigned XFRecord::bottomBorderStyle() const
|
|
{
|
|
return d->bottomBorderStyle;
|
|
}
|
|
|
|
void XFRecord::setBottomBorderStyle( unsigned style )
|
|
{
|
|
d->bottomBorderStyle = style;
|
|
}
|
|
|
|
unsigned XFRecord::bottomBorderColor() const
|
|
{
|
|
return d->bottomBorderColor;
|
|
}
|
|
|
|
void XFRecord::setBottomBorderColor( unsigned color )
|
|
{
|
|
d->bottomBorderColor = color;
|
|
}
|
|
|
|
bool XFRecord::diagonalTopLeft() const
|
|
{
|
|
return d->diagonalTopLeft;
|
|
}
|
|
|
|
void XFRecord::setDiagonalTopLeft( bool dd )
|
|
{
|
|
d->diagonalTopLeft = dd;
|
|
}
|
|
|
|
bool XFRecord::diagonalBottomLeft() const
|
|
{
|
|
return d->diagonalBottomLeft;
|
|
}
|
|
|
|
void XFRecord::setDiagonalBottomLeft( bool dd )
|
|
{
|
|
d->diagonalBottomLeft = dd;
|
|
}
|
|
|
|
unsigned XFRecord::diagonalStyle() const
|
|
{
|
|
return d->diagonalStyle;
|
|
}
|
|
|
|
void XFRecord::setDiagonalStyle( unsigned style )
|
|
{
|
|
d->diagonalStyle = style;
|
|
}
|
|
|
|
unsigned XFRecord::diagonalColor() const
|
|
{
|
|
return d->diagonalColor;
|
|
}
|
|
|
|
void XFRecord::setDiagonalColor( unsigned color )
|
|
{
|
|
d->diagonalColor = color;
|
|
}
|
|
|
|
unsigned XFRecord::fillPattern() const
|
|
{
|
|
return d->fillPattern;
|
|
}
|
|
|
|
void XFRecord::setFillPattern( unsigned pattern )
|
|
{
|
|
d->fillPattern = pattern;
|
|
}
|
|
|
|
unsigned XFRecord::patternForeColor() const
|
|
{
|
|
return d->patternForeColor;
|
|
}
|
|
|
|
void XFRecord::setPatternForeColor( unsigned color )
|
|
{
|
|
d->patternForeColor = color;
|
|
}
|
|
|
|
unsigned XFRecord::patternBackColor() const
|
|
{
|
|
return d->patternBackColor;
|
|
}
|
|
|
|
void XFRecord::setPatternBackColor( unsigned color )
|
|
{
|
|
d->patternBackColor = color;
|
|
}
|
|
|
|
void XFRecord::setData( unsigned size, const unsigned char* data )
|
|
{
|
|
unsigned recordSize = ( version() == Excel97 ) ? 20: 16;
|
|
if( size < recordSize ) return;
|
|
|
|
setFontIndex( readU16( data ) );
|
|
setFormatIndex( readU16( data+2 ) );
|
|
|
|
unsigned protection = readU16( data+4 ) & 7;
|
|
setLocked( protection & 1 );
|
|
setFormulaHidden( protection & 2 );
|
|
|
|
setParentStyle( readU16( data+4 ) >> 4 );
|
|
|
|
unsigned align = data[6];
|
|
setHorizontalAlignment( align & 0x07 );
|
|
setVerticalAlignment( align >> 4 );
|
|
setTextWrap( align & 0x08 );
|
|
|
|
unsigned angle = data[7];
|
|
setRotationAngle( ( angle != 255 ) ? ( angle & 0x7f ) : 0 );
|
|
setStackedLetters( angle == 255 );
|
|
|
|
if( version() == Excel97 )
|
|
{
|
|
unsigned options = data[8];
|
|
unsigned attributes = data[9];
|
|
|
|
setIndentLevel( options & 0x0f );
|
|
setShrinkContent( options & 0x10 );
|
|
|
|
unsigned linestyle = readU16( data + 10 );
|
|
unsigned color1 = readU16( data + 12 );
|
|
// unsigned color2 = readU16( data + 14 );
|
|
unsigned flag = readU16( data + 16 );
|
|
unsigned fill = readU16( data + 18 );
|
|
|
|
setLeftBorderStyle( linestyle & 0xf );
|
|
setRightBorderStyle( ( linestyle >> 4 ) & 0xf );
|
|
setTopBorderStyle( ( linestyle >> 8 ) & 0xf );
|
|
setBottomBorderStyle( ( linestyle >> 12 ) & 0xf );
|
|
|
|
setLeftBorderColor( color1 & 0x7f );
|
|
setRightBorderColor( ( color1 >> 7 ) & 0x7f );
|
|
setTopBorderColor( color1 & 0x7f );
|
|
setBottomBorderColor( ( color1 >> 7 ) & 0x7f );
|
|
|
|
setDiagonalTopLeft( color1 & 0x40 );
|
|
setDiagonalBottomLeft( color1 & 0x40 );
|
|
setDiagonalStyle( ( flag >> 4 ) & 0x1e );
|
|
setDiagonalColor( ( ( flag & 0x1f ) << 2 ) + ( ( color1 >> 14 ) & 3 ));
|
|
|
|
setFillPattern( ( flag >> 10 ) & 0x3f );
|
|
setPatternForeColor( fill & 0x7f );
|
|
setPatternBackColor( ( fill >> 7 ) & 0x7f );
|
|
}
|
|
else
|
|
{
|
|
unsigned data1 = readU32( data + 8 );
|
|
unsigned data2 = readU32( data + 12 );
|
|
|
|
setPatternForeColor( data1 & 0x7f );
|
|
setPatternBackColor( ( data1 >> 7 ) & 0x7f );
|
|
setFillPattern( ( data1 >> 16 ) & 0x3f );
|
|
|
|
setBottomBorderStyle( ( data1 >> 22 ) & 0x07 );
|
|
setBottomBorderColor( ( data1 >> 25 ) & 0x7f );
|
|
|
|
setTopBorderStyle( data2 & 0x07 );
|
|
setLeftBorderStyle( ( data2 >> 3 ) & 0x07 );
|
|
setRightBorderStyle( ( data2 >> 6 ) & 0x07 );
|
|
|
|
setTopBorderColor( ( data2 >> 9 ) & 0x7f );
|
|
setLeftBorderColor( ( data2 >> 16 ) & 0x7f );
|
|
setRightBorderColor( ( data2 >> 23 ) & 0x7f );
|
|
}
|
|
}
|
|
|
|
void XFRecord::dump( std::ostream& out ) const
|
|
{
|
|
out << "XF" << std::endl;
|
|
out << " Parent Style : " << parentStyle() << std::endl;
|
|
out << " Font Index : " << fontIndex() << std::endl;
|
|
out << " Format Index : " << formatIndex() << std::endl;
|
|
out << " Locked : " << (locked()?"Yes":"No") << std::endl;
|
|
out << " Formula Visibility : " << (formulaHidden()?"Hidden":"Visible") << std::endl;
|
|
out << " Horizontal Align : " << horizontalAlignmentAsString() << std::endl;
|
|
out << " Vertical Align : " << verticalAlignmentAsString() << std::endl;
|
|
out << " Text Wrap : " << ( textWrap() ? "yes" : "no" ) << std::endl;
|
|
out << " Rotation : " << rotationAngle() << std::endl;
|
|
out << " Stacked Letters : " << ( stackedLetters() ? "yes" : "no" ) << std::endl;
|
|
out << " Indent Level : " << indentLevel() << std::endl;
|
|
out << " Shrink To Fit : " << ( shrinkContent() ? "yes" : "no" ) << std::endl;
|
|
out << " Left Border : Style " << leftBorderStyle();
|
|
out << " Color: " << leftBorderColor() << std::endl;
|
|
out << " Right Border : Style " << rightBorderStyle();
|
|
out << " Color: " << rightBorderColor() << std::endl;
|
|
out << " Top Border : Style " << topBorderStyle();
|
|
out << " Color: " << topBorderColor() << std::endl;
|
|
out << " Bottom Border : Style " << bottomBorderStyle();
|
|
out << " Color: " << bottomBorderColor() << std::endl;
|
|
out << " Diagonal Lines : " ;
|
|
if ( diagonalTopLeft() ) out << "TopLeft ";
|
|
if ( diagonalBottomLeft() ) out << "BottomLeft ";
|
|
out << "Style " << diagonalStyle() << " Color: " << diagonalColor() << std::endl;
|
|
out << " Fill Pattern : " << fillPattern() << std::endl;
|
|
out << " Fill Color : Fore " << patternForeColor() << " Back: "
|
|
<< patternBackColor() << std::endl;
|
|
}
|
|
|
|
//=============================================
|
|
// ExcelReader
|
|
//=============================================
|
|
|
|
struct ExcelReaderExternalWorkbook
|
|
{
|
|
bool addin;
|
|
bool external;
|
|
bool internal;
|
|
bool objectLink;
|
|
};
|
|
|
|
class ExcelReader::Private
|
|
{
|
|
public:
|
|
|
|
// the workbook
|
|
Workbook* workbook;
|
|
|
|
// password protection flag
|
|
// TODO: password hash for record decryption
|
|
bool passwordProtected;
|
|
|
|
// active sheet, all cell records will be stored here
|
|
Sheet* activeSheet;
|
|
|
|
// for FORMULA+STRING record pair
|
|
Cell* formulaCell;
|
|
|
|
// mapping from BOF pos to actual Sheet
|
|
std::map<unsigned,Sheet*> bofMap;
|
|
|
|
// shared-string table
|
|
std::vector<UString> stringTable;
|
|
|
|
// table of format
|
|
std::map<unsigned,FormatRecord> formatTable;
|
|
std::map<unsigned,UString> formatsTable;
|
|
|
|
// table of font
|
|
std::vector<FontRecord> fontTable;
|
|
|
|
// table of Xformat
|
|
std::vector<XFRecord> xfTable;
|
|
|
|
// color table (from Palette record)
|
|
std::vector<Color> colorTable;
|
|
|
|
// mapping from font index to Swinder::FormatFont
|
|
std::map<unsigned,FormatFont> fontCache;
|
|
|
|
// for NAME and EXTERNNAME
|
|
std::vector<UString> nameTable;
|
|
|
|
// for SUPBOOK
|
|
std::vector<ExcelReaderExternalWorkbook> externalWorkbooks;
|
|
|
|
// for EXTERNSHEET
|
|
std::vector<UString> sheetRefs;
|
|
|
|
// for mergeToken functions
|
|
UString mergedTokens;
|
|
};
|
|
|
|
ExcelReader::ExcelReader()
|
|
{
|
|
d = new ExcelReader::Private();
|
|
|
|
d->workbook = 0;
|
|
d->activeSheet = 0;
|
|
d->formulaCell = 0;
|
|
|
|
d->passwordProtected = false;
|
|
|
|
d->mergedTokens.reserve(1024);
|
|
|
|
// initialize palette
|
|
// default palette for all but the first 8 colors
|
|
static const char *const default_palette[64-8] =
|
|
{
|
|
"#000000", "#ffffff", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff",
|
|
"#00ffff", "#800000", "#008000", "#000080", "#808000", "#800080", "#008080",
|
|
"#c0c0c0", "#808080", "#9999ff", "#993366", "#ffffcc", "#ccffff", "#660066",
|
|
"#ff8080", "#0066cc", "#ccccff", "#000080", "#ff00ff", "#ffff00", "#00ffff",
|
|
"#800080", "#800000", "#008080", "#0000ff", "#00ccff", "#ccffff", "#ccffcc",
|
|
"#ffff99", "#99ccff", "#ff99cc", "#cc99ff", "#ffcc99", "#3366ff", "#33cccc",
|
|
"#99cc00", "#ffcc00", "#ff9900", "#ff6600", "#666699", "#969696", "#003366",
|
|
"#339966", "#003300", "#333300", "#993300", "#993366", "#333399", "#333333",
|
|
};
|
|
for( int i = 0; i < 64-8; i++ ) {
|
|
d->colorTable.push_back(Color(default_palette[i]));
|
|
}
|
|
|
|
// default number formats
|
|
for( int format = 0; format < 50; format++)
|
|
{
|
|
UString valueFormat;
|
|
switch(format)
|
|
{
|
|
case 0: break;
|
|
case 1: valueFormat = "0"; break;
|
|
case 2: valueFormat = "0.00"; break;
|
|
case 3: valueFormat = "#,##0"; break;
|
|
case 4: valueFormat = "#,##0.00"; break;
|
|
case 5: valueFormat = "\"$\"#,##0_);(\"S\"#,##0)"; break;
|
|
case 6: valueFormat = "\"$\"#,##0_);[Red](\"S\"#,##0)"; break;
|
|
case 7: valueFormat = "\"$\"#,##0.00_);(\"S\"#,##0.00)"; break;
|
|
case 8: valueFormat = "\"$\"#,##0.00_);[Red](\"S\"#,##0.00)"; break;
|
|
case 9: valueFormat = "0%"; break;
|
|
case 10: valueFormat = "0.00%"; break;
|
|
case 11: valueFormat = "0.00E+00"; break;
|
|
case 12: valueFormat = "#?/?"; break;
|
|
case 13: valueFormat = "#\?\?/\?\?"; break;
|
|
case 14: valueFormat = "M/D/YY"; break;
|
|
case 15: valueFormat = "D-MMM-YY"; break;
|
|
case 16: valueFormat = "D-MMM"; break;
|
|
case 17: valueFormat = "MMM-YY"; break;
|
|
case 18: valueFormat = "h:mm AM/PM"; break;
|
|
case 19: valueFormat = "h:mm:ss AM/PM"; break;
|
|
case 20: valueFormat = "h:mm"; break;
|
|
case 21: valueFormat = "h:mm:ss"; break;
|
|
case 22: valueFormat = "M/D/YY h:mm"; break;
|
|
case 37: valueFormat = "_(#,##0_);(#,##0)"; break;
|
|
case 38: valueFormat = "_(#,##0_);[Red](#,##0)"; break;
|
|
case 39: valueFormat = "_(#,##0.00_);(#,##0)"; break;
|
|
case 40: valueFormat = "_(#,##0.00_);[Red](#,##0)"; break;
|
|
case 41: valueFormat = "_(\"$\"*#,##0_);_(\"$\"*#,##0_);_(\"$\"*\"-\");(@_)"; break;
|
|
case 42: valueFormat = "_(*#,##0_);(*(#,##0);_(*\"-\");_(@_)"; break;
|
|
case 43: valueFormat = "_(\"$\"*#,##0.00_);_(\"$\"*#,##0.00_);_(\"$\"*\"-\");(@_)"; break;
|
|
case 44: valueFormat = "_(\"$\"*#,##0.00_);_(\"$\"*#,##0.00_);_(\"$\"*\"-\");(@_)"; break;
|
|
case 45: valueFormat = "mm:ss"; break;
|
|
case 46: valueFormat = "[h]:mm:ss"; break;
|
|
case 47: valueFormat = "mm:ss.0"; break;
|
|
case 48: valueFormat = "##0.0E+0"; break;
|
|
case 49: valueFormat = "@"; break;
|
|
}
|
|
d->formatsTable[format] = valueFormat;
|
|
}
|
|
}
|
|
|
|
ExcelReader::~ExcelReader()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
|
|
// convert border style, e.g MediumDashed to a Pen
|
|
static Pen convertBorderStyle( unsigned style )
|
|
{
|
|
Pen pen;
|
|
switch( style )
|
|
{
|
|
case XFRecord::NoLine:
|
|
pen.width = 0;
|
|
pen.style = Pen::NoLine;
|
|
break;
|
|
case XFRecord::Thin:
|
|
pen.width = 1;
|
|
pen.style = Pen::SolidLine;
|
|
break;
|
|
case XFRecord::Medium:
|
|
pen.width = 3;
|
|
pen.style = Pen::SolidLine;
|
|
break;
|
|
case XFRecord::Dashed:
|
|
pen.width = 1;
|
|
pen.style = Pen::DashLine;
|
|
break;
|
|
case XFRecord::Dotted:
|
|
pen.width = 1;
|
|
pen.style = Pen::DotLine;
|
|
break;
|
|
case XFRecord::Thick:
|
|
pen.width = 4;
|
|
pen.style = Pen::SolidLine;
|
|
break;
|
|
case XFRecord::Double:
|
|
// FIXME no equivalent ?
|
|
pen.width = 4;
|
|
pen.style = Pen::SolidLine;
|
|
break;
|
|
case XFRecord::Hair:
|
|
// FIXME no equivalent ?
|
|
pen.width = 1;
|
|
pen.style = Pen::DotLine;
|
|
break;
|
|
case XFRecord::MediumDashed:
|
|
pen.width = 3;
|
|
pen.style = Pen::DashLine;
|
|
break;
|
|
case XFRecord::ThinDashDotted:
|
|
pen.width = 1;
|
|
pen.style = Pen::DashDotLine;
|
|
break;
|
|
case XFRecord::MediumDashDotted:
|
|
pen.width = 3;
|
|
pen.style = Pen::DashDotLine;
|
|
break;
|
|
case XFRecord::ThinDashDotDotted:
|
|
pen.width = 1;
|
|
pen.style = Pen::DashDotDotLine;
|
|
break;
|
|
case XFRecord::MediumDashDotDotted:
|
|
pen.width = 3;
|
|
pen.style = Pen::DashDotDotLine;
|
|
break;
|
|
case XFRecord::SlantedMediumDashDotted:
|
|
// FIXME no equivalent ?
|
|
pen.width = 3;
|
|
pen.style = Pen::DashDotLine;
|
|
break;
|
|
default:
|
|
// fallback, simple solid line
|
|
pen.width = 1;
|
|
pen.style = Pen::SolidLine;
|
|
break;
|
|
};
|
|
|
|
return pen;
|
|
}
|
|
|
|
unsigned convertPatternStyle( unsigned pattern )
|
|
{
|
|
switch ( pattern )
|
|
{
|
|
case 0x00: return FormatBackground::EmptyPattern;
|
|
case 0x01: return FormatBackground::SolidPattern;
|
|
case 0x02: return FormatBackground::Dense4Pattern;
|
|
case 0x03: return FormatBackground::Dense3Pattern;
|
|
case 0x04: return FormatBackground::Dense5Pattern;
|
|
case 0x05: return FormatBackground::HorPattern;
|
|
case 0x06: return FormatBackground::VerPattern;
|
|
case 0x07: return FormatBackground::FDiagPattern;
|
|
case 0x08: return FormatBackground::BDiagPattern;
|
|
case 0x09: return FormatBackground::Dense1Pattern;
|
|
case 0x0A: return FormatBackground::Dense2Pattern;
|
|
case 0x0B: return FormatBackground::HorPattern;
|
|
case 0x0C: return FormatBackground::VerPattern;
|
|
case 0x0D: return FormatBackground::FDiagPattern;
|
|
case 0x0E: return FormatBackground::BDiagPattern;
|
|
case 0x0F: return FormatBackground::CrossPattern;
|
|
case 0x10: return FormatBackground::DiagCrossPattern;
|
|
case 0x11: return FormatBackground::Dense6Pattern;
|
|
case 0x12: return FormatBackground::Dense7Pattern;
|
|
default: return FormatBackground::SolidPattern; // fallback
|
|
}
|
|
}
|
|
|
|
|
|
bool ExcelReader::load( Workbook* workbook, const char* filename )
|
|
{
|
|
POLE::Storage storage( filename );
|
|
if( !storage.open() )
|
|
{
|
|
//std::cerr << "Cannot open " << filename << std::endl;
|
|
return false;
|
|
}
|
|
|
|
unsigned version = Swinder::Excel97;
|
|
POLE::Stream* stream;
|
|
stream = new POLE::Stream( &storage, "/Workbook" );
|
|
if( stream->fail() )
|
|
{
|
|
delete stream;
|
|
stream = new POLE::Stream( &storage, "/Book" );
|
|
version = Swinder::Excel95;
|
|
}
|
|
|
|
if( stream->fail() )
|
|
{
|
|
//std::cerr << filename << " is not Excel workbook" << std::endl;
|
|
delete stream;
|
|
return false;
|
|
}
|
|
|
|
unsigned long stream_size = stream->size();
|
|
|
|
unsigned int buffer_size = 65536; // current size of the buffer
|
|
unsigned char *buffer = (unsigned char *) malloc(buffer_size);
|
|
unsigned char small_buffer[128]; // small, fixed size buffer
|
|
|
|
workbook->clear();
|
|
d->workbook = workbook;
|
|
|
|
d->passwordProtected = false;
|
|
|
|
// assume
|
|
|
|
while( stream->tell() < stream_size )
|
|
{
|
|
|
|
// this is set by FILEPASS record
|
|
// subsequent records will need to be decrypted
|
|
// since we do not support it yet, we have to bail out
|
|
if(d->passwordProtected)
|
|
{
|
|
d->workbook->setPasswordProtected( true );
|
|
break;
|
|
}
|
|
|
|
// get record type and data size
|
|
unsigned long pos = stream->tell();
|
|
unsigned bytes_read = stream->read( buffer, 4 );
|
|
if( bytes_read != 4 ) break;
|
|
|
|
unsigned long type = readU16( buffer );
|
|
unsigned long size = readU16( buffer + 2 );
|
|
|
|
// verify buffer is large enough to hold the record data
|
|
if (size > buffer_size) {
|
|
buffer = (unsigned char *) realloc(buffer, size);
|
|
buffer_size = size;
|
|
}
|
|
|
|
// load actual record data
|
|
bytes_read = stream->read( buffer, size );
|
|
if( bytes_read != size ) break;
|
|
|
|
// save current position in stream, to be able to restore the position later on
|
|
unsigned long saved_pos;
|
|
// repeatedly check if the next record is type 0x3C, a continuation record
|
|
unsigned long next_type; // the type of the next record
|
|
do {
|
|
saved_pos = stream->tell();
|
|
|
|
bytes_read = stream->read( small_buffer, 4 );
|
|
if (bytes_read != 4) break;
|
|
|
|
next_type = readU16( small_buffer );
|
|
unsigned long next_size = readU16( small_buffer + 2 );
|
|
|
|
if (next_type == 0x3C) {
|
|
// type of next record is 0x3C, so go ahead and append the contents of the next record to the buffer
|
|
|
|
// first verify the buffer is large enough to hold all the data
|
|
if ( (size + next_size) > buffer_size) {
|
|
buffer = (unsigned char *) realloc(buffer, size + next_size);
|
|
buffer_size = size + next_size;
|
|
}
|
|
|
|
// next read the data of the record
|
|
bytes_read = stream->read( buffer + size, next_size );
|
|
if (bytes_read != next_size) {
|
|
std::cout << "ERROR!" << std::endl;
|
|
break;
|
|
}
|
|
|
|
// if the first read byte is a zero, remove it (at least that is what the old excel97 filter did...)
|
|
if (buffer[size] == 0) {
|
|
memmove( buffer + size, buffer + size + 1, --next_size );
|
|
}
|
|
|
|
// and finally update size
|
|
size += next_size;
|
|
}
|
|
} while (next_type == 0x3C);
|
|
|
|
// restore position in stream to the beginning of the next record
|
|
stream->seek( saved_pos );
|
|
|
|
// skip record type 0, this is just for filler
|
|
if( type == 0 ) continue;
|
|
|
|
// create the record using the factory
|
|
Record* record = Record::create( type );
|
|
|
|
if( record )
|
|
{
|
|
// setup the record and invoke handler
|
|
record->setVersion( version );
|
|
record->setData( size, buffer );
|
|
record->setPosition( pos );
|
|
|
|
handleRecord( record );
|
|
|
|
// special handling to find Excel version
|
|
if ( record->rtti() == BOFRecord::id )
|
|
{
|
|
BOFRecord* bof = static_cast<BOFRecord*>(record);
|
|
if( bof ) if( bof->type() == BOFRecord::Workbook )
|
|
version = bof->version();
|
|
}
|
|
|
|
#ifdef SWINDER_XLS2RAW
|
|
std::cout << "Record 0x";
|
|
std::cout << std::setfill('0') << std::setw(4) << std::hex << record->rtti();
|
|
std::cout << " ";
|
|
std::cout << std::dec;
|
|
std::cout << "(Pos: " << record->position() << ") ";
|
|
record->dump( std::cout );
|
|
std::cout << std::endl;
|
|
#endif
|
|
|
|
delete record;
|
|
}
|
|
|
|
#ifdef SWINDER_XLS2RAW
|
|
if( !record )
|
|
{
|
|
std::cout << "Record 0x";
|
|
std::cout << std::setfill('0') << std::setw(4) << std::hex << type;
|
|
std::cout << std::dec;
|
|
std::cout << "(Pos: " << pos << ") ";
|
|
std::cout << std::endl;
|
|
std::cout << std::endl;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
free(buffer);
|
|
|
|
delete stream;
|
|
|
|
storage.close();
|
|
|
|
// for each XF, create the corresponding format
|
|
for(int i = 0; i < d->xfTable.size(); i++ )
|
|
{
|
|
Format format;
|
|
const XFRecord& xf = d->xfTable[i];
|
|
|
|
UString valueFormat = d->formatsTable[xf.formatIndex()];
|
|
format.setValueFormat( valueFormat );
|
|
|
|
format.setFont( convertFont( xf.fontIndex() ) );
|
|
|
|
FormatAlignment alignment;
|
|
switch( xf.horizontalAlignment() )
|
|
{
|
|
case XFRecord::Left:
|
|
alignment.setAlignX( Format::Left ); break;
|
|
case XFRecord::Right:
|
|
alignment.setAlignX( Format::Right ); break;
|
|
case XFRecord::Centered:
|
|
alignment.setAlignX( Format::Center ); break;
|
|
default: break;
|
|
// FIXME still unsupported: Repeat, Justified, Filled, Distributed
|
|
};
|
|
switch( xf.verticalAlignment() )
|
|
{
|
|
case XFRecord::Top:
|
|
alignment.setAlignY( Format::Top ); break;
|
|
case XFRecord::VCentered:
|
|
alignment.setAlignY( Format::Middle ); break;
|
|
case XFRecord::Bottom:
|
|
alignment.setAlignY( Format::Bottom ); break;
|
|
default: break;
|
|
// FIXME still unsupported: Justified, Distributed
|
|
}
|
|
alignment.setWrap( xf.textWrap() );
|
|
format.setAlignment( alignment );
|
|
|
|
FormatBorders borders;
|
|
|
|
Pen pen;
|
|
pen = convertBorderStyle( xf.leftBorderStyle() );
|
|
pen.color = convertColor( xf.leftBorderColor() );
|
|
borders.setLeftBorder( pen );
|
|
|
|
pen = convertBorderStyle( xf.rightBorderStyle() );
|
|
pen.color = convertColor( xf.rightBorderColor() );
|
|
borders.setRightBorder( pen );
|
|
|
|
pen = convertBorderStyle( xf.topBorderStyle() );
|
|
pen.color = convertColor( xf.topBorderColor() );
|
|
borders.setTopBorder( pen );
|
|
|
|
pen = convertBorderStyle( xf.bottomBorderStyle() );
|
|
pen.color = convertColor( xf.bottomBorderColor() );
|
|
borders.setBottomBorder( pen );
|
|
|
|
format.setBorders( borders );
|
|
|
|
FormatBackground background;
|
|
background.setForegroundColor( convertColor( xf.patternForeColor() ) );
|
|
background.setBackgroundColor( convertColor( xf.patternBackColor() ) );
|
|
background.setPattern( convertPatternStyle( xf.fillPattern() ) );
|
|
format.setBackground( background );
|
|
|
|
d->workbook->setFormat( i, format );
|
|
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ExcelReader::handleRecord( Record* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
unsigned type = record->rtti();
|
|
switch( type )
|
|
{
|
|
case BottomMarginRecord::id:
|
|
handleBottomMargin( static_cast<BottomMarginRecord*>( record ) ); break;
|
|
case BoundSheetRecord::id:
|
|
handleBoundSheet( static_cast<BoundSheetRecord*>( record ) ); break;
|
|
case BOFRecord::id:
|
|
handleBOF( static_cast<BOFRecord*>( record ) ); break;
|
|
case BoolErrRecord::id:
|
|
handleBoolErr( static_cast<BoolErrRecord*>( record ) ); break;
|
|
case BlankRecord::id:
|
|
handleBlank( static_cast<BlankRecord*>( record ) ); break;
|
|
case CalcModeRecord::id:
|
|
handleCalcMode( static_cast<CalcModeRecord*>( record ) ); break;
|
|
case ColInfoRecord::id:
|
|
handleColInfo( static_cast<ColInfoRecord*>( record ) ); break;
|
|
case ExternNameRecord::id:
|
|
handleExternName( static_cast<ExternNameRecord*>( record ) ); break;
|
|
case ExternSheetRecord::id:
|
|
handleExternSheet( static_cast<ExternSheetRecord*>( record ) ); break;
|
|
case FilepassRecord::id:
|
|
handleFilepass( static_cast<FilepassRecord*>( record ) ); break;
|
|
case FormatRecord::id:
|
|
handleFormat( static_cast<FormatRecord*>( record ) ); break;
|
|
case FormulaRecord::id:
|
|
handleFormula( static_cast<FormulaRecord*>( record ) ); break;
|
|
case FontRecord::id:
|
|
handleFont( static_cast<FontRecord*>( record ) ); break;
|
|
case FooterRecord::id:
|
|
handleFooter( static_cast<FooterRecord*>( record ) ); break;
|
|
case HeaderRecord::id:
|
|
handleHeader( static_cast<HeaderRecord*>( record ) ); break;
|
|
case LabelRecord::id:
|
|
handleLabel( static_cast<LabelRecord*>( record ) ); break;
|
|
case LabelSSTRecord::id:
|
|
handleLabelSST( static_cast<LabelSSTRecord*>( record ) ); break;
|
|
case LeftMarginRecord::id:
|
|
handleLeftMargin( static_cast<LeftMarginRecord*>( record ) ); break;
|
|
case MergedCellsRecord::id:
|
|
handleMergedCells( static_cast<MergedCellsRecord*>( record ) ); break;
|
|
case MulBlankRecord::id:
|
|
handleMulBlank( static_cast<MulBlankRecord*>( record ) ); break;
|
|
case MulRKRecord::id:
|
|
handleMulRK( static_cast<MulRKRecord*>( record ) ); break;
|
|
case NameRecord::id:
|
|
handleName( static_cast<NameRecord*>( record ) ); break;
|
|
case NumberRecord::id:
|
|
handleNumber( static_cast<NumberRecord*>( record ) ); break;
|
|
case PaletteRecord::id:
|
|
handlePalette( static_cast<PaletteRecord*>( record ) ); break;
|
|
case RightMarginRecord::id:
|
|
handleRightMargin( static_cast<RightMarginRecord*>( record ) ); break;
|
|
case RKRecord::id:
|
|
handleRK( static_cast<RKRecord*>( record ) ); break;
|
|
case RowRecord::id:
|
|
handleRow( static_cast<RowRecord*>( record ) ); break;
|
|
case RStringRecord::id:
|
|
handleRString( static_cast<RStringRecord*>( record ) ); break;
|
|
case SSTRecord::id:
|
|
handleSST( static_cast<SSTRecord*>( record ) ); break;
|
|
case StringRecord::id:
|
|
handleString( static_cast<StringRecord*>( record ) ); break;
|
|
case SupbookRecord::id:
|
|
handleSupbook( static_cast<SupbookRecord*>( record ) ); break;
|
|
case TopMarginRecord::id:
|
|
handleTopMargin( static_cast<TopMarginRecord*>( record ) ); break;
|
|
case XFRecord::id:
|
|
handleXF( static_cast<XFRecord*>( record ) ); break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ExcelReader::handleBottomMargin( BottomMarginRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( !d->activeSheet ) return;
|
|
|
|
// convert from inches to points
|
|
double margin = record->bottomMargin() * 72.0;
|
|
d->activeSheet->setBottomMargin( margin );
|
|
}
|
|
|
|
// FIXME does the order of sheet follow BOUNDSHEET of BOF(Worksheet) ?
|
|
// for now, assume BOUNDSHEET, hence we should create the sheet here
|
|
void ExcelReader::handleBoundSheet( BoundSheetRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
// only care for Worksheet, forget everything else
|
|
if( record->type() == BoundSheetRecord::Worksheet )
|
|
{
|
|
// create a new sheet
|
|
Sheet* sheet = new Sheet( d->workbook );
|
|
sheet->setName( record->sheetName() );
|
|
sheet->setVisible( record->visible() );
|
|
|
|
d->workbook->appendSheet( sheet );
|
|
|
|
// update bof position map
|
|
unsigned bofPos = record->bofPosition();
|
|
d->bofMap[ bofPos ] = sheet;
|
|
}
|
|
}
|
|
|
|
void ExcelReader::handleBOF( BOFRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( record->type() == BOFRecord::Worksheet )
|
|
{
|
|
// find the sheet and make it active
|
|
// which sheet ? look from from previous BoundSheet
|
|
Sheet* sheet = d->bofMap[ record->position() ];
|
|
if( sheet ) d->activeSheet = sheet;
|
|
}
|
|
}
|
|
|
|
void ExcelReader::handleBoolErr( BoolErrRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( !d->activeSheet ) return;
|
|
|
|
unsigned column = record->column();
|
|
unsigned row = record->row();
|
|
unsigned xfIndex = record->xfIndex();
|
|
|
|
Cell* cell = d->activeSheet->cell( column, row, true );
|
|
if( cell )
|
|
{
|
|
cell->setValue( record->value() );
|
|
cell->setFormatIndex( xfIndex );
|
|
}
|
|
}
|
|
|
|
void ExcelReader::handleBlank( BlankRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( !d->activeSheet ) return;
|
|
|
|
unsigned column = record->column();
|
|
unsigned row = record->row();
|
|
unsigned xfIndex = record->xfIndex();
|
|
|
|
Cell* cell = d->activeSheet->cell( column, row, true );
|
|
if( cell )
|
|
{
|
|
cell->setFormatIndex( xfIndex );
|
|
}
|
|
}
|
|
|
|
void ExcelReader::handleCalcMode( CalcModeRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
d->workbook->setAutoCalc( record->autoCalc() );
|
|
}
|
|
|
|
void ExcelReader::handleColInfo( ColInfoRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( !d->activeSheet ) return;
|
|
|
|
unsigned firstColumn = record->firstColumn();
|
|
unsigned lastColumn = record->lastColumn();
|
|
unsigned xfIndex = record->xfIndex();
|
|
unsigned width = record->width();
|
|
bool hidden = record->hidden();
|
|
|
|
for( unsigned i = firstColumn; i <= lastColumn; i++ )
|
|
{
|
|
Column* column = d->activeSheet->column( i, true );
|
|
if( column )
|
|
{
|
|
column->setWidth( width / 120 );
|
|
column->setFormatIndex( xfIndex );
|
|
column->setVisible( !hidden );
|
|
}
|
|
}
|
|
}
|
|
|
|
void ExcelReader::handleDateMode( DateModeRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
// FIXME FIXME what to do ??
|
|
std::cerr << "WARNING: Workbook uses unsupported 1904 Date System " << std::endl;
|
|
}
|
|
|
|
void ExcelReader::handleDimension( DimensionRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
// in the mean time we don't need to handle this because we don't care
|
|
// about the used range of the sheet
|
|
}
|
|
|
|
void ExcelReader::handleLabel( LabelRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( !d->activeSheet ) return;
|
|
|
|
unsigned column = record->column();
|
|
unsigned row = record->row();
|
|
unsigned xfIndex = record->xfIndex();
|
|
UString label = record->label();
|
|
|
|
Cell* cell = d->activeSheet->cell( column, row, true );
|
|
if( cell )
|
|
{
|
|
cell->setValue( Value( label ) );
|
|
cell->setFormatIndex( xfIndex );
|
|
}
|
|
}
|
|
|
|
void ExcelReader::handleLeftMargin( LeftMarginRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( !d->activeSheet ) return;
|
|
|
|
// convert from inches to points
|
|
double margin = record->leftMargin() * 72.0;
|
|
d->activeSheet->setLeftMargin( margin );
|
|
}
|
|
|
|
void ExcelReader::handleFormat( FormatRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
d->formatTable[ record->index() ] = *record;
|
|
d->formatsTable[ record->index() ] = record->formatString();
|
|
}
|
|
|
|
void ExcelReader::handleFormula( FormulaRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( !d->activeSheet ) return;
|
|
|
|
unsigned column = record->column();
|
|
unsigned row = record->row();
|
|
unsigned xfIndex = record->xfIndex();
|
|
Value value = record->result();
|
|
|
|
#if 1
|
|
// this gives the formula in OpenDocument format, e.g. "=SUM([A1]; [A2])
|
|
UString formula = decodeFormula( row, column, record->tokens(), true );
|
|
#else
|
|
// this gives the formula in standard Excel format, e.g. "=SUM(A1, A2)
|
|
UString formula = decodeFormula( row, column, record->tokens(), true );
|
|
#endif
|
|
|
|
Cell* cell = d->activeSheet->cell( column, row, true );
|
|
if( cell )
|
|
{
|
|
cell->setValue( value );
|
|
if( !formula.isEmpty() )
|
|
cell->setFormula( formula );
|
|
cell->setFormatIndex( xfIndex );
|
|
|
|
// if value is string, real value is in subsequent String record
|
|
if( value.isString() )
|
|
d->formulaCell = cell;
|
|
}
|
|
}
|
|
|
|
void ExcelReader::handleExternName( ExternNameRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
d->nameTable.push_back( record->externName() );
|
|
}
|
|
|
|
void ExcelReader::handleExternSheet( ExternSheetRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if(record->version() >= Excel97)
|
|
for(unsigned i = 0; i < record->count(); i++)
|
|
{
|
|
UString decodedRef("#REF");
|
|
|
|
unsigned index = record->refIndex(i);
|
|
unsigned first = record->firstSheet(i);
|
|
unsigned last = record->lastSheet(i);
|
|
|
|
if(index < d->externalWorkbooks.size())
|
|
{
|
|
// handle reference to own workbook
|
|
if(d->externalWorkbooks[index].internal)
|
|
if(first < d->workbook->sheetCount())
|
|
decodedRef = d->workbook->sheet(first)->name();
|
|
|
|
// add-in? let's (re)use # marker as in Excel 95
|
|
if(d->externalWorkbooks[index].addin)
|
|
decodedRef = UString("#");
|
|
}
|
|
|
|
d->sheetRefs.push_back(decodedRef);
|
|
}
|
|
else
|
|
{
|
|
UString ref = record->refName();
|
|
d->sheetRefs.push_back(ref);
|
|
}
|
|
}
|
|
|
|
void ExcelReader::handleFilepass( FilepassRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
d->passwordProtected = true;
|
|
}
|
|
|
|
void ExcelReader::handleFont( FontRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
d->fontTable.push_back( *record );
|
|
|
|
// font #4 is never used, so add a dummy one
|
|
if( d->fontTable.size() == 4 )
|
|
d->fontTable.push_back( FontRecord() );
|
|
}
|
|
|
|
void ExcelReader::handleFooter( FooterRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( !d->activeSheet ) return;
|
|
|
|
UString footer = record->footer();
|
|
UString left, center, right;
|
|
int pos = -1, len = 0;
|
|
|
|
// left part
|
|
pos = footer.find( UString("&L") );
|
|
if( pos >= 0 )
|
|
{
|
|
pos += 2;
|
|
len = footer.find( UString("&C") ) - pos;
|
|
if( len > 0 )
|
|
{
|
|
left = footer.substr( pos, len );
|
|
footer = footer.substr( pos+len, footer.length() );
|
|
}
|
|
}
|
|
|
|
// center part
|
|
pos = footer.find( UString("&C") );
|
|
if( pos >= 0 )
|
|
{
|
|
pos += 2;
|
|
len = footer.find( UString("&R") ) - pos;
|
|
if( len > 0 )
|
|
{
|
|
center = footer.substr( pos, len );
|
|
footer = footer.substr( pos+len, footer.length() );
|
|
}
|
|
}
|
|
|
|
// right part
|
|
pos = footer.find( UString("&R") );
|
|
if( pos >= 0 )
|
|
{
|
|
pos += 2;
|
|
right = footer.substr( pos, footer.length() - pos );
|
|
}
|
|
|
|
d->activeSheet->setLeftFooter( left );
|
|
d->activeSheet->setCenterFooter( center );
|
|
d->activeSheet->setRightFooter( right );
|
|
}
|
|
|
|
void ExcelReader::handleHeader( HeaderRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( !d->activeSheet ) return;
|
|
|
|
UString header = record->header();
|
|
UString left, center, right;
|
|
int pos = -1, len = 0;
|
|
|
|
// left part of the header
|
|
pos = header.find( UString("&L") );
|
|
if( pos >= 0 )
|
|
{
|
|
pos += 2;
|
|
len = header.find( UString("&C") ) - pos;
|
|
if( len > 0 )
|
|
{
|
|
left = header.substr( pos, len );
|
|
header = header.substr( pos+len, header.length() );
|
|
}
|
|
}
|
|
|
|
// center part of the header
|
|
pos = header.find( UString("&C") );
|
|
if( pos >= 0 )
|
|
{
|
|
pos += 2;
|
|
len = header.find( UString("&R") ) - pos;
|
|
if( len > 0 )
|
|
{
|
|
center = header.substr( pos, len );
|
|
header = header.substr( pos+len, header.length() );
|
|
}
|
|
}
|
|
|
|
// right part of the header
|
|
pos = header.find( UString("&R") );
|
|
if( pos >= 0 )
|
|
{
|
|
pos += 2;
|
|
right = header.substr( pos, header.length() - pos );
|
|
}
|
|
|
|
d->activeSheet->setLeftHeader( left );
|
|
d->activeSheet->setCenterHeader( center );
|
|
d->activeSheet->setRightHeader( right );
|
|
}
|
|
|
|
void ExcelReader::handleLabelSST( LabelSSTRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( !d->activeSheet ) return;
|
|
|
|
unsigned column = record->column();
|
|
unsigned row = record->row();
|
|
unsigned index = record->sstIndex();
|
|
unsigned xfIndex = record->xfIndex();
|
|
|
|
UString str;
|
|
if( index < d->stringTable.size() )
|
|
str = d->stringTable[ index ];
|
|
|
|
Cell* cell = d->activeSheet->cell( column, row, true );
|
|
if( cell )
|
|
{
|
|
cell->setValue( Value( str ) );
|
|
cell->setFormatIndex( xfIndex );
|
|
}
|
|
}
|
|
|
|
void ExcelReader::handleMergedCells( MergedCellsRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( !d->activeSheet ) return;
|
|
|
|
for( unsigned i = 0; i < record->count(); i++ )
|
|
{
|
|
unsigned firstRow = record->firstRow( i );
|
|
unsigned lastRow = record->lastRow( i );
|
|
unsigned firstColumn = record->firstColumn( i );
|
|
unsigned lastColumn = record->lastColumn( i );
|
|
|
|
Cell* cell = d->activeSheet->cell( firstColumn, firstRow, true );
|
|
if( cell )
|
|
{
|
|
cell->setColumnSpan( lastColumn - firstColumn + 1 );
|
|
cell->setRowSpan( lastRow - firstRow + 1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
void ExcelReader::handleMulBlank( MulBlankRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( !d->activeSheet ) return;
|
|
|
|
unsigned firstColumn = record->firstColumn();
|
|
unsigned lastColumn = record->lastColumn();
|
|
unsigned row = record->row();
|
|
|
|
for( unsigned column = firstColumn; column <= lastColumn; column++ )
|
|
{
|
|
Cell* cell = d->activeSheet->cell( column, row, true );
|
|
if( cell )
|
|
{
|
|
cell->setFormatIndex( record->xfIndex( column - firstColumn ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
void ExcelReader::handleMulRK( MulRKRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( !d->activeSheet ) return;
|
|
|
|
unsigned firstColumn = record->firstColumn();
|
|
unsigned lastColumn = record->lastColumn();
|
|
unsigned row = record->row();
|
|
|
|
for( unsigned column = firstColumn; column <= lastColumn; column++ )
|
|
{
|
|
Cell* cell = d->activeSheet->cell( column, row, true );
|
|
if( cell )
|
|
{
|
|
unsigned i = column - firstColumn;
|
|
Value value;
|
|
if( record->isInteger( i ) )
|
|
value.setValue( record->asInteger( i ) );
|
|
else
|
|
value.setValue( record->asFloat( i ) );
|
|
cell->setValue( value );
|
|
cell->setFormatIndex( record->xfIndex( column-firstColumn ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
void ExcelReader::handleName( NameRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
d->nameTable.push_back( record->definedName() );
|
|
}
|
|
|
|
void ExcelReader::handleNumber( NumberRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( !d->activeSheet ) return;
|
|
|
|
unsigned column = record->column();
|
|
unsigned row = record->row();
|
|
unsigned xfIndex = record->xfIndex();
|
|
double number = record->number();
|
|
|
|
Cell* cell = d->activeSheet->cell( column, row, true );
|
|
if( cell )
|
|
{
|
|
cell->setValue( Value( number ) );
|
|
cell->setFormatIndex( xfIndex );
|
|
}
|
|
}
|
|
|
|
void ExcelReader::handlePalette( PaletteRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
d->colorTable.clear();
|
|
for( unsigned i = 0; i < record->count(); i++ )
|
|
d->colorTable.push_back( record->color( i ) );
|
|
}
|
|
|
|
void ExcelReader::handleRightMargin( RightMarginRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( !d->activeSheet ) return;
|
|
|
|
// convert from inches to points
|
|
double margin = record->rightMargin() * 72.0;
|
|
d->activeSheet->setRightMargin( margin );
|
|
}
|
|
|
|
void ExcelReader::handleRK( RKRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( !d->activeSheet ) return;
|
|
|
|
unsigned column = record->column();
|
|
unsigned row = record->row();
|
|
unsigned xfIndex = record->xfIndex();
|
|
|
|
Value value;
|
|
if( record->isInteger() )
|
|
value.setValue( record->asInteger() );
|
|
else
|
|
value.setValue( record->asFloat() );
|
|
|
|
Cell* cell = d->activeSheet->cell( column, row, true );
|
|
if( cell )
|
|
{
|
|
cell->setValue( value );
|
|
cell->setFormatIndex( xfIndex );
|
|
}
|
|
}
|
|
|
|
void ExcelReader::handleRow( RowRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( !d->activeSheet ) return;
|
|
|
|
unsigned index = record->row();
|
|
unsigned xfIndex = record->xfIndex();
|
|
unsigned height = record->height();
|
|
bool hidden = record->hidden();
|
|
|
|
Row* row = d->activeSheet->row( index, true );
|
|
if( row )
|
|
{
|
|
row->setHeight( height / 20.0 );
|
|
row->setFormatIndex( xfIndex );
|
|
row->setVisible( !hidden );
|
|
}
|
|
}
|
|
|
|
void ExcelReader::handleRString( RStringRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( !d->activeSheet ) return;
|
|
|
|
unsigned column = record->column();
|
|
unsigned row = record->row();
|
|
unsigned xfIndex = record->xfIndex();
|
|
UString label = record->label();
|
|
|
|
Cell* cell = d->activeSheet->cell( column, row, true );
|
|
if( cell )
|
|
{
|
|
cell->setValue( Value( label ) );
|
|
cell->setFormatIndex( xfIndex );
|
|
}
|
|
}
|
|
|
|
void ExcelReader::handleSST( SSTRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
d->stringTable.clear();
|
|
for( unsigned i = 0; i < record->count();i++ )
|
|
{
|
|
UString str = record->stringAt( i );
|
|
d->stringTable.push_back( str );
|
|
}
|
|
}
|
|
|
|
void ExcelReader::handleString( StringRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( !d->activeSheet ) return;
|
|
if( !d->formulaCell ) return;
|
|
|
|
d->formulaCell->setValue( record->value() );
|
|
|
|
d->formulaCell = 0;
|
|
}
|
|
|
|
void ExcelReader::handleSupbook( SupbookRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
ExcelReaderExternalWorkbook ext;
|
|
ext.addin = record->referenceType() == SupbookRecord::AddInRef;
|
|
ext.internal = record->referenceType() == SupbookRecord::InternalRef;
|
|
ext.external = record->referenceType() == SupbookRecord::ExternalRef;
|
|
ext.objectLink = record->referenceType() == SupbookRecord::ObjectLink;
|
|
d->externalWorkbooks.push_back(ext);
|
|
}
|
|
|
|
void ExcelReader::handleTopMargin( TopMarginRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
if( !d->activeSheet ) return;
|
|
|
|
|
|
// convert from inches to points
|
|
double margin = record->topMargin() * 72.0;
|
|
d->activeSheet->setTopMargin( margin );
|
|
}
|
|
|
|
FormatFont ExcelReader::convertFont( unsigned fontIndex )
|
|
{
|
|
// speed-up trick: check in the cache first
|
|
FormatFont font = d->fontCache[ fontIndex ];
|
|
if( font.isNull() && ( fontIndex < d->fontTable.size() ))
|
|
{
|
|
FontRecord fr = d->fontTable[ fontIndex ];
|
|
font.setFontSize( fr.height() / 20.0 );
|
|
font.setFontFamily( fr.fontName() );
|
|
font.setColor( convertColor( fr.colorIndex() ) );
|
|
font.setBold( fr.boldness() > 500 );
|
|
font.setItalic( fr.italic() );
|
|
font.setStrikeout( fr.strikeout() );
|
|
font.setSubscript( fr.escapement() == FontRecord::Subscript );
|
|
font.setSuperscript( fr.escapement() == FontRecord::Superscript );
|
|
font.setUnderline( fr.underline() != FontRecord::None );
|
|
|
|
// put in the cache for further use
|
|
d->fontCache[ fontIndex ] = font;
|
|
}
|
|
|
|
return font;
|
|
}
|
|
|
|
Color ExcelReader::convertColor( unsigned colorIndex )
|
|
{
|
|
if( ( colorIndex >= 8 ) && ( colorIndex < 0x40 ) )
|
|
if( colorIndex-8 < d->colorTable.size() )
|
|
return d->colorTable[ colorIndex-8 ];
|
|
|
|
// FIXME the following colors depend on system color settings
|
|
// 0x0040 system window text colour for border lines
|
|
// 0x0041 system window background colour for pattern background
|
|
// 0x7fff system window text colour for fonts
|
|
if( colorIndex == 0x40 ) return Color( 0, 0, 0 );
|
|
if( colorIndex == 0x41 ) return Color( 255, 255, 255 );
|
|
if( colorIndex == 0x7fff ) return Color( 0, 0, 0 );
|
|
|
|
// fallback: just "black"
|
|
Color color;
|
|
|
|
// standard colors: black, white, red, green, blue,
|
|
// yellow, magenta, cyan
|
|
switch( colorIndex )
|
|
{
|
|
case 0: color = Color( 0, 0, 0 ); break;
|
|
case 1: color = Color( 255, 255, 255 ); break;
|
|
case 2: color = Color( 255, 0, 0 ); break;
|
|
case 3: color = Color( 0, 255, 0 ); break;
|
|
case 4: color = Color( 0, 0, 255 ); break;
|
|
case 5: color = Color( 255, 255, 0 ); break;
|
|
case 6: color = Color( 255, 0, 255 ); break;
|
|
case 7: color = Color( 0, 255, 255 ); break;
|
|
default: break;
|
|
}
|
|
|
|
return color;
|
|
}
|
|
|
|
// big task: convert Excel XFormat into Swinder::Format
|
|
Format ExcelReader::convertFormat( unsigned xfIndex )
|
|
{
|
|
Format format;
|
|
|
|
if( xfIndex >= d->xfTable.size() ) return format;
|
|
|
|
XFRecord xf = d->xfTable[ xfIndex ];
|
|
|
|
UString valueFormat = d->formatsTable[xf.formatIndex()];
|
|
format.setValueFormat( valueFormat );
|
|
|
|
format.setFont( convertFont( xf.fontIndex() ) );
|
|
|
|
FormatAlignment alignment;
|
|
switch( xf.horizontalAlignment() )
|
|
{
|
|
case XFRecord::Left:
|
|
alignment.setAlignX( Format::Left ); break;
|
|
case XFRecord::Right:
|
|
alignment.setAlignX( Format::Right ); break;
|
|
case XFRecord::Centered:
|
|
alignment.setAlignX( Format::Center ); break;
|
|
default: break;
|
|
// FIXME still unsupported: Repeat, Justified, Filled, Distributed
|
|
};
|
|
switch( xf.verticalAlignment() )
|
|
{
|
|
case XFRecord::Top:
|
|
alignment.setAlignY( Format::Top ); break;
|
|
case XFRecord::VCentered:
|
|
alignment.setAlignY( Format::Middle ); break;
|
|
case XFRecord::Bottom:
|
|
alignment.setAlignY( Format::Bottom ); break;
|
|
default: break;
|
|
// FIXME still unsupported: Justified, Distributed
|
|
}
|
|
alignment.setWrap( xf.textWrap() );
|
|
format.setAlignment( alignment );
|
|
|
|
FormatBorders borders;
|
|
|
|
Pen pen;
|
|
pen = convertBorderStyle( xf.leftBorderStyle() );
|
|
pen.color = convertColor( xf.leftBorderColor() );
|
|
borders.setLeftBorder( pen );
|
|
|
|
pen = convertBorderStyle( xf.rightBorderStyle() );
|
|
pen.color = convertColor( xf.rightBorderColor() );
|
|
borders.setRightBorder( pen );
|
|
|
|
pen = convertBorderStyle( xf.topBorderStyle() );
|
|
pen.color = convertColor( xf.topBorderColor() );
|
|
borders.setTopBorder( pen );
|
|
|
|
pen = convertBorderStyle( xf.bottomBorderStyle() );
|
|
pen.color = convertColor( xf.bottomBorderColor() );
|
|
borders.setBottomBorder( pen );
|
|
|
|
format.setBorders( borders );
|
|
|
|
FormatBackground background;
|
|
background.setForegroundColor( convertColor( xf.patternForeColor() ) );
|
|
background.setBackgroundColor( convertColor( xf.patternBackColor() ) );
|
|
background.setPattern( convertPatternStyle( xf.fillPattern() ) );
|
|
format.setBackground( background );
|
|
|
|
return format;
|
|
}
|
|
|
|
void ExcelReader::handleXF( XFRecord* record )
|
|
{
|
|
if( !record ) return;
|
|
|
|
d->xfTable.push_back( *record );
|
|
}
|
|
|
|
|
|
void ExcelReader::mergeTokens( UStringStack* stack, int count, const char* mergeString )
|
|
{
|
|
if( !stack ) return;
|
|
if( !stack->size() ) return;
|
|
if( count < 1 ) return;
|
|
|
|
d->mergedTokens.truncate(0);
|
|
while(count)
|
|
{
|
|
count--;
|
|
|
|
// sanity check
|
|
if(stack->size() == 0) break;
|
|
|
|
d->mergedTokens.prepend((*stack)[ stack->size()-1 ]);
|
|
if( count )
|
|
d->mergedTokens.prepend(mergeString);
|
|
stack->resize( stack->size()-1 );
|
|
}
|
|
|
|
stack->push_back( d->mergedTokens );
|
|
}
|
|
|
|
void ExcelReader::mergeTokens( UStringStack* stack, int count, const char mergeChar )
|
|
{
|
|
if( !stack ) return;
|
|
if( !stack->size() ) return;
|
|
if( count < 1 ) return;
|
|
|
|
d->mergedTokens.truncate(0);
|
|
while(count)
|
|
{
|
|
count--;
|
|
|
|
// sanity check
|
|
if(stack->size() == 0) break;
|
|
|
|
d->mergedTokens.prepend((*stack)[ stack->size()-1 ]);
|
|
if( count )
|
|
d->mergedTokens.prepend(mergeChar);
|
|
stack->resize( stack->size()-1 );
|
|
}
|
|
|
|
stack->push_back( d->mergedTokens );
|
|
}
|
|
|
|
#ifdef SWINDER_XLS2RAW
|
|
void dumpStack( std::vector<UString> stack )
|
|
{
|
|
std::cout << std::endl;
|
|
std::cout << "Stack now is: " ;
|
|
if( !stack.size() )
|
|
std::cout << "(empty)" ;
|
|
|
|
for( unsigned i = 0; i < stack.size(); i++ )
|
|
std::cout << " " << i << ": " << stack[i].ascii() << std::endl;
|
|
std::cout << std::endl;
|
|
}
|
|
#endif
|
|
|
|
UString ExcelReader::decodeFormula( unsigned row, unsigned col,
|
|
const FormulaTokens& tokens, bool openDocumentFormat )
|
|
{
|
|
UStringStack stack;
|
|
|
|
char argumentSeparator = ',';
|
|
if( openDocumentFormat )
|
|
argumentSeparator = ';';
|
|
|
|
for( unsigned c=0; c < tokens.size(); c++ )
|
|
{
|
|
FormulaToken token = tokens[c];
|
|
|
|
#ifdef SWINDER_XLS2RAW
|
|
std::cout << "Token " << c << ": ";
|
|
std::cout << token.id() << " ";
|
|
std::cout << token.idAsString() << std::endl;
|
|
#endif
|
|
|
|
switch( token.id() )
|
|
{
|
|
case FormulaToken::Add:
|
|
mergeTokens( &stack, 2, '+' );
|
|
break;
|
|
|
|
case FormulaToken::Sub:
|
|
mergeTokens( &stack, 2, '-' );
|
|
break;
|
|
|
|
case FormulaToken::Mul:
|
|
mergeTokens( &stack, 2, '*' );
|
|
break;
|
|
|
|
case FormulaToken::Div:
|
|
mergeTokens( &stack, 2, '/' );
|
|
break;
|
|
|
|
case FormulaToken::Power:
|
|
mergeTokens( &stack, 2, '^' );
|
|
break;
|
|
|
|
case FormulaToken::Concat:
|
|
mergeTokens( &stack, 2, '&' );
|
|
break;
|
|
|
|
case FormulaToken::LT:
|
|
mergeTokens( &stack, 2, '<' );
|
|
break;
|
|
|
|
case FormulaToken::LE:
|
|
mergeTokens( &stack, 2, "<=" );
|
|
break;
|
|
|
|
case FormulaToken::EQ:
|
|
mergeTokens( &stack, 2, '=' );
|
|
break;
|
|
|
|
case FormulaToken::GE:
|
|
mergeTokens( &stack, 2, ">=" );
|
|
break;
|
|
|
|
case FormulaToken::GT:
|
|
mergeTokens( &stack, 2, '>' );
|
|
break;
|
|
|
|
case FormulaToken::NE:
|
|
mergeTokens( &stack, 2, "<>" );
|
|
break;
|
|
|
|
case FormulaToken::Intersect:
|
|
mergeTokens( &stack, 2, ' ' );
|
|
break;
|
|
|
|
case FormulaToken::List:
|
|
mergeTokens( &stack, 2, ';' );
|
|
break;
|
|
|
|
case FormulaToken::Range:
|
|
mergeTokens( &stack, 2, ';' );
|
|
break;
|
|
|
|
case FormulaToken::UPlus:
|
|
if(stack.size() > 1)
|
|
stack[ stack.size()-1 ].prepend('+');
|
|
break;
|
|
|
|
case FormulaToken::UMinus:
|
|
if(stack.size() > 1)
|
|
stack[ stack.size()-1 ].prepend('-');
|
|
break;
|
|
|
|
case FormulaToken::Percent:
|
|
stack[ stack.size()-1 ].append('%');
|
|
break;
|
|
|
|
case FormulaToken::Paren:
|
|
{
|
|
UString str(stack[ stack.size()-1 ]);
|
|
str.prepend('(');
|
|
str.append(')');
|
|
stack[ stack.size()-1 ] = str;
|
|
}
|
|
break;
|
|
|
|
case FormulaToken::MissArg:
|
|
// just ignore
|
|
stack.push_back( UString(" ") );
|
|
break;
|
|
|
|
case FormulaToken::String:
|
|
{
|
|
UString str(token.value().asString());
|
|
str.prepend('\"');
|
|
str.append('\"');
|
|
stack.push_back( str );
|
|
}
|
|
break;
|
|
|
|
case FormulaToken::Bool:
|
|
if( token.value().asBoolean() )
|
|
stack.push_back( UString( "TRUE" ) );
|
|
else
|
|
stack.push_back( UString( "FALSE" ) );
|
|
break;
|
|
|
|
case FormulaToken::Integer:
|
|
stack.push_back( UString::number( token.value().asInteger() ) );
|
|
break;
|
|
|
|
case FormulaToken::Float:
|
|
stack.push_back( UString::number( token.value().asFloat() ) );
|
|
break;
|
|
|
|
case FormulaToken::Array:
|
|
// FIXME handle this !
|
|
break;
|
|
|
|
case FormulaToken::Ref:
|
|
{
|
|
UString refName(token.ref( row, col ));
|
|
if( openDocumentFormat )
|
|
{
|
|
refName.prepend('[');
|
|
refName.append(']');
|
|
}
|
|
stack.push_back( refName );
|
|
}
|
|
break;
|
|
|
|
case FormulaToken::Ref3d:
|
|
{
|
|
UString refName("#REF");
|
|
refName.reserve(32);
|
|
|
|
unsigned sheetIndex = token.externSheetRef();
|
|
if(sheetIndex < d->sheetRefs.size())
|
|
{
|
|
UString cellName = token.ref(row, col);
|
|
UString sheetName = d->sheetRefs[sheetIndex];
|
|
|
|
// OpenDocument example: [Sheet1.B1]
|
|
if( openDocumentFormat )
|
|
{
|
|
refName = UString("[");
|
|
refName.append( sheetName );
|
|
if(!sheetName.isEmpty())
|
|
refName.append(UString("."));
|
|
refName.append( cellName );
|
|
refName.append(UString("]"));
|
|
}
|
|
else
|
|
{
|
|
refName = sheetName;
|
|
if(!sheetName.isEmpty())
|
|
refName.append(UString("."));
|
|
refName.append(UString("!"));
|
|
refName.append(cellName);
|
|
}
|
|
}
|
|
|
|
stack.push_back( refName );
|
|
}
|
|
break;
|
|
|
|
case FormulaToken::Area:
|
|
{
|
|
UString areaName( token.area( row, col ) );
|
|
if( openDocumentFormat )
|
|
{
|
|
areaName.prepend('[');
|
|
areaName.append(']');
|
|
}
|
|
stack.push_back( areaName);
|
|
}
|
|
break;
|
|
|
|
case FormulaToken::Area3d:
|
|
{
|
|
UString areaName("#REF");
|
|
areaName.reserve(32);
|
|
|
|
unsigned sheetIndex = token.externSheetRef();
|
|
if(sheetIndex < d->sheetRefs.size())
|
|
{
|
|
UString rangeName = token.area(row, col);
|
|
UString sheetName = d->sheetRefs[sheetIndex];
|
|
|
|
// OpenDocument example: [Sheet1.B1:B3]
|
|
if( openDocumentFormat )
|
|
{
|
|
areaName = UString("[");
|
|
areaName.append( sheetName );
|
|
if(!sheetName.isEmpty())
|
|
areaName.append('.');
|
|
areaName.append( rangeName );
|
|
areaName.append(']');
|
|
}
|
|
else
|
|
{
|
|
areaName = sheetName;
|
|
if(!sheetName.isEmpty())
|
|
{
|
|
areaName.append('.');
|
|
areaName.append('!');
|
|
}
|
|
areaName.append(rangeName);
|
|
}
|
|
}
|
|
stack.push_back( areaName);
|
|
}
|
|
break;
|
|
|
|
case FormulaToken::Function:
|
|
{
|
|
mergeTokens( &stack, token.functionParams(), argumentSeparator );
|
|
if( stack.size() )
|
|
{
|
|
UString str( token.functionName() ? token.functionName() : "??" );
|
|
str.reserve(256);
|
|
str.append( '(' );
|
|
str.append( stack[ stack.size()-1 ] );
|
|
str.append( ')' );
|
|
stack[ stack.size()-1 ] = str;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case FormulaToken::FunctionVar:
|
|
if( token.functionIndex() != 255 )
|
|
{
|
|
mergeTokens( &stack, token.functionParams(), argumentSeparator );
|
|
if( stack.size() )
|
|
{
|
|
UString str;
|
|
if( token.functionIndex() != 255 )
|
|
str = token.functionName() ? token.functionName() : "??";
|
|
str.reserve(256);
|
|
str.append( '(' );
|
|
str.append( stack[ stack.size()-1 ] );
|
|
str.append( ')' );
|
|
stack[ stack.size()-1 ] = str;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
unsigned count = token.functionParams()-1;
|
|
mergeTokens( &stack, count, argumentSeparator );
|
|
if( stack.size() )
|
|
{
|
|
UString str;
|
|
str.append( '(' );
|
|
str.append( stack[ stack.size()-1 ] );
|
|
str.append( ')' );
|
|
stack[ stack.size()-1 ] = str;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case FormulaToken::Attr:
|
|
if( token.attr() & 0x10 ) // SUM
|
|
{
|
|
mergeTokens( &stack, 1, argumentSeparator );
|
|
if( stack.size() )
|
|
{
|
|
UString str( "SUM" );
|
|
str.append( '(' );
|
|
str.append( stack[ stack.size()-1 ] );
|
|
str.append( ')' );
|
|
stack[ stack.size()-1 ] = str;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case FormulaToken::NameX:
|
|
if( token.nameIndex() > 0 )
|
|
if( token.nameIndex() <= d->nameTable.size() )
|
|
stack.push_back( d->nameTable[ token.nameIndex()-1 ] );
|
|
break;
|
|
|
|
case FormulaToken::Matrix:
|
|
{
|
|
int row = token.refRow();
|
|
int col = token.refColumn();
|
|
//std::cout << "row is " << row << " col is " << col << std::endl;
|
|
}
|
|
break;
|
|
|
|
case FormulaToken::NatFormula:
|
|
case FormulaToken::Sheet:
|
|
case FormulaToken::EndSheet:
|
|
case FormulaToken::ErrorCode:
|
|
case FormulaToken::Name:
|
|
case FormulaToken::MemArea:
|
|
case FormulaToken::MemErr:
|
|
case FormulaToken::MemNoMem:
|
|
case FormulaToken::MemFunc:
|
|
case FormulaToken::RefErr:
|
|
case FormulaToken::AreaErr:
|
|
case FormulaToken::RefN:
|
|
case FormulaToken::AreaN:
|
|
case FormulaToken::MemAreaN:
|
|
case FormulaToken::MemNoMemN:
|
|
case FormulaToken::RefErr3d:
|
|
case FormulaToken::AreaErr3d:
|
|
default:
|
|
// FIXME handle this !
|
|
stack.push_back( UString("UnknownToken") );
|
|
//std::cout << "UnknownToken ID=" << token.id() << std::endl;
|
|
break;
|
|
};
|
|
|
|
#ifdef SWINDER_XLS2RAW
|
|
dumpStack( stack );
|
|
#endif
|
|
|
|
}
|
|
|
|
UString result;
|
|
for( unsigned i = 0; i < stack.size(); i++ )
|
|
result.append( stack[i] );
|
|
|
|
#ifdef SWINDER_XLS2RAW
|
|
std::cout << "FORMULA Result: " << result << std::endl;
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
|
|
#ifdef SWINDER_XLS2RAW
|
|
|
|
#include <iostream>
|
|
|
|
int main( int argc, char ** argv )
|
|
{
|
|
if( argc < 2 )
|
|
{
|
|
std::cout << "Usage: xls2raw filename" << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
char* filename = argv[1];
|
|
std::cout << "Checking " << filename << std::endl;
|
|
|
|
Workbook* workbook = new Workbook();
|
|
ExcelReader* reader = new ExcelReader();
|
|
reader->load( workbook, filename );
|
|
delete reader;
|
|
delete workbook;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif // XLS2RAW
|