mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-16 06:05:58 -06:00
547 lines
16 KiB
C++
547 lines
16 KiB
C++
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
*
|
|
* This file is part of xlslib -- A multiplatform, C/C++ library
|
|
* for dynamic generation of Excel(TM) files.
|
|
*
|
|
* xlslib is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* xlslib is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with xlslib. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Copyright 2004 Yeico S. A. de C. V.
|
|
* Copyright 2008 David Hoerl
|
|
*
|
|
* $Source: /cvsroot/xlslib/xlslib/src/oledoc/oledoc.cpp,v $
|
|
* $Revision: 1.3 $
|
|
* $Author: dhoerl $
|
|
* $Date: 2008/10/25 18:39:53 $
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
*
|
|
* File description:
|
|
*
|
|
*
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#include <XLS/ole/oledoc.h>
|
|
#include <XLS/timespan.h>
|
|
|
|
using namespace std;
|
|
using namespace xlslib_core;
|
|
|
|
/*
|
|
***********************************
|
|
COleDoc class implementation
|
|
***********************************
|
|
*/
|
|
const unsigned8_t COleDoc::OLE_FILETYPE[] =
|
|
{ 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1};
|
|
|
|
|
|
COleDoc::COleDoc()
|
|
{
|
|
|
|
}
|
|
/*
|
|
COleDoc::COleDoc(const string& file_name)
|
|
{
|
|
Open(file_name);
|
|
|
|
}
|
|
*/
|
|
|
|
COleDoc::~COleDoc()
|
|
{
|
|
|
|
}
|
|
|
|
/*
|
|
***********************************
|
|
***********************************
|
|
*/
|
|
|
|
int COleDoc::DumpHeader(blocks bks, unsigned32_t total_data_size)
|
|
{
|
|
unsigned32_t i, total_data_blocks, sectorID, msatID;
|
|
int errcode = NO_ERRORS;
|
|
|
|
total_data_blocks = total_data_size/BIG_BLOCK_SIZE;
|
|
|
|
#if OLE_DEBUG
|
|
fprintf(stderr, "dataBlocks=%u\n", total_data_blocks), fflush(stderr);
|
|
#endif
|
|
// [00]FILETYPE
|
|
WriteByteArray(COleDoc::OLE_FILETYPE, /*(unsigned32_t)*/sizeof(COleDoc::OLE_FILETYPE));
|
|
// [08]UK1
|
|
WriteSigned32(HEADVAL_DFLT_UK1);
|
|
// [0c]UK2
|
|
WriteSigned32(HEADVAL_DFLT_UK2);
|
|
// [10]UK2b
|
|
WriteSigned32(HEADVAL_DFLT_UK2b);
|
|
// [14]UK3
|
|
WriteSigned32(HEADVAL_DFLT_UK3);
|
|
// [18]UK4
|
|
WriteSigned16(HEADVAL_DFLT_UK4);
|
|
// [1a]UK5
|
|
WriteSigned16(HEADVAL_DFLT_UK5);
|
|
// [1c]UK6
|
|
WriteSigned16(HEADVAL_DFLT_UK6);
|
|
// [1e]LOG_2_BIG_BLOCK
|
|
WriteSigned16(HEADVAL_DFLT_LOG2_BIGBLOCK);
|
|
// [20]LOG_2_SMALL_BLOCK
|
|
WriteSigned32(HEADVAL_DFLT_LOG2_SMALLBLOCK);
|
|
// [24]UK7
|
|
WriteSigned32(HEADVAL_DFLT_UK7);
|
|
// [28]UK8
|
|
WriteSigned32(HEADVAL_DFLT_UK8);
|
|
|
|
// [2c] BAT_COUNT (BBDEPOT NUM BLOCKS)
|
|
WriteUnsigned32(bks.bat_count);
|
|
|
|
//[30] PROPERTIES_START_BLOCK
|
|
// Since the big block depot will go immediately after the data, I need
|
|
// to know the size of the data and the size of the BAT in blocks (prev)
|
|
WriteUnsigned32(bks.msat_count+total_data_blocks+bks.bat_count);
|
|
#if OLE_DEBUG
|
|
fprintf(stderr, "HEADER says directory at %d\n", bks.msat_count+total_data_blocks+bks.bat_count);
|
|
#endif
|
|
// [34] UK9
|
|
WriteSigned32(HEADVAL_DFLT_UK9);
|
|
// [38] UK10
|
|
WriteSigned32(HEADVAL_DFLT_UK10);
|
|
|
|
// [3c] SBAT_START
|
|
// No small blocks will be used, so this is set to the default empty value
|
|
WriteSigned32(HEADVAL_DFLT_SBAT_START);
|
|
|
|
// [40] SBAT_BLOCKCOUNT_NUMBER
|
|
// Use the default value
|
|
WriteSigned32(HEADVAL_DFLT_SBAT_COUNT);
|
|
|
|
// [44] XBAT_START
|
|
// we will use first and possibly additional blocks for large files
|
|
WriteSigned32(bks.msat_count ? 0 : HEADVAL_DFLT_XBAT_START);
|
|
#if OLE_DEBUG
|
|
fprintf(stderr, "xbatStart=%d\n", bks.msat_count ? 0 : HEADVAL_DFLT_XBAT_START), fflush(stderr);
|
|
#endif
|
|
|
|
// [48] XBAT_COUNT
|
|
WriteUnsigned32(bks.msat_count); // was HEADVAL_DFLT_XBAT_COUNT (0)
|
|
#if OLE_DEBUG
|
|
fprintf(stderr, "msat_count=%d\n", bks.msat_count), fflush(stderr);
|
|
#endif
|
|
|
|
// [4C] BAT_ARRAY
|
|
// The BAT_ARRAY shall be calculated from the number of BAT blocks and their position
|
|
|
|
// The additional blocks, if needed, are directly below the header block, so we can write
|
|
// them out contiguously. The special conditions are:
|
|
// * for each MSAT block, the last entry needs to be a pointer to the next block
|
|
// * the fill is -1 for all unused entries
|
|
// * if there are MSAT blocks, the very last entry in the last block is a special marker
|
|
// first sector ID
|
|
sectorID = bks.msat_count + total_data_blocks;
|
|
for(i=0; i<bks.header_bat_count; i++) {
|
|
#if OLE_DEBUG
|
|
// fprintf(stderr, "sectorID=%d\n", sectorID), fflush(stderr);
|
|
#endif
|
|
WriteUnsigned32(sectorID++);
|
|
}
|
|
#if OLE_DEBUG
|
|
fprintf(stderr, "Position=0x%lx\n", Position() );
|
|
// fprintf(stderr, "SEC_ID[%u]=%u\n", i, total_data_size/(BIG_BLOCK_SIZE) + i), fflush(stderr);
|
|
#endif
|
|
|
|
// if we don't fill the header table, zero out unused entries
|
|
for(i = 0; i<bks.header_fill; i++) {
|
|
WriteSigned32(BAT_NOT_USED);
|
|
}
|
|
|
|
#if OLE_DEBUG
|
|
fprintf(stderr, "Position=0x%lx\n", Position() );
|
|
#endif
|
|
|
|
// plow ahead, adding up to 127 entries per extra MSAT block
|
|
msatID = 1; // sector 0 is the first MSAT, if used
|
|
for(i=1; i<=bks.extra_bat_count; i++) {
|
|
WriteUnsigned32(sectorID++);
|
|
if((i % BAT_BLOCKS_PER_MSAT_BLOCK) == 0) {
|
|
if(i == bks.extra_bat_count) {
|
|
WriteSigned32(BAT_END_CHAIN); // pointer to next MSAT
|
|
} else {
|
|
WriteUnsigned32(msatID++); // pointer to next MSAT
|
|
}
|
|
}
|
|
}
|
|
if(bks.extra_fill) {
|
|
for(i = 0; i<bks.extra_fill; i++) {
|
|
WriteSigned32(BAT_NOT_USED);
|
|
}
|
|
WriteSigned32(BAT_END_CHAIN); // pointer to next MSAT
|
|
}
|
|
|
|
#if OLE_DEBUG
|
|
fprintf(stderr, "Position=0x%lx\n", Position() );
|
|
#endif
|
|
|
|
assert(Position() == (HEAD_SIZE + (bks.msat_count*BIG_BLOCK_SIZE)));
|
|
|
|
return errcode;
|
|
}
|
|
|
|
/*
|
|
***********************************
|
|
***********************************
|
|
*/
|
|
int COleDoc::DumpData(void)
|
|
{
|
|
int errcode = NO_ERRORS;
|
|
|
|
NodeList_t node_list;
|
|
GetAllNodes(node_list);
|
|
for(NodeList_Itor_t i = node_list.begin(); i != node_list.end(); i++)
|
|
{
|
|
if((*i)->GetType() == PTYPE_FILE)
|
|
{
|
|
for(DataList_Itor_t j = (*i)->GetDataPointer()->begin();
|
|
j != (*i)->GetDataPointer()->end(); j++)
|
|
{
|
|
WriteByteArray((*j)->GetBuffer(), (*j)->GetDataSize());
|
|
}
|
|
}
|
|
}
|
|
|
|
return errcode;
|
|
}
|
|
|
|
/*
|
|
***********************************
|
|
***********************************
|
|
*/
|
|
int COleDoc::DumpDepots(blocks bks)
|
|
{
|
|
int errcode = NO_ERRORS;
|
|
|
|
NodeList_t node_list;
|
|
GetAllNodes(node_list);
|
|
unsigned32_t bat_index;
|
|
|
|
bat_index = 0;
|
|
|
|
// tells Excel that these are used by the MSAT
|
|
for(unsigned32_t i=0; i<bks.msat_count; ++i) {
|
|
WriteSigned32(BAT_MSAT_PLACE);
|
|
++bat_index;
|
|
++bks._bat_entries;
|
|
}
|
|
|
|
#if OLE_DEBUG
|
|
int foo=0;
|
|
#endif
|
|
for(NodeList_Itor_t node = node_list.begin(); node != node_list.end(); node++)
|
|
{
|
|
unsigned32_t chain_len, data_size;
|
|
// The start block is set in the node.
|
|
(*node)->SetStartBlock(bat_index);
|
|
// Write the chain for this node element
|
|
data_size = (*node)->GetDataPointer()->GetDataSize();
|
|
chain_len = data_size/BIG_BLOCK_SIZE - 1;
|
|
#if OLE_DEBUG
|
|
fprintf(stderr, "NODE[%d]: start_block=%d data=%d Sectors= %d\n", foo, bat_index, chain_len + 1 /* directory_terminator */);
|
|
#endif
|
|
for(unsigned32_t i = 0; i < chain_len; i++)
|
|
{
|
|
WriteSigned32(++bat_index);
|
|
++bks._bat_entries;
|
|
}
|
|
|
|
// Set the terminator number
|
|
WriteSigned32(BAT_END_CHAIN);
|
|
++bat_index;
|
|
++bks._bat_entries;
|
|
}
|
|
|
|
#if OLE_DEBUG
|
|
fprintf(stderr, "BAT_SELF_PLACE=%d -> %d TOTAL=%d\n", bat_index+1, bat_index+1+bks.bat_count+1, bks.bat_count);
|
|
#endif
|
|
// Write the -3 number for every index in the BAT that references to some BAT block (uh!?)
|
|
for(unsigned32_t i=0; i<bks.bat_count;i++)
|
|
{
|
|
WriteSigned32(BAT_SELF_PLACE);
|
|
++bat_index;
|
|
++bks._bat_entries;
|
|
}
|
|
#if OLE_DEBUG
|
|
fprintf(stderr, "last write: left = %d\n", Position() % HEAD_SIZE);
|
|
#endif
|
|
|
|
// This is the entry for the directory chain, the very last block, saying directory is just one sector
|
|
WriteSigned32(BAT_END_CHAIN);
|
|
++bat_index;
|
|
++bks._bat_entries;
|
|
|
|
//Fill the rest of the _LAST_ BAT block, code appears to handle the 0 case
|
|
unsigned32_t num_indexes = bat_index;
|
|
unsigned32_t to_fill_size = BIG_BLOCK_SIZE - ((4*num_indexes) % BIG_BLOCK_SIZE);
|
|
SerializeFixedArray(BAT_NOT_USED_BYTE, to_fill_size);
|
|
|
|
#if OLE_DEBUG
|
|
fprintf(stderr, "last write: left = %d\n", Position() % HEAD_SIZE);
|
|
#endif
|
|
|
|
#if OLE_DEBUG
|
|
fprintf(stderr, "Position=0x%lx\n", Position() );
|
|
fprintf(stderr, "bat_entries=%d actual=%d\n", bks.bat_entries, bks._bat_entries );
|
|
#endif
|
|
|
|
assert(bks.bat_entries == bks._bat_entries);
|
|
|
|
return errcode;
|
|
}
|
|
|
|
/*
|
|
***********************************
|
|
***********************************
|
|
*/
|
|
int COleDoc::DumpFileSystem(void)
|
|
{
|
|
int errcode = NO_ERRORS;
|
|
|
|
|
|
NodeList_t node_list;
|
|
GetAllNodes(node_list);
|
|
|
|
#if OLE_DEBUG
|
|
fprintf(stderr, "FILESYSTEM directory at SecID=%d (remain=%d)\n", (Position() - HEAD_SIZE)/BIG_BLOCK_SIZE, Position() % BIG_BLOCK_SIZE);
|
|
#endif
|
|
DumpNode(GetRootEntry());
|
|
|
|
for(NodeList_Itor_t node = node_list.begin(); node != node_list.end(); node++)
|
|
DumpNode(*(*node));
|
|
|
|
return errcode;
|
|
}
|
|
|
|
/*
|
|
***********************************
|
|
***********************************
|
|
*/
|
|
int COleDoc::DumpOleFile(void)
|
|
{
|
|
blocks bks;
|
|
|
|
int errcode = NO_ERRORS;
|
|
|
|
bks = GetBATCount();
|
|
unsigned32_t total_data_size = GetTotalDataSize();
|
|
|
|
errcode |= DumpHeader(bks, total_data_size);
|
|
assert((Position() % 512) == 0 /*1*/);
|
|
|
|
errcode |= DumpData();
|
|
assert((Position() % 512) == 0 /*2*/);
|
|
|
|
errcode |= DumpDepots(bks);
|
|
assert((Position() % 512) == 0 /*3*/);
|
|
|
|
errcode |= DumpFileSystem();
|
|
assert((Position() % 512) == 0 /*3*/);
|
|
|
|
return errcode;
|
|
}
|
|
|
|
/***************************************************************
|
|
*****************************************************************/
|
|
|
|
blocks COleDoc::GetBATCount()
|
|
{
|
|
blocks bks;
|
|
unsigned32_t bat_num_entries, data_bat_entries, bat_num_blocks, dir_bat_entries, bat_blocks_needed, bat_block_capacity;
|
|
unsigned32_t extra_bats, msat_blocks, msat_bats, last_block_extras;
|
|
|
|
memset(&bks, 0, sizeof(bks));
|
|
|
|
data_bat_entries = GetTotalDataSize()/BIG_BLOCK_SIZE; // + GetNumDataFiles(); //terminator???
|
|
assert(GetTotalDataSize() == (data_bat_entries * BIG_BLOCK_SIZE) );
|
|
|
|
// Block allocation strategy. Within the OLE header are 109 slots for BAT Sectors.
|
|
// But, when the file gets big, you run out (127 sectors in each BAT Sector). So,
|
|
// the 110th BAT has to go into a special block dedicated to hold these. One additional
|
|
// block gets you 127 more BAT entries, and so forth.
|
|
|
|
dir_bat_entries = 1; // Last block is directory, this is the terminator that says dir uses only one sector
|
|
bat_num_blocks = 1; // seed to enter loop below
|
|
bat_block_capacity = 0;
|
|
msat_blocks = 0;
|
|
msat_bats = 0;
|
|
bat_num_entries = 0; // kch
|
|
bat_blocks_needed = data_bat_entries/BAT_ENTRIES_PER_BLOCK; // minimum
|
|
bat_blocks_needed += bat_blocks_needed/BAT_ENTRIES_PER_BLOCK; // for the BAT itself
|
|
|
|
// first loop assumes no MSATs involved
|
|
while(bat_num_blocks > bat_block_capacity || bat_num_blocks != bat_blocks_needed) {
|
|
bat_num_blocks = bat_blocks_needed;
|
|
if(bat_num_blocks > HEADER_SAT_SIZE) {
|
|
msat_bats = bat_num_blocks - HEADER_SAT_SIZE;
|
|
msat_blocks = msat_bats/BAT_BLOCKS_PER_MSAT_BLOCK;
|
|
if(msat_bats % BAT_BLOCKS_PER_MSAT_BLOCK) ++msat_blocks;
|
|
}
|
|
|
|
bat_num_entries = msat_blocks + data_bat_entries + bat_num_blocks + dir_bat_entries; // bat_bat_entries
|
|
|
|
// based on what we know now, this is what we need
|
|
bat_blocks_needed = bat_num_entries/BAT_ENTRIES_PER_BLOCK;
|
|
if(bat_num_entries % BAT_ENTRIES_PER_BLOCK) ++bat_blocks_needed;
|
|
|
|
// number of slots available
|
|
bat_block_capacity = HEADER_SAT_SIZE + BAT_BLOCKS_PER_MSAT_BLOCK*msat_blocks;
|
|
#if OLE_DEBUG
|
|
fprintf(stderr, "bat_blocks=%d capacity=%d needed=%d\n", bat_num_blocks, bat_block_capacity, bat_blocks_needed);
|
|
#endif
|
|
}
|
|
|
|
if(bat_num_blocks > HEADER_SAT_SIZE) {
|
|
extra_bats = bat_num_blocks - HEADER_SAT_SIZE;
|
|
|
|
bks.msat_count = msat_blocks;
|
|
bks.header_bat_count = HEADER_SAT_SIZE;
|
|
bks.extra_bat_count = extra_bats;
|
|
|
|
last_block_extras = extra_bats % BAT_BLOCKS_PER_MSAT_BLOCK;
|
|
if(last_block_extras) {
|
|
bks.extra_fill = BAT_BLOCKS_PER_MSAT_BLOCK - last_block_extras;
|
|
}
|
|
} else {
|
|
bks.header_bat_count = bat_num_blocks;
|
|
bks.header_fill = HEADER_SAT_SIZE - bat_num_blocks;
|
|
}
|
|
bks.bat_entries = bat_num_entries;
|
|
bks.bat_count = bat_num_blocks;
|
|
|
|
#if OLE_DEBUG
|
|
fprintf(stderr, "entries=%u bats=%d msats=%d headerBats=%d extraBats=%d headFill=%d extraFill=%d\n", bks.bat_entries,
|
|
bks.bat_count, bks.msat_count, bks.header_bat_count, bks.extra_bat_count, bks.header_fill, bks.extra_fill);
|
|
#endif
|
|
|
|
return bks;
|
|
}
|
|
|
|
|
|
/*
|
|
***********************************
|
|
***********************************
|
|
*/
|
|
// NOTE: name_unicode has to be deleted after this function finishes.
|
|
// Ideally, this function should be implemented as part of a std::string
|
|
// derived class, so the array would be deleted automatically
|
|
|
|
|
|
signed16_t COleDoc::GetUnicodeName(const char* name, char** ppname_unicode)
|
|
{
|
|
unsigned16_t name_size = strlen(name);
|
|
if(name_size > PROPERTY_MAX_NAME_LENGTH)
|
|
name_size = PROPERTY_MAX_NAME_LENGTH;
|
|
|
|
unsigned8_t size_unicode = (name_size+1)*2;
|
|
|
|
if(*ppname_unicode != NULL) delete[] *ppname_unicode;
|
|
*ppname_unicode = (char*)new unsigned8_t[size_unicode];
|
|
memset(*ppname_unicode, 0x00, size_unicode);
|
|
|
|
for(int i=0; i<(size_unicode/2-1); i++)
|
|
(*ppname_unicode)[2*i] = name[i];
|
|
|
|
return size_unicode;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
***********************************
|
|
***********************************
|
|
*/
|
|
int COleDoc::DumpNode(COleProp& node)
|
|
{
|
|
|
|
int errcode = NO_ERRORS;
|
|
char* name_unicode = NULL;
|
|
|
|
// Get the unicode name and its size
|
|
signed16_t size_name = GetUnicodeName(node.GetName().c_str(), &name_unicode);
|
|
|
|
// [00] PROPERTY_NAME
|
|
WriteByteArray((const unsigned8_t*)name_unicode, size_name);
|
|
|
|
// Fill the rest of the name field with 0x00
|
|
SerializeFixedArray(PROPERTY_DFLT_NOTUSED, PPTPOS_NAMELENGTH - size_name);
|
|
|
|
// [40] NAME_SIZE
|
|
WriteSigned16(size_name);
|
|
|
|
// [42] PROPERTY_TYPE
|
|
WriteByte(node.GetType());
|
|
|
|
// [43] NODE_COLOR
|
|
WriteByte(node.GetColor());
|
|
|
|
// [44] PREVIOUS_PROP
|
|
WriteSigned32(node.GetPreviousIndex());
|
|
// [48] NEXT_PROP
|
|
WriteSigned32(node.GetNextIndex());
|
|
// [4c] CHILD_PROP
|
|
WriteSigned32(node.GetChildIndex());
|
|
|
|
// Fill empty block
|
|
SerializeFixedArray(PROPERTY_DFLT_NOTUSED, (PPTPOS_SECS1 - PPTPOS_UNUSED_EMPTY0));
|
|
|
|
//[64]...[70]
|
|
// SECONDS_1, DAYS_2, SECONDS_2, DAYS_2
|
|
WriteSigned32(node.GetCreatedSecs());
|
|
WriteSigned32(node.GetCreatedDays());
|
|
WriteSigned32(node.GetModifiedDays());
|
|
WriteSigned32(node.GetModifiedSecs());
|
|
|
|
// [74] START_BLOCK
|
|
#if OLE_DEBUG
|
|
fprintf(stderr, "START_BLOCK_1=%d\n", node.GetStartBlock() );
|
|
#endif
|
|
WriteSigned32(node.GetStartBlock());
|
|
|
|
// [78] SIZE
|
|
if(node.GetType() == PTYPE_FILE)
|
|
WriteSigned32(node.GetSize());
|
|
else
|
|
WriteSigned32(0);
|
|
|
|
// A unused space:
|
|
WriteSigned32(PROPERTY_DFLT_NOTUSED);
|
|
|
|
delete[] name_unicode;
|
|
name_unicode = NULL;
|
|
|
|
return errcode;
|
|
|
|
}
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
* $Log: oledoc.cpp,v $
|
|
* Revision 1.3 2008/10/25 18:39:53 dhoerl
|
|
* 2008
|
|
*
|
|
* Revision 1.2 2004/09/01 00:47:04 darioglz
|
|
* + Modified to gain independence of target
|
|
*
|
|
* Revision 1.1.1.1 2004/08/27 16:31:43 darioglz
|
|
* Initial Import.
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|