#include "ExcelFormat.h" namespace YCompoundFiles { /********************************** Start of Class Block *************************************/ // PURPOSE: Manage a file by treating it as blocks of data of a certain size. Block::Block() : blockSize_(512), fileSize_(0), indexEnd_(0), filename_(0) {} bool Block::Create(const wchar_t* filename) // PURPOSE: Create a new block file and open it. // PURPOSE: If file is present, truncate it and then open it. // PROMISE: Return true if file is successfully created and opened, false if otherwise. { // Create new file size_t filenameLength = wcslen(filename); char* name = new char[filenameLength+1]; wcstombs(name, filename, filenameLength); name[filenameLength] = 0; file_.open(name, ios_base::out | ios_base::trunc); file_.close(); file_.clear(); // Open the file bool ret = this->Open(filename); delete[] name; return ret; } bool Block::Open(const wchar_t* filename, ios_base::openmode mode) // PURPOSE: Open an existing block file. // PROMISE: Return true if file is successfully opened, false if otherwise. { // Open existing file for reading or writing or both size_t filenameLength = wcslen(filename); filename_.resize(filenameLength+1, 0); wcstombs(&*(filename_.begin()), filename, filenameLength); file_.open(&*(filename_.begin()), mode | ios_base::binary); if (!file_.is_open()) return false; mode_ = mode; // Calculate filesize if (mode & ios_base::in) { file_.seekg(0, ios_base::end); fileSize_ = file_.tellg(); } else if (mode & ios_base::out) { file_.seekp(0, ios_base::end); fileSize_ = file_.tellp(); } else { this->Close(); return false; } // Calculate last index + 1 indexEnd_ = fileSize_/blockSize_ + (fileSize_ % blockSize_ ? 1 : 0); return true; } bool Block::Close() // PURPOSE: Close the opened block file. // PROMISE: Return true if file is successfully closed, false if otherwise. { file_.close(); file_.clear(); filename_.clear(); fileSize_ = 0; indexEnd_ = 0; blockSize_ = 512; return !file_.is_open(); } bool Block::IsOpen() // PURPOSE: Check if the block file is still opened. // PROMISE: Return true if file is still opened, false if otherwise. { return file_.is_open(); } bool Block::Read(size_t index, char* block) // PURPOSE: Read a block of data from the opened file at the index position. // EXPLAIN: index is from [0..]. // PROMISE: Return true if data are successfully read, false if otherwise. { if (!(mode_ & ios_base::in)) return false; if (index < indexEnd_) { file_.seekg(index * blockSize_); file_.read(block, blockSize_); return !file_.fail(); } else return false; } bool Block::Write(size_t index, const char* block) // PURPOSE: Write a block of data to the opened file at the index position. // EXPLAIN: index is from [0..]. // PROMISE: Return true if data are successfully written, false if otherwise. { if (!(mode_ & ios_base::out)) return false; file_.seekp(index * blockSize_); file_.write(block, blockSize_); if (indexEnd_ <= index) { indexEnd_ = index + 1; fileSize_ += blockSize_; } file_.close(); file_.clear(); file_.open(&*(filename_.begin()), mode_ | ios_base::binary); return file_.is_open(); } bool Block::Swap(size_t index1, size_t index2) // PURPOSE: Swap two blocks of data in the opened file at the index positions. // EXPLAIN: index1 and index2 are from [0..]. // PROMISE: Return true if data are successfully swapped, false if otherwise. { if (!(mode_ & ios_base::out)) return false; if (index1 < indexEnd_ && index2 < indexEnd_) { if (index1 == index2) return true; char* block1 = new char[blockSize_]; if (!this->Read(index1, block1)) return false; char* block2 = new char[blockSize_]; if (!this->Read(index2, block2)) return false; if (!this->Write(index1, block2)) return false; if (!this->Write(index2, block1)) return false; delete[] block1; delete[] block2; return true; } else return false; } bool Block::Move(size_t from, size_t to) // PURPOSE: Move a block of data in the opened file from an index position to another index position. // EXPLAIN: from and to are from [0..]. // PROMISE: Return true if data are successfully moved, false if otherwise. { if (!(mode_ & ios_base::out)) return false; if (from < indexEnd_ && to < indexEnd_) { if (to > from) { for (size_t i=from; i!=to; ++i) { if (!this->Swap(i, i+1)) return false; } } else { for (size_t i=from; i!=to; --i) { if (!this->Swap(i, i-1)) return false; } } return true; } else return false; } bool Block::Insert(size_t index, const char* block) // PURPOSE: Insert a new block of data in the opened file at the index position. // EXPLAIN: index is from [0..]. // PROMISE: Return true if data are successfully inserted, false if otherwise. { if (!(mode_ & ios_base::out)) return false; if (index <= indexEnd_) { // Write block to end of file if (!this->Write(indexEnd_, block)) return false; // Move block to index if necessary if (index < indexEnd_-1) return this->Move(indexEnd_-1, index); else return true; } else { // Write block to index after end of file return this->Write(index, block); } } bool Block::Erase(size_t index) // PURPOSE: Erase a block of data in the opened file at the index position. // EXPLAIN: index is from [0..]. // PROMISE: Return true if data are successfully erased, false if otherwise. { if (!(mode_ & ios_base::out)) return false; if (index < indexEnd_) { fileSize_ -= blockSize_; indexEnd_ -= 1; // Read entire file except the block to be deleted into memory. char* buffer = new char[fileSize_]; for (size_t i=0, j=0; i!=indexEnd_+1; ++i) { file_.seekg(i*blockSize_); if (i != index) { file_.read(buffer+j*blockSize_, blockSize_); ++j; } } file_.close(); file_.open(&*(filename_.begin()), ios_base::out | ios_base::trunc | ios_base::binary); file_.write(buffer, fileSize_); // Write the new file. file_.close(); file_.open(&*(filename_.begin()), mode_ | ios_base::binary); delete[] buffer; return true; } else return false; } bool Block::Erase(vector& indices) // PURPOSE: Erase blocks of data in the opened file at the index positions. // EXPLAIN: Each index in indices is from [0..]. // PROMISE: Return true if data are successfully erased, false if otherwise. { if (!(mode_ & ios_base::out)) return false; // Read entire file except the blocks to be deleted into memory. size_t maxIndices = indices.size(); fileSize_ -= maxIndices*blockSize_; char* buffer = new char[fileSize_]; for (size_t i=0, k=0; i!=indexEnd_; ++i) { file_.seekg(i*blockSize_); bool toDelete = false; for (size_t j=0; jClose();} /************************* Compound File Functions ***************************/ bool CompoundFile::Create(const wchar_t* filename) // PURPOSE: Create a new compound file and open it. // PURPOSE: If file is present, truncate it and then open it. // PROMISE: Return true if file is successfully created and opened, false if otherwise. { Close(); file_.Create(filename); // Write compound file header header_ = Header(); SaveHeader(); // Save BAT blocksIndices_.clear(); blocksIndices_.resize(128, -1); blocksIndices_[0] = -3; blocksIndices_[1] = -2; SaveBAT(); // Save properties Property* root = new Property; wcscpy(root->name_, L"Root Entry"); root->propertyType_ = 5; properties_.push_back(root); SaveProperties(); // Set property tree propertyTrees_ = new PropertyTree; propertyTrees_->parent_ = 0; propertyTrees_->self_ = properties_[0]; propertyTrees_->index_ = 0; currentDirectory_ = propertyTrees_; return true; } bool CompoundFile::Open(const wchar_t* filename, ios_base::openmode mode) // PURPOSE: Open an existing compound file. // PROMISE: Return true if file is successfully opened, false if otherwise. { Close(); if (!file_.Open(filename, mode)) return false; // Load header if (!LoadHeader()) return false; // Load BAT information LoadBAT(); // Load properties propertyTrees_ = new PropertyTree; LoadProperties(); currentDirectory_ = propertyTrees_; return true; } bool CompoundFile::Close() // PURPOSE: Close the opened compound file. // PURPOSE: Reset BAT indices, SBAT indices, properties and properties tree information. // PROMISE: Return true if file is successfully closed, false if otherwise. { blocksIndices_.clear(); sblocksIndices_.clear(); size_t maxProperties = properties_.size(); for (size_t i=0; iparent_ != 0) { currentDirectory_ = currentDirectory_->parent_; } previousDirectories_.pop_back(); return SUCCESS; } if (wcscmp(path, L"\\") == 0) { // Go to root directory currentDirectory_ = propertyTrees_; previousDirectories_.pop_back(); return SUCCESS; } // Handle normal cases size_t ipos = 0; size_t npos = 0; size_t pathLength = wcslen(path); if (pathLength > 0 && path[0] == L'\\') { // Start from root directory currentDirectory_ = propertyTrees_; ++ipos; ++npos; } do { for (; npospropertyType_ = 1; int ret = MakeProperty(path, property); currentDirectory_ = previousDirectories_.back(); previousDirectories_.pop_back(); SaveHeader(); SaveBAT(); SaveProperties(); return ret; } int CompoundFile::PresentWorkingDirectory(wchar_t* path) // PURPOSE: Get the full path of the current directory in the compound file. // REQUIRE: path must be large enough to receive the full path information. { previousDirectories_.push_back(currentDirectory_); vector fullpath; do { size_t directoryLength = wcslen(currentDirectory_->self_->name_); vector directory(directoryLength+1); directory[0] = L'\\'; copy (currentDirectory_->self_->name_, currentDirectory_->self_->name_+directoryLength, directory.begin()+1); fullpath.insert(fullpath.begin(), directory.begin(), directory.end()); } while (currentDirectory_ = currentDirectory_->parent_); fullpath.erase(fullpath.begin(), fullpath.begin()+11); if (fullpath.empty()) fullpath.push_back(L'\\'); copy (fullpath.begin(), fullpath.end(), path); path[fullpath.size()] = 0; currentDirectory_ = previousDirectories_.back(); previousDirectories_.pop_back(); return SUCCESS; } int CompoundFile::PresentWorkingDirectory(vector& path) // PURPOSE: Get the full path of the current directory in the compound file. { previousDirectories_.push_back(currentDirectory_); path.clear(); do { size_t directoryLength = wcslen(currentDirectory_->self_->name_); vector directory(directoryLength+1); directory[0] = L'\\'; copy (currentDirectory_->self_->name_, currentDirectory_->self_->name_+directoryLength, directory.begin()+1); path.insert(path.begin(), directory.begin(), directory.end()); } while (currentDirectory_ = currentDirectory_->parent_); path.erase(path.begin(), path.begin()+11); if (path.empty()) path.push_back(L'\\'); currentDirectory_ = previousDirectories_.back(); previousDirectories_.pop_back(); return SUCCESS; } int CompoundFile::RemoveDirectory(const wchar_t* path) // PURPOSE: Remove a directory in the compound file. // PROMISE: Directory will not be removed if it has subdirectories or files under it. { PropertyTree* directory = FindProperty(path); if (directory == 0) return DIRECTORY_NOT_FOUND; if (directory->self_->childProp_ != -1) return DIRECTORY_NOT_EMPTY; DeletePropertyTree(directory); SaveHeader(); SaveBAT(); SaveProperties(); return SUCCESS; } int CompoundFile::DelTree(const wchar_t* path) // PURPOSE: Remove everything in the path in the compound file, including // PURPOSE: any files and subdirectories. { previousDirectories_.push_back(currentDirectory_); PropertyTree* directory = FindProperty(path); if (directory == 0) return DIRECTORY_NOT_FOUND; if (directory->self_->childProp_ != -1) { size_t maxChildren = directory->children_.size(); wchar_t* curpath = new wchar_t[65535]; for (size_t i=0; ichildren_[i]; PresentWorkingDirectory(curpath); if (directory->children_[i]->self_->propertyType_ == 1) { // Directory DelTree(curpath); } else if (directory->children_[i]->self_->propertyType_ == 2) { // File RemoveFile(curpath); } } directory->self_->childProp_ = -1; delete[] curpath; } if (directory->self_->propertyType_ == 1) { // Directory RemoveDirectory(path); } else if (directory->self_->propertyType_ == 2) { // File RemoveFile(path); } currentDirectory_ = previousDirectories_.back(); previousDirectories_.pop_back(); return SUCCESS; } int CompoundFile::DirectoryList(vector >& list, const wchar_t* path) { previousDirectories_.push_back(currentDirectory_); if (path != 0) { int ret = ChangeDirectory(path); if (ret != SUCCESS) return ret; } list.clear(); size_t maxChildren = currentDirectory_->children_.size(); vector name(32); for (size_t i=0; ichildren_[i]->self_->name_); list.push_back(name); } currentDirectory_ = previousDirectories_.back(); previousDirectories_.pop_back(); return SUCCESS; } /************************* File Functions ***************************/ int CompoundFile::MakeFile(const wchar_t* path) // PURPOSE: Create a new file in the compound file. // PROMISE: File will not be created if it is already present or // PROMISE: a directory with the same name is present. { previousDirectories_.push_back(currentDirectory_); Property* property = new Property; property->propertyType_ = 2; int ret = MakeProperty(path, property); currentDirectory_ = previousDirectories_.back(); previousDirectories_.pop_back(); SaveHeader(); SaveBAT(); SaveProperties(); return ret; } int CompoundFile::RemoveFile(const wchar_t* path) // PURPOSE: Remove a file in the compound file. { int ret = WriteFile(path, 0, 0); if (ret == SUCCESS) { DeletePropertyTree(FindProperty(path)); SaveHeader(); SaveBAT(); SaveProperties(); return SUCCESS; } else return ret; } int CompoundFile::FileSize(const wchar_t* path, size_t& size) // PURPOSE: Get the size of a file in the compound file. // PROMISE: Return the data size stored in the Root Entry if path = "\". // PROMISE: size will not be set if file is not present in the compound file. { // Special case of reading root entry if (wcscmp(path, L"\\") == 0) { size = propertyTrees_->self_->size_; return SUCCESS; } // Check to see if file is present in the specified directory. PropertyTree* property = FindProperty(path); if (property == 0) return FILE_NOT_FOUND; else { size = property->self_->size_; return SUCCESS; } } int CompoundFile::ReadFile(const wchar_t* path, char* data) // PURPOSE: Read a file's data in the compound file. // REQUIRE: data must be large enough to receive the file's data. // REQUIRE: The required data size can be obtained by using FileSize(). // PROMISE: Returns the small blocks of data stored by the Root Entry if path = "\". // PROMISE: data will not be set if file is not present in the compound file. { // Special case of reading root entry char* buffer; if (wcscmp(path, L"\\") == 0) { buffer = new char[DataSize(propertyTrees_->self_->startBlock_, true)]; ReadData(propertyTrees_->self_->startBlock_, buffer, true); copy (buffer, buffer+propertyTrees_->self_->size_, data); delete[] buffer; return SUCCESS; } // Check to see if file is present in the specified directory. PropertyTree* property = FindProperty(path); if (property == 0) return FILE_NOT_FOUND; if (property->self_->size_ >= 4096) { // Data stored in normal big blocks buffer = new char[DataSize(property->self_->startBlock_, true)]; ReadData(property->self_->startBlock_, buffer, true); } else { // Data stored in small blocks buffer = new char[DataSize(property->self_->startBlock_, false)]; ReadData(property->self_->startBlock_, buffer, false); } // Truncated the retrieved data to the actual file size. copy (buffer, buffer+property->self_->size_, data); delete[] buffer; return SUCCESS; } int CompoundFile::ReadFile(const wchar_t* path, vector& data) // PURPOSE: Read a file's data in the compound file. // PROMISE: Returns the small blocks of data stored by the Root Entry if path = "\". // PROMISE: data will not be set if file is not present in the compound file. { data.clear(); size_t dataSize; int ret = FileSize(path, dataSize); if (ret != SUCCESS) return ret; data.resize(dataSize); return ReadFile(path, &*(data.begin())); } int CompoundFile::WriteFile(const wchar_t* path, const char* data, size_t size) // PURPOSE: Write data to a file in the compound file. // PROMISE: The file's original data will be replaced by the new data. { PropertyTree* property = FindProperty(path); if (property == 0) return FILE_NOT_FOUND; if (property->self_->size_ >= 4096) { if (size >= 4096) property->self_->startBlock_ = WriteData(data, size, property->self_->startBlock_, true); else { property->self_->startBlock_ = WriteData(0, 0, property->self_->startBlock_, true); property->self_->startBlock_ = WriteData(data, size, property->self_->startBlock_, false); } } else { if (size < 4096) property->self_->startBlock_ = WriteData(data, size, property->self_->startBlock_, false); else { property->self_->startBlock_ = WriteData(0, 0, property->self_->startBlock_, false); property->self_->startBlock_ = WriteData(data, size, property->self_->startBlock_, true); } } property->self_->size_ = size; SaveHeader(); SaveBAT(); SaveProperties(); return SUCCESS; } int CompoundFile::WriteFile(const wchar_t* path, const vector& data, size_t size) // PURPOSE: Write data to a file in the compound file. // PROMISE: The file's original data will be replaced by the new data. { return WriteFile(path, &*(data.begin()), size); } /*************ANSI char compound file, directory and file functions******************/ bool CompoundFile::Create(const char* filename) { size_t filenameLength = strlen(filename); wchar_t* wname = new wchar_t[filenameLength+1]; mbstowcs(wname, filename, filenameLength); wname[filenameLength] = 0; bool ret = Create(wname); delete[] wname; return ret; } bool CompoundFile::Open(const char* filename, ios_base::openmode mode) { size_t filenameLength = strlen(filename); wchar_t* wname = new wchar_t[filenameLength+1]; mbstowcs(wname, filename, filenameLength); wname[filenameLength] = 0; bool ret = Open(wname, mode); delete[] wname; return ret; } int CompoundFile::ChangeDirectory(const char* path) { size_t pathLength = strlen(path); wchar_t* wpath = new wchar_t[pathLength+1]; mbstowcs(wpath, path, pathLength); wpath[pathLength] = 0; int ret = ChangeDirectory(wpath); delete[] wpath; return ret; } int CompoundFile::MakeDirectory(const char* path) { size_t pathLength = strlen(path); wchar_t* wpath = new wchar_t[pathLength+1]; mbstowcs(wpath, path, pathLength); wpath[pathLength] = 0; int ret = MakeDirectory(wpath); delete[] wpath; return ret; } int CompoundFile::PresentWorkingDirectory(char* path) { size_t pathLength = strlen(path); wchar_t* wpath = new wchar_t[pathLength+1]; int ret = PresentWorkingDirectory(wpath); if (ret == SUCCESS) { pathLength = wcslen(wpath); wcstombs(path, wpath, pathLength); path[pathLength] = 0; } delete[] wpath; return ret; } int CompoundFile::PresentWorkingDirectory(vector& path) { vector wpath; int ret = PresentWorkingDirectory(wpath); if (ret == SUCCESS) { size_t pathLength = wpath.size(); path.resize(pathLength); wcstombs(&*(path.begin()), &*(wpath.begin()), pathLength); path[pathLength] = 0; } return ret; } int CompoundFile::RemoveDirectory(const char* path) { size_t pathLength = strlen(path); wchar_t* wpath = new wchar_t[pathLength+1]; mbstowcs(wpath, path, pathLength); wpath[pathLength] = 0; int ret = RemoveDirectory(wpath); delete[] wpath; return ret; } int CompoundFile::DelTree(const char* path) { size_t pathLength = strlen(path); wchar_t* wpath = new wchar_t[pathLength+1]; mbstowcs(wpath, path, pathLength); wpath[pathLength] = 0; int ret = DelTree(wpath); delete[] wpath; return ret; } int CompoundFile::MakeFile(const char* path) { size_t pathLength = strlen(path); wchar_t* wpath = new wchar_t[pathLength+1]; mbstowcs(wpath, path, pathLength); wpath[pathLength] = 0; int ret = MakeFile(wpath); delete[] wpath; return ret; } int CompoundFile::RemoveFile(const char* path) { size_t pathLength = strlen(path); wchar_t* wpath = new wchar_t[pathLength+1]; mbstowcs(wpath, path, pathLength); wpath[pathLength] = 0; int ret = RemoveFile(wpath); delete[] wpath; return ret; } int CompoundFile::FileSize(const char* path, size_t& size) { size_t pathLength = strlen(path); wchar_t* wpath = new wchar_t[pathLength+1]; mbstowcs(wpath, path, pathLength); wpath[pathLength] = 0; int ret = FileSize(wpath, size); delete[] wpath; return ret; } int CompoundFile::ReadFile(const char* path, char* data) { size_t pathLength = strlen(path); wchar_t* wpath = new wchar_t[pathLength+1]; mbstowcs(wpath, path, pathLength); wpath[pathLength] = 0; int ret = ReadFile(wpath, data); delete[] wpath; return ret; } int CompoundFile::ReadFile(const char* path, vector& data) { size_t pathLength = strlen(path); wchar_t* wpath = new wchar_t[pathLength+1]; mbstowcs(wpath, path, pathLength); wpath[pathLength] = 0; int ret = ReadFile(wpath, data); delete[] wpath; return ret; } int CompoundFile::WriteFile(const char* path, char* data, size_t size) { size_t pathLength = strlen(path); wchar_t* wpath = new wchar_t[pathLength+1]; mbstowcs(wpath, path, pathLength); wpath[pathLength] = 0; int ret = WriteFile(wpath, data, size); delete[] wpath; return ret; } int CompoundFile::WriteFile(const char* path, vector& data, size_t size) { size_t pathLength = strlen(path); wchar_t* wpath = new wchar_t[pathLength+1]; mbstowcs(wpath, path, pathLength); wpath[pathLength] = 0; int ret = WriteFile(wpath, data, size); delete[] wpath; return ret; } /*********************** Inaccessible General Functions ***************************/ void CompoundFile::IncreaseLocationReferences(vector indices) // PURPOSE: Increase block location references in header, BAT indices and properties, // PURPOSE: which will be affected by the insertion of new indices contained in indices. // PROMISE: Block location references which are smaller than all the new indices // PROMISE: will not be affected. // PROMISE: SBAT location references will not be affected. // PROMISE: Changes will not be written to compound file. { size_t maxIndices = indices.size(); // Change BAT Array references {for (size_t i=0; i<109 && header_.BATArray_[i]!=-1; ++i) { size_t count = 0; for (size_t j=0; j= indices[j] && header_.BATArray_[i] != -1) ++count; } header_.BATArray_[i] += count; }} // Change XBAT start block if any if (header_.XBATCount_) { size_t count = 0; for (size_t j=0; j= indices[j] && header_.XBATStart_ != -2) ++count; } header_.XBATStart_ += count; } // Change SBAT start block if any if (header_.SBATCount_) { size_t count = 0; for (size_t j=0; j= indices[j] && header_.SBATStart_ != -2) ++count; } header_.SBATStart_ += count; } // Change BAT block indices size_t maxBATindices = blocksIndices_.size(); {for (size_t i=0; i indices[j] && blocksIndices_[i] != -2 && blocksIndices_[i] != -3) ++count; } blocksIndices_[i] += count; }} // Change properties start block size_t count = 0; {for (size_t i=0; i= indices[i] && header_.propertiesStart_ != -2) ++count; }} header_.propertiesStart_ += count; // Change individual properties start block if their size is more than 4096 size_t maxProperties = properties_.size(); if (!properties_.empty()) { size_t count = 0; for (size_t j=0; jstartBlock_ >= indices[j] && properties_[0]->startBlock_ != -2) ++count; } properties_[0]->startBlock_ += count; } {for (size_t i=1; isize_ >= 4096) { size_t count = 0; for (size_t j=0; jstartBlock_ >= indices[j] && properties_[i]->startBlock_ != -2) ++count; } properties_[i]->startBlock_ += count; } }} } void CompoundFile::DecreaseLocationReferences(vector indices) // PURPOSE: Decrease block location references in header, BAT indices and properties, // PURPOSE: which will be affected by the deletion of indices contained in indices. // PROMISE: BAT indices pointing to a deleted index will be redirected to point to // PROMISE: the location where the deleted index original points to. // PROMISE: Block location references which are smaller than all the new indices // PROMISE: will not be affected. // PROMISE: SBAT location references will not be affected. // PROMISE: Changes will not be written to compound file. { size_t maxIndices = indices.size(); // Change BAT Array references {for (size_t i=0; i<109 && header_.BATArray_[i]!=-1; ++i) { size_t count = 0; for (size_t j=0; j indices[j] && header_.BATArray_[i] != -1) ++count; } header_.BATArray_[i] -= count; }} // Change XBAT start block if any if (header_.XBATCount_) { size_t count = 0; for (size_t j=0; j indices[j] && header_.XBATStart_ != -2) ++count; } header_.XBATStart_ -= count; } // Change SBAT start block if any if (header_.SBATCount_) { size_t count = 0; for (size_t j=0; j indices[j] && header_.SBATStart_ != -2) ++count; } header_.SBATStart_ -= count; } // Change BAT block indices // Redirect BAT indices pointing to a deleted index to point to // the location where the deleted index original points to. size_t maxBATindices = blocksIndices_.size(); {for (size_t i=0; i()); {for (size_t i=0; i indices[j] && blocksIndices_[i] != -2 && blocksIndices_[i] != -3) ++count; } blocksIndices_[i] -= count; }} // Change properties start block size_t count = 0; {for (size_t i=0; i indices[i] && header_.propertiesStart_ != -2) ++count; }} header_.propertiesStart_ -= count; size_t maxProperties = properties_.size(); // Change Root Entry start block if (!properties_.empty()) { size_t count = 0; for (size_t j=0; jstartBlock_ > indices[j] && properties_[0]->startBlock_ != -2) ++count; } properties_[0]->startBlock_ -= count; } {for (size_t i=1; isize_ >= 4096) { // Change individual properties start block if their size is more than 4096 size_t count = 0; for (size_t j=0; jstartBlock_ > indices[j] && properties_[i]->startBlock_ != -2) ++count; } properties_[i]->startBlock_ -= count; } }} } void CompoundFile::SplitPath(const wchar_t* path, wchar_t*& parentpath, wchar_t*& propertyname) // PURPOSE: Get a path's parent path and its name. // EXPLAIN: E.g. path = "\\Abc\\def\\ghi => parentpath = "\\Abc\\def", propertyname = "ghi". // REQUIRE: Calling function is responsible for deleting the memory created for // REQUIRE: parentpath and propertyname. { size_t pathLength = wcslen(path); int npos; for (npos=pathLength-1; npos>0; --npos) { if (path[npos] == L'\\') break; } if (npos != 0) { // Get parent path if available parentpath = new wchar_t[npos+1]; copy (path, path+npos, parentpath); parentpath[npos] = 0; ++npos; } // Get property name (ignore initial "\" if present) if (npos==0 && pathLength > 0 && path[0] == L'\\') ++npos; propertyname = new wchar_t[pathLength-npos+1]; copy (path+npos, path+pathLength, propertyname); propertyname[pathLength-npos] = 0; } /*********************** Inaccessible Header Functions ***************************/ bool CompoundFile::LoadHeader() // PURPOSE: Load header information for compound file. // PROMISE: Return true if file header contain magic number, false if otherwise. { file_.Read(0, &*(block_.begin())); header_.Read(&*(block_.begin())); // Check magic number to see if it is a compound file if (header_.fileType_ != LONGINT_CONST(0xE11AB1A1E011CFD0)) return false; block_.resize(header_.bigBlockSize_); // Resize buffer block file_.SetBlockSize(header_.bigBlockSize_); // Resize block array block size return true; } void CompoundFile::SaveHeader() // PURPOSE: Save header information for compound file. { header_.Write(&*(block_.begin())); file_.Write(0, &*(block_.begin())); } /*********************** Inaccessible BAT Functions ***************************/ void CompoundFile::LoadBAT() // PURPOSE: Load all block allocation table information for compound file. { // Read BAT indices {for (size_t i=0; i indices; if (isBig) { GetBlockIndices(startIndex, indices, true); return indices.size()*header_.bigBlockSize_; } else { GetBlockIndices(startIndex, indices, false); return indices.size()*header_.smallBlockSize_; } } size_t CompoundFile::ReadData(size_t startIndex, char* data, bool isBig) // PURPOSE: Read a property's data, starting from startIndex. // REQUIRE: data must be large enough to receive the property's data // REQUIRE: The required data size can be obtained by using DataSize(). // EXPLAIN: isBig is true if property uses big blocks, false if it uses small blocks. // PROMISE: Returns the total size occupied by the property which is the total // PROMISE: number of blocks occupied multiply by the block size. { vector indices; if (isBig) { GetBlockIndices(startIndex, indices, true); size_t maxIndices = indices.size(); for (size_t i=0; istartBlock_, true)]; ReadData(properties_[0]->startBlock_, buffer, true); size_t maxIndices = indices.size(); for (size_t i=0; i indices; GetBlockIndices(startIndex, indices, true); size_t maxPresentBlocks = indices.size(); // Calculate how many blocks does the data need size_t extraSize = size % header_.bigBlockSize_; size_t maxNewBlocks = size / header_.bigBlockSize_ + (extraSize ? 1 : 0); // Readjust indices and remove blocks if new data size is smaller than original int extraBlocks = maxPresentBlocks - maxNewBlocks; if (extraBlocks > 0) { // Place new end marker if (maxNewBlocks != 0) blocksIndices_[indices[maxNewBlocks]-1] = -2; else startIndex = -2; // Get indices of blocks to delete vector indicesToRemove(extraBlocks); copy (indices.begin()+maxNewBlocks, indices.end(), indicesToRemove.begin()); indices.erase(indices.begin()+maxNewBlocks, indices.end()); // Remove extra blocks and readjust indices FreeBlocks(indicesToRemove, true); } // Write blocks into available space size_t remainingFullBlocks = size / header_.bigBlockSize_; size_t curIndex=0; if (maxPresentBlocks != 0) { for (; remainingFullBlocks && curIndex= maxPresentBlocks) { // No more free blocks to write extra block data newIndex = GetFreeBlockIndex(true); // Get new free block to write data if (startIndex == -2) startIndex = newIndex; else LinkBlocks(index, newIndex,true); } else newIndex = indices[curIndex]; // Write extra block after increasing its size to the minimum block size vector tempdata(header_.bigBlockSize_, 0); copy (data+curIndex*header_.bigBlockSize_, data+curIndex*header_.bigBlockSize_+extraSize, tempdata.begin()); file_.Write(newIndex+1, &*(tempdata.begin())); } return startIndex; } else { if (size==0 && startIndex==-2) return startIndex; if (size != 0 && properties_[0]->startBlock_ == -2) { size_t newIndex = GetFreeBlockIndex(true); fill (block_.begin(), block_.end(), 0); file_.Insert(newIndex, &*(block_.begin())); IncreaseLocationReferences(vector(1, newIndex)); properties_[0]->startBlock_ = newIndex; properties_[0]->size_ = header_.bigBlockSize_; } // Get present indices vector indices; GetBlockIndices(startIndex, indices, false); size_t maxPresentBlocks = indices.size(); // Calculate how many blocks does the data need size_t extraSize = size % header_.smallBlockSize_; size_t maxNewBlocks = size / header_.smallBlockSize_ + (extraSize ? 1 : 0); vector smallBlocksData; int extraBlocks = maxPresentBlocks - maxNewBlocks; if (extraBlocks > 0) { // Readjust indices and remove blocks // Place new end marker if (maxNewBlocks != 0) sblocksIndices_[indices[maxNewBlocks]-1] = -2; else startIndex = -2; // Get indices of blocks to delete vector indicesToRemove(extraBlocks); copy (indices.begin()+maxNewBlocks, indices.end(), indicesToRemove.begin()); indices.erase(indices.begin()+maxNewBlocks, indices.end()); // Remove extra blocks and readjust indices FreeBlocks(indicesToRemove, false); } else if (extraBlocks < 0) { size_t maxBlocks = properties_[0]->size_ / header_.bigBlockSize_ + (properties_[0]->size_ % header_.bigBlockSize_ ? 1 : 0); size_t actualSize = maxBlocks * header_.bigBlockSize_; smallBlocksData.resize(actualSize); ReadData(properties_[0]->startBlock_, &*(smallBlocksData.begin()), true); smallBlocksData.resize(properties_[0]->size_); // Readjust indices and add blocks size_t newBlocksNeeded = -extraBlocks; size_t index = maxPresentBlocks - 1; for (size_t i=0; isize_ = newBlocksNeeded * header_.smallBlockSize_; } if (smallBlocksData.empty()) { size_t maxBlocks = properties_[0]->size_ / header_.bigBlockSize_ + (properties_[0]->size_ % header_.bigBlockSize_ ? 1 : 0); size_t actualSize = maxBlocks * header_.bigBlockSize_; smallBlocksData.resize(actualSize); ReadData(properties_[0]->startBlock_, &*(smallBlocksData.begin()), true); smallBlocksData.resize(properties_[0]->size_); } // Write blocks GetBlockIndices(startIndex, indices, false); size_t fullBlocks = size / header_.smallBlockSize_; for (size_t i=0; isize_, properties_[0]->startBlock_, true); return startIndex; } } void CompoundFile::GetBlockIndices(size_t startIndex, vector& indices, bool isBig) // PURPOSE: Get the indices of blocks where data are stored, starting from startIndex. // EXPLAIN: isBig is true if property uses big blocks, false if it uses small blocks. { indices.clear(); if (isBig) { for (size_t i=startIndex; i!=-2; i=blocksIndices_[i]) indices.push_back(i); } else { for (size_t i=startIndex; i!=-2; i=sblocksIndices_[i]) indices.push_back(i); } } size_t CompoundFile::GetFreeBlockIndex(bool isBig) // PURPOSE: Get the index of a new block where data can be stored. // EXPLAIN: isBig is true if property uses big blocks, false if it uses small blocks. // PROMISE: It does not physically create a new block in the compound file. // PROMISE: It only adjust BAT arrays and indices or SBAT arrays and indices so that // PROMISE: it gives the index of a new block where data can be inserted. { size_t index; if (isBig) { // Find first free location index = distance(blocksIndices_.begin(), find(blocksIndices_.begin(), blocksIndices_.end(), -1)); if (index == blocksIndices_.size()) { ExpandBATArray(true); index = distance(blocksIndices_.begin(), find(blocksIndices_.begin(), blocksIndices_.end(), -1)); } blocksIndices_[index] = -2; } else { // Find first free location index = distance(sblocksIndices_.begin(), find(sblocksIndices_.begin(), sblocksIndices_.end(), -1)); if (index == sblocksIndices_.size()) { ExpandBATArray(false); index = distance(sblocksIndices_.begin(), find(sblocksIndices_.begin(), sblocksIndices_.end(), -1)); } sblocksIndices_[index] = -2; } return index; } void CompoundFile::ExpandBATArray(bool isBig) // PURPOSE: Create a new block of BAT or SBAT indices. // EXPLAIN: isBig is true if property uses big blocks, false if it uses small blocks. { size_t newIndex; fill (block_.begin(), block_.end(), -1); if (isBig) { size_t BATindex = distance(&header_.BATArray_[0], find(header_.BATArray_, header_.BATArray_+109, -1)); if (BATindex < 109) { // Set new BAT index location newIndex = blocksIndices_.size(); // New index location file_.Insert(newIndex+1, &*(block_.begin())); IncreaseLocationReferences(vector(1, newIndex)); // Update BAT array header_.BATArray_[BATindex] = newIndex; ++header_.BATCount_; } else { // No free BAT indices. Increment using XBAT // Set new XBAT index location if (header_.XBATCount_ != 0) { newIndex = header_.XBATStart_ + header_.XBATCount_; file_.Insert(newIndex, &*(block_.begin())); IncreaseLocationReferences(vector(1, newIndex)); } else { newIndex = blocksIndices_.size(); file_.Insert(newIndex, &*(block_.begin())); IncreaseLocationReferences(vector(1, newIndex)); header_.XBATStart_ = newIndex; } ++header_.XBATCount_; } blocksIndices_.insert(blocksIndices_.begin()+newIndex, -3); blocksIndices_.resize(blocksIndices_.size()+127, -1); } else { // Set new SBAT index location if (header_.SBATCount_ != 0) { newIndex = header_.SBATStart_ + header_.SBATCount_; file_.Insert(newIndex, &*(block_.begin())); IncreaseLocationReferences(vector(1, newIndex)); } else { newIndex = GetFreeBlockIndex(true); file_.Insert(newIndex, &*(block_.begin())); IncreaseLocationReferences(vector(1, newIndex)); header_.SBATStart_ = newIndex; } ++header_.SBATCount_; sblocksIndices_.resize(sblocksIndices_.size()+128, -1); } } void CompoundFile::LinkBlocks(size_t from, size_t to, bool isBig) // PURPOSE: Link one BAT index to another. // EXPLAIN: isBig is true if property uses big blocks, false if it uses small blocks. { if (isBig) blocksIndices_[from] = to; else sblocksIndices_[from] = to; } void CompoundFile::FreeBlocks(vector& indices, bool isBig) // PURPOSE: Delete blocks of data from compound file. // EXPLAIN: indices contains indices to blocks of data to be deleted. // EXPLAIN: isBig is true if property uses big blocks, false if it uses small blocks. { if (isBig) { // Decrease all location references before deleting blocks from file. DecreaseLocationReferences(indices); size_t maxIndices = indices.size(); {for (size_t i=0; i indicesToRemove; while (distance(find(blocksIndices_.begin(), blocksIndices_.end(),-1), blocksIndices_.end()) >= 128) { blocksIndices_.resize(blocksIndices_.size()-128); if (header_.XBATCount_ != 0) { // Shrink XBAT first --header_.XBATCount_; indicesToRemove.push_back(header_.XBATStart_+header_.XBATCount_+1); // Add 1 because Block index 1 corresponds to index 0 here if (header_.XBATCount_ == 0) header_.XBATStart_ = -2; } else { // No XBAT, delete last occupied BAT array element size_t BATindex = distance(&header_.BATArray_[0], find(header_.BATArray_, header_.BATArray_+109, -1)); if (BATindex != 109) { --header_.BATCount_; indicesToRemove.push_back(header_.BATArray_[BATindex-1]+1); // Add 1 because Block index 1 corresponds to index 0 here header_.BATArray_[BATindex-1] = -1; } } } // Erase extra BAT indices if present if (!indicesToRemove.empty()) file_.Erase(indicesToRemove); } else { // Erase block size_t maxIndices = indices.size(); size_t maxBlocks = properties_[0]->size_ / header_.bigBlockSize_ + (properties_[0]->size_ % header_.bigBlockSize_ ? 1 : 0); size_t size = maxBlocks * header_.bigBlockSize_; char* data = new char[size]; ReadData(properties_[0]->startBlock_, data, true); size_t maxSmallBlocks = properties_[0]->size_ / header_.smallBlockSize_; char* newdata = new char[properties_[0]->size_-maxIndices*header_.smallBlockSize_]; {for (size_t i=0, j=0; istartBlock_ = WriteData(newdata, properties_[0]->size_-maxIndices*header_.smallBlockSize_, properties_[0]->startBlock_, true); properties_[0]->size_ -= maxIndices*header_.smallBlockSize_; delete[] data; delete[] newdata; // Change SBAT indices size_t maxSBATindices = sblocksIndices_.size(); {for (size_t i=0; i indices[i] && sblocksIndices_[j] != -1 && sblocksIndices_[j] != -2) --sblocksIndices_[j]; } }} sort (indices.begin(), indices.end(), greater()); {for (size_t i=0; i indicesToRemove; while (distance(find(sblocksIndices_.begin(), sblocksIndices_.end(),-1), sblocksIndices_.end()) >= 128) { // Shrink SBAT indices if necessary sblocksIndices_.resize(sblocksIndices_.size()-128); --header_.SBATCount_; indicesToRemove.push_back(header_.SBATStart_+header_.SBATCount_); if (header_.SBATCount_ == 0) header_.SBATStart_ = -2; } FreeBlocks(indicesToRemove, true); } } /*********************** Inaccessible Properties Functions ***************************/ void CompoundFile::LoadProperties() // PURPOSE: Load properties information for compound file. { // Read properties' data from compound file. size_t propertiesSize = DataSize(header_.propertiesStart_, true); char* buffer = new char[propertiesSize]; ReadData(header_.propertiesStart_, buffer, true); // Split properties' data into individual property. size_t maxPropertiesBlock = propertiesSize / header_.bigBlockSize_; size_t propertiesPerBlock = header_.bigBlockSize_ / 128; size_t maxProperties = maxPropertiesBlock * propertiesPerBlock; size_t maxBlocks = maxProperties / propertiesPerBlock + (maxProperties % propertiesPerBlock ? 1 : 0); for (size_t i=0; iRead(buffer+i*512+j*128); if (wcslen(property->name_) == 0) { delete property; break; } properties_.push_back(property); } } delete[] buffer; // Generate property trees propertyTrees_->parent_ = 0; propertyTrees_->self_ = properties_[0]; propertyTrees_->index_ = 0; InsertPropertyTree(propertyTrees_, properties_[properties_[0]->childProp_], properties_[0]->childProp_); } void CompoundFile::SaveProperties() // PURPOSE: Save properties information for compound file. { // Calculate total size required by properties size_t maxProperties = properties_.size(); size_t propertiesPerBlock = header_.bigBlockSize_ / 128; size_t maxBlocks = maxProperties / propertiesPerBlock + (maxProperties % propertiesPerBlock ? 1 : 0); size_t propertiesSize = maxBlocks*header_.bigBlockSize_; char* buffer = new char[propertiesSize]; {for (size_t i=0; iWrite(buffer+i*128); }} // Write properties' data to compound file. WriteData(buffer, propertiesSize, header_.propertiesStart_, true); delete[] buffer; } int CompoundFile::MakeProperty(const wchar_t* path, CompoundFile::Property* property) // PURPOSE: Create a new property in the compound file. // EXPLAIN: path is the full path name for the property. // EXPLAIN: property contains information on the type of property to be created. { wchar_t* parentpath = 0; wchar_t* propertyname = 0; // Change to the specified directory. If specified directory is not present, // create it. if (wcslen(path) != 0) { if (path[0] == L'\\') currentDirectory_ = propertyTrees_; } SplitPath(path, parentpath, propertyname); if (propertyname != 0) { if (parentpath != 0) { if (ChangeDirectory(parentpath) != SUCCESS) { int ret = MakeDirectory(parentpath); if (ret != SUCCESS) { delete[] parentpath; delete[] propertyname; return ret; } else ChangeDirectory(parentpath); } delete[] parentpath; } // Insert property into specified directory size_t propertynameLength = wcslen(propertyname); if (propertynameLength >= 32) { delete[] propertyname; return NAME_TOO_LONG; } wcscpy(property->name_, propertyname); delete[] propertyname; property->nameSize_ = propertynameLength*2+2; if (FindProperty(currentDirectory_, property->name_) == 0) { // Find location to insert property size_t maxProperties = properties_.size(); size_t index; for (index=1; index *property) break; } if (index != maxProperties) { // Change references for all properties affected by the new property IncreasePropertyReferences(propertyTrees_, index); } properties_.insert(properties_.begin()+index, property); InsertPropertyTree(currentDirectory_, property, index); return SUCCESS; } else return DUPLICATE_PROPERTY; } else { if (parentpath != 0) delete[] parentpath; return INVALID_PATH; } } CompoundFile::PropertyTree* CompoundFile::FindProperty(size_t index) // PURPOSE: Find property in the compound file, given the index of the property. // PROMISE: Returns a pointer to the property tree of the property if property // PROMISE: is present, 0 if otherwise. { if (previousDirectories_.empty()) previousDirectories_.push_back(propertyTrees_); PropertyTree* currentTree = previousDirectories_.back(); if (currentTree->index_ != index) { size_t maxChildren = currentTree->children_.size(); for (size_t i=0; ichildren_[i]); PropertyTree* child = FindProperty(index); if (child != 0) { previousDirectories_.pop_back(); return child; } } } else { previousDirectories_.pop_back(); return currentTree; } previousDirectories_.pop_back(); return 0; } CompoundFile::PropertyTree* CompoundFile::FindProperty(const wchar_t* path) // PURPOSE: Find property in the compound file, given the path of the property. // PROMISE: Returns a pointer to the property tree of the property if property // PROMISE: is present, 0 if otherwise. { previousDirectories_.push_back(currentDirectory_); // Change to specified directory wchar_t* parentpath = 0; wchar_t* filename = 0; if (wcslen(path) != 0) { if (path[0] == L'\\') currentDirectory_ = propertyTrees_; } SplitPath(path, parentpath, filename); if (parentpath != 0) { int ret = ChangeDirectory(parentpath); delete[] parentpath; if (ret != SUCCESS) { // Cannot change to specified directory if (filename != 0) delete[] filename; currentDirectory_ = previousDirectories_.back(); previousDirectories_.pop_back(); PropertyTree* property = 0; return property; } } // Check to see if file is present in the specified directory. PropertyTree* property = 0; if (filename != 0) { property = FindProperty(currentDirectory_, filename); delete[] filename; } currentDirectory_ = previousDirectories_.back(); previousDirectories_.pop_back(); return property; } CompoundFile::PropertyTree* CompoundFile::FindProperty(CompoundFile::PropertyTree* parentTree, wchar_t* name) // PURPOSE: Find property in the compound file, given the parent property tree and its name. // PROMISE: Returns a pointer to the property tree of the property if property // PROMISE: is present, 0 if otherwise. { if (parentTree->self_->childProp_ != -1) { size_t maxChildren = parentTree->children_.size(); for (size_t i=0; ichildren_[i]->self_->name_, name) == 0) { return parentTree->children_[i]; } } } return 0; } void CompoundFile::InsertPropertyTree(CompoundFile::PropertyTree* parentTree, CompoundFile::Property* property, size_t index) // PURPOSE: Insert a property and all its siblings and children into the property tree. // REQUIRE: If the property is a new property and its index is already occupied by // REQUIRE: another property, the calling function has to call IncreasePropertyReferences() // REQUIRE: first before calling this function. // EXPLAIN: This function is used by LoadProperty() to initialize the property trees // EXPLAIN: and MakeProperty() thus resulting in the above requirements. // EXPLAIN: parentTree is the parent of the new property. // EXPLAIN: property is the property to be added. // EXPLAIN: index is the index of the new property. // PROMISE: The property will be added as the parent tree's child and the parent's // PROMISE: child property and all the its children previous property and next property // PROMISE: will be readjusted to accomodate the next property. { PropertyTree* tree = new PropertyTree; tree->parent_ = parentTree; tree->self_ = property; tree->index_ = index; if (property->previousProp_ != -1) { InsertPropertyTree(parentTree, properties_[property->previousProp_], property->previousProp_); } if (property->nextProp_ != -1) { InsertPropertyTree(parentTree, properties_[property->nextProp_], property->nextProp_); } if (property->childProp_ != -1) { InsertPropertyTree(tree, properties_[property->childProp_], property->childProp_); } // Sort children size_t maxChildren = parentTree->children_.size(); size_t i; for (i=0; ichildren_[i]->index_) break; } parentTree->children_.insert(parentTree->children_.begin()+i, tree); // Update children indices UpdateChildrenIndices(parentTree); } void CompoundFile::DeletePropertyTree(CompoundFile::PropertyTree* tree) // PURPOSE: Delete a property from properties. // EXPLAIN: tree is the property tree to be deleted. // PROMISE: The tree's parent's child property and all the its children previous property // PROMISE: and next property will be readjusted to accomodate the deleted property. { // Decrease all property references DecreasePropertyReferences(propertyTrees_, tree->index_); // Remove property if (properties_[tree->index_]) delete properties_[tree->index_]; properties_.erase(properties_.begin()+tree->index_); // Remove property from property trees size_t maxChildren = tree->parent_->children_.size(); size_t i; for (i=0; iparent_->children_[i]->index_ == tree->index_) break; } tree->parent_->children_.erase(tree->parent_->children_.begin()+i); // Update children indices UpdateChildrenIndices(tree->parent_); } void CompoundFile::UpdateChildrenIndices(CompoundFile::PropertyTree* parentTree) { // Update indices for 1st to middle child size_t maxChildren = parentTree->children_.size(); if (maxChildren != 0) { vector& children = parentTree->children_; size_t prevChild = 0; children[0]->self_->previousProp_ = -1; children[0]->self_->nextProp_ = -1; size_t curChild; for (curChild=1; curChild<=maxChildren/2; ++curChild) { children[curChild]->self_->previousProp_ = children[prevChild]->index_; children[curChild]->self_->nextProp_ = -1; prevChild = curChild; } // Update middle child --curChild; children[curChild]->parent_->self_->childProp_ = children[curChild]->index_; // Update from middle to last child size_t nextChild = curChild + 1; if (nextChild < maxChildren) { children[curChild]->self_->nextProp_ = children[nextChild]->index_; for (++curChild, ++nextChild; nextChildself_->previousProp_ = -1; children[curChild]->self_->nextProp_ = children[nextChild]->index_; } children[curChild]->self_->previousProp_ = -1; children[curChild]->self_->nextProp_ = -1; } } else { parentTree->self_->childProp_ = -1; } } void CompoundFile::IncreasePropertyReferences(CompoundFile::PropertyTree* parentTree, size_t index) // PURPOSE: Increase all property references (previous property, next property // PURPOSE: and child property) which will be affected by the insertion of the new index. // EXPLAIN: The recursive method of going through each property tree is used instead of // EXPLAIN: using the iterative method of going through each property in properties_ is // EXPLAIN: because the index in property tree needs to be updated also. { if (parentTree->index_ >= index) ++parentTree->index_; if (parentTree->self_->previousProp_ != -1) { if (parentTree->self_->previousProp_ >= index) { ++parentTree->self_->previousProp_; } } if (parentTree->self_->nextProp_!= -1) { if (parentTree->self_->nextProp_ >= index) { ++parentTree->self_->nextProp_; } } if (parentTree->self_->childProp_ != -1) { if (parentTree->self_->childProp_ >= index) { ++parentTree->self_->childProp_; } } size_t maxChildren = parentTree->children_.size(); for (size_t i=0; ichildren_[i], index); } } void CompoundFile::DecreasePropertyReferences(CompoundFile::PropertyTree* parentTree, size_t index) // PURPOSE: Decrease all property references (previous property, next property // PURPOSE: and child property) which will be affected by the deletion of the index. // EXPLAIN: The recursive method of going through each property tree is used instead of // EXPLAIN: using the iterative method of going through each property in properties_ is // EXPLAIN: because the index in property tree needs to be updated also. { if (parentTree->index_ > index) --parentTree->index_; if (parentTree->self_->previousProp_ != -1) { if (parentTree->self_->previousProp_ > index) { --parentTree->self_->previousProp_; } } if (parentTree->self_->nextProp_!= -1) { if (parentTree->self_->nextProp_ > index) { --parentTree->self_->nextProp_; } } if (parentTree->self_->childProp_ != -1) { if (parentTree->self_->childProp_ > index) { --parentTree->self_->childProp_; } } size_t maxChildren = parentTree->children_.size(); for (size_t i=0; ichildren_[i], index); } } } // YCompoundFiles namespace end namespace YExcel { using namespace YCompoundFiles; /************************************************************************************************************/ Record::Record() : dataSize_(0), recordSize_(4) {} Record::~Record() {} size_t Record::Read(const char* data) { LittleEndian::Read(data, code_, 0, 2); // Read operation code. LittleEndian::Read(data, dataSize_, 2, 2); // Read size of record. data_.assign(data+4, data+4+dataSize_); recordSize_ = 4 + dataSize_; // Check if next record is a continue record continueIndices_.clear(); short code; LittleEndian::Read(data, code, dataSize_+4, 2); while (code == CODE::CONTINUE) { continueIndices_.push_back(dataSize_); size_t size; LittleEndian::Read(data, size, recordSize_+2, 2); data_.insert(data_.end(), data+recordSize_+4, data+recordSize_+4+size); dataSize_ += size; recordSize_ += 4 + size; LittleEndian::Read(data, code, recordSize_, 2); }; return recordSize_; } size_t Record::Write(char* data) { LittleEndian::Write(data, code_, 0, 2); // Write operation code. size_t npos = 2; if (continueIndices_.empty()) { size_t size = dataSize_; size_t i=0; while (size > 8224) { LittleEndian::Write(data, 8224, npos, 2); // Write size of record. npos += 2; size -= 8224; copy (data_.begin()+i*8224, data_.begin()+(i+1)*8224, data+npos); npos += 8224; if (size != 0) { ++i; LittleEndian::Write(data, 0x3C, npos, 2); // Write CONTINUE code. npos += 2; } } LittleEndian::Write(data, size, npos, 2); // Write size of record. npos += 2; copy (data_.begin()+i*8224, data_.begin()+i*8224+size, data+npos); npos += size; } else { size_t maxContinue = continueIndices_.size(); size_t size = continueIndices_[0]; LittleEndian::Write(data, size, npos, 2); // Write size of record npos += 2; copy (data_.begin(), data_.begin()+size, data+npos); npos += size; size_t c=0; for (c=1; c0) bytesRead += ContinueRead(data+2, stringSize); else bytesRead = 3; return bytesRead; } size_t LargeString::ContinueRead(const char* data, size_t size) { if (size == 0) return 0; char unicode; LittleEndian::Read(data, unicode, 0, 1); if (unicode_ == -1) unicode_ = unicode; if (unicode_ & 1) { // Present stored string is uncompressed (16 bit) size_t npos = 1; if (richtext_) npos += 2; if (phonetic_) npos += 4; size_t strpos = wname_.size(); wname_.resize(strpos+size, 0); if (unicode & 1) { LittleEndian::ReadString(data, &*(wname_.begin())+strpos, npos, size); npos += size * SIZEOFWCHAR_T; } else { // String to be read is in ANSI vector name(size); LittleEndian::ReadString(data, &*(name.begin()), npos, size); mbstowcs(&*(wname_.begin())+strpos, &*(name.begin()), size); npos += size; } if (richtext_) npos += 4*richtext_; if (phonetic_) npos += phonetic_; return npos; } else { // Present stored string has character compression (8 bit) size_t npos = 1; if (richtext_) npos += 2; if (phonetic_) npos += 4; size_t strpos = name_.size(); name_.resize(strpos+size, 0); if (unicode & 1) { // String to be read is in unicode vector name(size); LittleEndian::ReadString(data, &*(name.begin()), npos, size); wcstombs(&*(name_.begin())+strpos, &*(name.begin()), size); npos += size * SIZEOFWCHAR_T; } else { LittleEndian::ReadString(data, &*(name_.begin())+strpos, npos, size); npos += size; } if (richtext_) npos += 4*richtext_; if (phonetic_) npos += phonetic_; return npos; } } size_t LargeString::Write(char* data) { size_t stringSize = 0; size_t bytesWrite = 0; if (unicode_ & 1) { // UNICODE unicode_ = 1; // Don't handle richtext or phonetic for now. if (!wname_.empty()) { stringSize = wname_.size(); LittleEndian::Write(data, stringSize, 0, 2); LittleEndian::Write(data, unicode_, 2, 1); LittleEndian::WriteString(data, &*(wname_.begin()), 3, stringSize); bytesWrite = 3 + stringSize * SIZEOFWCHAR_T; } else { LittleEndian::Write(data, stringSize, 0, 2); LittleEndian::Write(data, unicode_, 2, 1); bytesWrite = 3; } } else { // ANSI string unicode_ = 0; // Don't handle richtext or phonetic for now. if (!name_.empty()) { stringSize = name_.size(); LittleEndian::Write(data, stringSize, 0, 2); LittleEndian::Write(data, unicode_, 2, 1); LittleEndian::WriteString(data, &*(name_.begin()), 3, stringSize); bytesWrite = 3 + stringSize; } else { LittleEndian::Write(data, stringSize, 0, 2); LittleEndian::Write(data, unicode_, 2, 1); bytesWrite = 3; } } return bytesWrite; } size_t LargeString::DataSize() { size_t dataSize = StringSize() + 3; if (richtext_) dataSize += 2 + 4*richtext_; if (phonetic_) dataSize += 4 + phonetic_; return dataSize; } size_t LargeString::RecordSize() {return DataSize();} size_t LargeString::StringSize() { if (unicode_ & 1) return wname_.size() * SIZEOFWCHAR_T; else return name_.size(); } /************************************************************************************************************/ /************************************************************************************************************/ Workbook::Workbook() { bof_.version_ = 1536; bof_.type_ = 5; bof_.buildIdentifier_ = 6560; bof_.buildYear_ = 1997; bof_.fileHistoryFlags_ = 49353; bof_.lowestExcelVersion_ = 774; } size_t Workbook::Read(const char* data) { size_t bytesRead = 0; short code; LittleEndian::Read(data, code, 0, 2); while (code != CODE::YEOF) { switch (code) { case CODE::BOF: bytesRead += bof_.Read(data+bytesRead); break; case CODE::WINDOW1: bytesRead += window1_.Read(data+bytesRead); break; case CODE::FONT: fonts_.push_back(Font()); bytesRead += fonts_.back().Read(data+bytesRead); break; //MF case CODE::FORMAT: formats_.push_back(Format()); bytesRead += formats_.back().Read(data+bytesRead); break; case CODE::XF: XFs_.push_back(XF()); bytesRead += XFs_.back().Read(data+bytesRead); break; case CODE::STYLE: styles_.push_back(Style()); bytesRead += styles_.back().Read(data+bytesRead); break; case CODE::BOUNDSHEET: boundSheets_.push_back(BoundSheet()); bytesRead += boundSheets_.back().Read(data+bytesRead); break; case CODE::SST: bytesRead += sst_.Read(data+bytesRead); break; // case CODE::EXTSST: // bytesRead += extSST_.Read(data+bytesRead); // break; default: { Record rec; bytesRead += rec.Read(data+bytesRead); } } LittleEndian::Read(data, code, bytesRead, 2); } bytesRead += eof_.RecordSize(); return bytesRead; } size_t Workbook::Write(char* data) { size_t bytesWritten = 0; bytesWritten += bof_.Write(data+bytesWritten); bytesWritten += window1_.Write(data+bytesWritten); size_t maxFonts = fonts_.size(); {for (size_t i=0; i= FIRST_USER_FORMAT_IDX) // only write user defined formats bytesWritten += formats_[i].Write(data+bytesWritten); }} size_t maxXFs = XFs_.size(); {for (size_t i=0; i= FIRST_USER_FORMAT_IDX) // only write user defined formats size += formats_[i].RecordSize(); }} size_t maxXFs = XFs_.size(); {for (size_t i=0; i= maxContinue || npos+stringSize*multiplier+3 <= continueIndices_[c]) { // String to be read is not split into two records npos += strings_[i].Read(&*(data_.begin())+npos); } else { // String to be read is split into two or more records int bytesRead = 2;// Start from unicode field int size = continueIndices_[c] - npos - 1 - bytesRead; ++c; if (size > 0) { size /= multiplier; // Number of characters available for string in current record. bytesRead += strings_[i].ContinueRead(&*(data_.begin())+npos+bytesRead, size); stringSize -= size; size = 0; } while (ccontinueIndices_[c]) { size_t dataSize = (continueIndices_[c] - continueIndices_[c-1] - 1) / multiplier; bytesRead += strings_[i].ContinueRead(&*(data_.begin())+npos+bytesRead, dataSize); stringSize -= dataSize + 1; ++c; }; if (stringSize>0) { bytesRead += strings_[i].ContinueRead(&*(data_.begin())+npos+bytesRead, stringSize); } npos += bytesRead; } } } return npos + 4*(npos/8224 + 1); } size_t Workbook::SharedStringTable::Write(char* data) { data_.resize(DataSize()); LittleEndian::Write(data_, stringsTotal_, 0, 4); LittleEndian::Write(data_, uniqueStringsTotal_, 4, 4); size_t maxContinue = continueIndices_.size(); for (size_t i=0, c=0, npos=8; i continueIndices_[c]) { // Insert unicode flag where appropriate for CONTINUE records. data_.insert(data_.begin()+continueIndices_[c], strings_[i].unicode_); data_.pop_back(); ++c; ++npos; } } return Record::Write(data); } size_t Workbook::SharedStringTable::DataSize() { dataSize_ = 8; continueIndices_.clear(); size_t curMax = 8224; for (size_t i=0; i= 12 bytes (2 for size, 1 for unicode and >=9 for data, can split string // otherwise, end record and start continue record. bool unicode = strings_[i].unicode_ & 1; if (curMax - dataSize_ >= 12) { if (unicode && !((curMax-dataSize_)%2)) --curMax; // Make sure space reserved for unicode strings is even. continueIndices_.push_back(curMax); stringSize -= (curMax - dataSize_ - 3); dataSize_ = curMax; curMax += 8224; size_t additionalContinueRecords = unicode ? stringSize/8222 : stringSize/8223; // 8222 or 8223 because the first byte is for unicode identifier for (size_t j=0; j= 12 bytes (2 for size, 1 for unicode and >=9 for data, can split string // otherwise, end record and start continue record. if (curMax - dataSize_ >= 12) { if (unicode && !((curMax-dataSize_)%2)) --curMax; // Make sure space reserved for unicode strings is even. continueIndices_.push_back(curMax); stringSize -= (curMax - dataSize_ - 3); dataSize_ = curMax; curMax += 8224; size_t additionalContinueRecords = unicode ? stringSize/8222 : stringSize/8223; // 8222 or 8223 because the first byte is for unicode identifier for (size_t j=0; j16) { for (size_t i=0; i>= 2; intdouble.intvalue_ = rkValue; intdouble.intvalue_ <<= 34; if (isMultiplied) intdouble.doublevalue_ *= 0.01; return intdouble.doublevalue_; } // Convert a rk value to an integer. int GetIntegerFromRKValue(int rkValue) { bool isMultiplied = rkValue & 1; rkValue >>= 2; if (isMultiplied) rkValue /= 100; return rkValue; } // Convert a double to a rk value. int GetRKValueFromDouble(double value) { union { LONGINT intvalue_; double doublevalue_; } intdouble; bool isMultiplied = false; int testVal1 = (int)value * 100; int testVal2 = (int)(value * 100); if (testVal1 != testVal2) { isMultiplied = true; value *= 100; } intdouble.doublevalue_ = value; intdouble.intvalue_ >>= 34; int rkValue = (int)intdouble.intvalue_; rkValue <<= 2; rkValue |= (isMultiplied? 1: 0); return rkValue; } // Convert an integer to a rk value. int GetRKValueFromInteger(int value) { value <<= 2; value |= 2; return value; } // Returns true if the supplied double can be stored as a rk value. bool CanStoreAsRKValue(double value) { int testVal1 = 100 * (int)(value * 100); int testVal2 = int(value * 10000); if (testVal1 != testVal2) return false; else return true; } /************************************************************************************************************/ /************************************************************************************************************/ BasicExcel::BasicExcel() {} BasicExcel::BasicExcel(const char* filename) { Load(filename); } BasicExcel::~BasicExcel() { Close(); } void BasicExcel::Close() { if (file_.IsOpen()) file_.Close(); } // Create a new Excel workbook with a given number of spreadsheets (Minimum 1) void BasicExcel::New(int sheets) { workbook_ = Workbook(); worksheets_.clear(); workbook_.fonts_.resize(1); //MF was 4, see XLSFormatManager::get_font_idx() workbook_.XFs_.resize(21); workbook_.styles_.resize(6); workbook_.boundSheets_.resize(1); worksheets_.resize(1); UpdateYExcelWorksheet(); for (int i=0; i data; file_.ReadFile("Workbook", data); Read(&*(data.begin()), data.size()); UpdateYExcelWorksheet(); return true; } else return false; } // Save current Excel workbook to opened file. bool BasicExcel::Save() { if (file_.IsOpen()) { // Prepare Raw Worksheets for saving. UpdateWorksheets(); AdjustStreamPositions(); // Calculate bytes needed for a workbook. size_t minBytes = workbook_.RecordSize(); size_t maxWorkSheets = worksheets_.size(); for (size_t i=0; i data(minBytes,0); Write(&*(data).begin()); if (file_.WriteFile("Workbook", data, data.size())!=CompoundFile::SUCCESS) return false; return true; } else return false; } // Save current Excel workbook to a file. bool BasicExcel::SaveAs(const char* filename) { if (file_.IsOpen()) file_.Close(); if (!file_.Create(filename)) return false; if (file_.MakeFile("Workbook")!=CompoundFile::SUCCESS) return false; return Save(); } // Total number of Excel worksheets in current Excel workbook. size_t BasicExcel::GetTotalWorkSheets() { return worksheets_.size(); } // Get a pointer to an Excel worksheet at the given index. // Index starts from 0. // Returns 0 if index is invalid. BasicExcelWorksheet* BasicExcel::GetWorksheet(size_t sheetIndex) { return &(yesheets_[sheetIndex]); } // Get a pointer to an Excel worksheet that has given ANSI name. // Returns 0 if there is no Excel worksheet with the given name. BasicExcelWorksheet* BasicExcel::GetWorksheet(const char* name) { size_t maxWorksheets = yesheets_.size(); for (size_t i=0; iname_ = name; worksheet->window2_.options_ &= ~0x200; return yesheet; } // Add a new Excel worksheet with given Unicode name to the given index. // Index starts from 0. // Worksheet is added to the last position if sheetIndex == -1. // Returns a pointer to the worksheet if successful, 0 if otherwise. BasicExcelWorksheet* BasicExcel::AddWorksheet(const wchar_t* name, int sheetIndex) { size_t maxWorksheets = yesheets_.size(); for (size_t i=0; iname_ = name; worksheet->window2_.options_ &= ~0x200; return yesheet; } // Delete an Excel worksheet at the given index. // Index starts from 0. // Returns true if successful, false if otherwise. bool BasicExcel::DeleteWorksheet(size_t sheetIndex) { if (sheetIndex= FIRST_USER_FORMAT_IDX) // only write user defined formats offset += workbook_.formats_[i].RecordSize(); }} size_t maxXFs = workbook_.XFs_.size(); {for (size_t i=0; i= workbook_.sst_.strings_.size()) break; size_t stringSize = workbook_.sst_.strings_[i*workbook_.extSST_.stringsTotal_+j].StringSize(); if (relativeOffset+stringSize+3 < 8224) { relativeOffset += stringSize + 3; } else { // If have >= 12 bytes (2 for size, 1 for unicode and >=9 for data, can split string // otherwise, end record and start continue record. if (8224 - relativeOffset >= 12) { stringSize -= (8224 - relativeOffset - 3); offset += 12 + relativeOffset; relativeOffset = 0; size_t additionalContinueRecords = stringSize / 8223; // 8223 because the first byte is for unicode for (size_t k=0; k= 12 bytes (2 for size, 1 for unicode and >=9 for data, can split string // otherwise, end record and start continue record. if (8224 - relativeOffset >= 12) { stringSize -= (8224 - relativeOffset - 3); offset += 12 + relativeOffset; relativeOffset = 0; size_t additionalContinueRecords = stringSize / 8223; // 8223 because the first byte is for unicode for (size_t k=0; k, size_t> stringMap; map, size_t>::iterator stringMapIt; map, size_t> wstringMap; map, size_t>::iterator wstringMapIt; // Reset worksheets and string table. worksheets_.clear(); worksheets_.resize(maxWorksheets); workbook_.sst_.stringsTotal_ = 0; workbook_.sst_.uniqueStringsTotal_ = 0; workbook_.sst_.strings_.clear(); for (size_t s=0; s 0) worksheets_[s].window2_.options_ &= ~0x200; // References and pointers to shorten code vector& rRowBlocks = worksheets_[s].cellTable_.rowBlocks_; vector* pCellBlocks; Worksheet::CellTable::RowBlock::CellBlock* pCell; rRowBlocks.resize(maxRows/32 + (maxRows%32 ? 1 : 0)); for (size_t r=0, curRowBlock=0; rreserve(1000); for (size_t c=0; cType(); if (cellType != BasicExcelCell::UNDEFINED) // Current cell contains some data { if (worksheets_[s].index_.firstUsedRowIndex_ == 100000) { // Set firstUsedRowIndex. worksheets_[s].index_.firstUsedRowIndex_ = r; worksheets_[s].dimensions_.firstUsedRowIndex_ = r; // Resize DBCellPos. size_t nm = int(worksheets_[s].index_.firstUnusedRowIndex_ - worksheets_[s].index_.firstUsedRowIndex_ - 1) / 32 + 1; worksheets_[s].index_.DBCellPos_.resize(nm); } if (worksheets_[s].dimensions_.firstUsedColIndex_ == 1000) { // Set firstUsedColIndex. worksheets_[s].dimensions_.firstUsedColIndex_ = c; } if (newRow) { // Prepare Row and DBCell for new row with data. Worksheet::CellTable::RowBlock& rRowBlock = rRowBlocks[curRowBlock-1]; rRowBlock.rows_.push_back(row); rRowBlock.rows_.back().rowIndex_ = r; rRowBlock.rows_.back().lastCellColIndexPlusOne_ = maxCols; rRowBlock.dbcell_.offsets_.push_back(0); newRow = false; } // Create new cellblock to store cell. pCellBlocks->push_back(cellBlock); if (pCellBlocks->size()%1000==0) pCellBlocks->reserve(pCellBlocks->size()+1000); pCell = &(pCellBlocks->back()); // Store cell. switch(cellType) { case BasicExcelCell::INT: { // Check whether it is a single cell or range of cells. size_t cl = c + 1; for (; clType()==BasicExcelCell::UNDEFINED || cellNext->Type()!=cell->Type()) break; } if (cl > c+1) { // MULRK cells pCell->type_ = CODE::MULRK; // pCell->normalType_ = true; pCell->mulrk_.rowIndex_ = r; pCell->mulrk_.firstColIndex_ = c; pCell->mulrk_.lastColIndex_ = cl - 1; pCell->mulrk_.XFRK_.resize(cl-c); for (size_t i=0; cmulrk_.XFRK_[i].RKValue_ = GetRKValueFromInteger(cell->GetInteger()); pCell->mulrk_.XFRK_[i].XFRecordIndex_ = cell->GetXFormatIdx(); //MF set format index } --c; } else { // Single cell // pCell->normalType_ = true; pCell->type_ = CODE::RK; pCell->rk_.rowIndex_ = r; pCell->rk_.colIndex_ = c; pCell->rk_.value_ = GetRKValueFromInteger(cell->GetInteger()); pCell->rk_.XFRecordIndex_ = cell->GetXFormatIdx(); //MF set format index } break; } case BasicExcelCell::DOUBLE: { // Check whether it is a single cell or range of cells. // Double values which cannot be stored as RK values will be stored as single cells. bool canStoreAsRKValue = CanStoreAsRKValue(cell->GetDouble()); size_t cl = c + 1; for (; clType()==BasicExcelCell::UNDEFINED || cellNext->Type()!=cell->Type() || canStoreAsRKValue!=CanStoreAsRKValue(cellNext->GetDouble())) break; } if (cl > c+1 && canStoreAsRKValue) { // MULRK cells pCell->type_ = CODE::MULRK; // pCell->normalType_ = true; pCell->mulrk_.rowIndex_ = r; pCell->mulrk_.firstColIndex_ = c; pCell->mulrk_.lastColIndex_ = cl - 1; pCell->mulrk_.XFRK_.resize(cl-c); for (size_t i=0; cmulrk_.XFRK_[i].RKValue_ = GetRKValueFromDouble(cell->GetDouble()); pCell->mulrk_.XFRK_[i].XFRecordIndex_ = cell->GetXFormatIdx(); //MF set format index } --c; } else { // Single cell // pCell->normalType_ = true; if (canStoreAsRKValue) { pCell->type_ = CODE::RK; pCell->rk_.rowIndex_ = r; pCell->rk_.colIndex_ = c; pCell->rk_.value_ = GetRKValueFromDouble(cell->GetDouble()); pCell->rk_.XFRecordIndex_ = cell->GetXFormatIdx(); //MF set format index } else { pCell->type_ = CODE::NUMBER; pCell->number_.rowIndex_ = r; pCell->number_.colIndex_ = c; pCell->number_.value_ = cell->GetDouble(); pCell->number_.XFRecordIndex_ = cell->GetXFormatIdx(); //MF set format index } } break; } case BasicExcelCell::STRING: { // Fill cell information pCell->type_ = CODE::LABELSST; // pCell->normalType_ = true; pCell->labelsst_.rowIndex_ = r; pCell->labelsst_.colIndex_ = c; // Get cell string vector str(cell->GetStringLength()+1); cell->Get(&*(str.begin())); str.pop_back(); // Remove null character because LargeString does not store null character. // Check if string is present in Shared string table. ++workbook_.sst_.stringsTotal_; size_t maxUniqueStrings = workbook_.sst_.uniqueStringsTotal_; size_t strIndex = 0; stringMapIt = stringMap.find(str); if (stringMapIt != stringMap.end()) strIndex = stringMapIt->second; else strIndex = maxUniqueStrings; if (strIndex < maxUniqueStrings) { // String is present in Shared string table. pCell->labelsst_.SSTRecordIndex_ = strIndex; } else { // New unique string. stringMap[str] = maxUniqueStrings; workbook_.sst_.strings_.push_back(largeString); workbook_.sst_.strings_[maxUniqueStrings].name_ = str; workbook_.sst_.strings_[maxUniqueStrings].unicode_ = 0; pCell->labelsst_.SSTRecordIndex_ = maxUniqueStrings; ++workbook_.sst_.uniqueStringsTotal_; } pCell->labelsst_.XFRecordIndex_ = cell->GetXFormatIdx(); //MF set format index break; } case BasicExcelCell::WSTRING: { // Fill cell information pCell->type_ = CODE::LABELSST; // pCell->normalType_ = true; pCell->labelsst_.rowIndex_ = r; pCell->labelsst_.colIndex_ = c; // Get cell string vector str(cell->GetStringLength()+1); cell->Get(&*(str.begin())); str.pop_back(); // Remove null character because LargeString does not store null character. // Check if string is present in Shared string table. ++workbook_.sst_.stringsTotal_; size_t maxUniqueStrings = workbook_.sst_.strings_.size(); size_t strIndex = 0; wstringMapIt = wstringMap.find(str); if (wstringMapIt != wstringMap.end()) strIndex = wstringMapIt->second; else strIndex = maxUniqueStrings; if (strIndex < maxUniqueStrings) { // String is present in Shared string table. pCell->labelsst_.SSTRecordIndex_ = strIndex; } else { // New unique string wstringMap[str] = maxUniqueStrings; workbook_.sst_.strings_.push_back(largeString); workbook_.sst_.strings_[maxUniqueStrings].wname_ = str; workbook_.sst_.strings_[maxUniqueStrings].unicode_ = 1; pCell->labelsst_.SSTRecordIndex_ = maxUniqueStrings; ++workbook_.sst_.uniqueStringsTotal_; } pCell->labelsst_.XFRecordIndex_ = cell->GetXFormatIdx(); //MF set format index break; } //MF: handle formulas case BasicExcelCell::FORMULA: { // Fill cell information pCell->type_ = CODE::FORMULA; // pCell->normalType_ = true; pCell->formula_.rowIndex_ = r; pCell->formula_.colIndex_ = c; pCell->formula_.XFRecordIndex_ = cell->GetXFormatIdx(); cell->get_formula(pCell); break; } } } } } // If worksheet has no data if (worksheets_[s].index_.firstUsedRowIndex_ == 100000) { // Set firstUsedRowIndex. worksheets_[s].index_.firstUsedRowIndex_ = 0; worksheets_[s].dimensions_.firstUsedRowIndex_ = 0; // Resize DBCellPos. size_t nm = int(worksheets_[s].index_.firstUnusedRowIndex_ - worksheets_[s].index_.firstUsedRowIndex_ - 1) / 32 + 1; worksheets_[s].index_.DBCellPos_.resize(nm); } if (worksheets_[s].dimensions_.firstUsedColIndex_ == 1000) { // Set firstUsedColIndex. worksheets_[s].dimensions_.firstUsedColIndex_ = 0; } } } /************************************************************************************************************/ /************************************************************************************************************/ BasicExcelWorksheet::BasicExcelWorksheet(BasicExcel* excel, size_t sheetIndex) : excel_(excel), sheetIndex_(sheetIndex) { UpdateCells(); } // Get the current worksheet name. // Returns 0 if name is in Unicode format. char* BasicExcelWorksheet::GetAnsiSheetName() { if (!(excel_->workbook_.boundSheets_[sheetIndex_].name_.unicode_ & 1)) { return excel_->workbook_.boundSheets_[sheetIndex_].name_.name_; } else return 0; } // Get the current worksheet name. // Returns 0 if name is in Ansi format. wchar_t* BasicExcelWorksheet::GetUnicodeSheetName() { if (excel_->workbook_.boundSheets_[sheetIndex_].name_.unicode_ & 1) { return excel_->workbook_.boundSheets_[sheetIndex_].name_.wname_; } else return 0; } // Get the current worksheet name. // Returns false if name is in Unicode format. bool BasicExcelWorksheet::GetSheetName(char* name) { if (!(excel_->workbook_.boundSheets_[sheetIndex_].name_.unicode_ & 1)) { strcpy(name, excel_->workbook_.boundSheets_[sheetIndex_].name_.name_); return true; } else return false; } // Get the current worksheet name. // Returns false if name is in Ansi format. bool BasicExcelWorksheet::GetSheetName(wchar_t* name) { if (excel_->workbook_.boundSheets_[sheetIndex_].name_.unicode_ & 1) { wcscpy(name, excel_->workbook_.boundSheets_[sheetIndex_].name_.wname_); return true; } else return false; } // Rename current Excel worksheet to another ANSI name. // Returns true if successful, false if otherwise. bool BasicExcelWorksheet::Rename(const char* to) { size_t maxWorksheets = excel_->workbook_.boundSheets_.size(); for (size_t i=0; iworkbook_.boundSheets_[i].name_.unicode_ & 1) continue; if (strcmp(to, excel_->workbook_.boundSheets_[i].name_.name_) == 0) return false; } excel_->workbook_.boundSheets_[sheetIndex_].name_ = to; return true; } // Rename current Excel worksheet to another Unicode name. // Returns true if successful, false if otherwise. bool BasicExcelWorksheet::Rename(const wchar_t* to) { size_t maxWorksheets = excel_->workbook_.boundSheets_.size(); for (size_t i=0; iworkbook_.boundSheets_[i].name_.unicode_ & 1)) continue; if (wcscmp(to, excel_->workbook_.boundSheets_[i].name_.wname_) == 0) return false; } excel_->workbook_.boundSheets_[sheetIndex_].name_ = to; return true; } ///< Print entire worksheet to an output stream, separating each column with the defined delimiter and enclosing text using the defined textQualifier. ///< Leave out the textQualifier argument if do not wish to have any text qualifiers. void BasicExcelWorksheet::Print(ostream& os, char delimiter, char textQualifier) { for (size_t r=0; rType()) { case BasicExcelCell::UNDEFINED: break; case BasicExcelCell::INT: os << cell->GetInteger(); break; case BasicExcelCell::DOUBLE: os << setprecision(15) << cell->GetDouble(); break; case BasicExcelCell::STRING: { if (textQualifier != '\0') { // Get string. size_t maxLength = cell->GetStringLength(); vector cellString(maxLength+1); cell->Get(&*(cellString.begin())); // Duplicate textQualifier if found in string. vector::iterator it; size_t npos = 0; while ((it=find(cellString.begin()+npos, cellString.end(), textQualifier)) != cellString.end()) { npos = distance(cellString.begin(), cellString.insert(it, textQualifier)) + 2; } // Print out string enclosed with textQualifier. os << textQualifier << &*(cellString.begin()) << textQualifier; } else os << cell->GetString(); break; } case BasicExcelCell::WSTRING: { // Print out string enclosed with textQualifier (does not work). //os << textQualifier << cell->GetWString() << textQualifier; break; } } if (c < maxCols_-1) os << delimiter; } os << endl; } } // Total number of rows in current Excel worksheet. size_t BasicExcelWorksheet::GetTotalRows() { return maxRows_; } // Total number of columns in current Excel worksheet. size_t BasicExcelWorksheet::GetTotalCols() { return maxCols_; } // Return a pointer to an Excel cell. // row and col starts from 0. // Returns 0 if row exceeds 65535 or col exceeds 255. BasicExcelCell* BasicExcelWorksheet::Cell(size_t row, size_t col) { // Check to ensure row and col does not exceed maximum allowable range for an Excel worksheet. if (row>65535 || col>255) return 0; // Increase size of cells matrix if necessary if (col>=maxCols_) { // Increase number of columns. maxCols_ = col + 1; for (size_t i=0; i=maxRows_) { // Increase number of rows. maxRows_ = row + 1; cells_.resize(maxRows_, vector(maxCols_)); } return &(cells_[row][col]); } // Erase content of a cell. row and col starts from 0. // Returns true if successful, false if row or col exceeds range. bool BasicExcelWorksheet::EraseCell(size_t row, size_t col) { if (rowworksheets_[sheetIndex_].dimensions_; vector& rRowBlocks = excel_->worksheets_[sheetIndex_].cellTable_.rowBlocks_; vector wstr; vector str; maxRows_ = dimension.lastUsedRowIndexPlusOne_; maxCols_ = dimension.lastUsedColIndexPlusOne_; // Resize the cells to the size of the worksheet vector cellCol(maxCols_); cells_.resize(maxRows_, cellCol); size_t maxRowBlocks = rRowBlocks.size(); for (size_t i=0; i& rCellBlocks = rRowBlocks[i].cellBlocks_; size_t maxCells = rCellBlocks.size(); for (size_t j=0; j& ss = excel_->workbook_.sst_.strings_; if (ss[rCellBlocks[j].labelsst_.SSTRecordIndex_].unicode_ & 1) { wstr = ss[rCellBlocks[j].labelsst_.SSTRecordIndex_].wname_; wstr.resize(wstr.size()+1); wstr.back() = L'\0'; cells_[row][col].Set(&*(wstr.begin())); } else { str = ss[rCellBlocks[j].labelsst_.SSTRecordIndex_].name_; str.resize(str.size()+1); str.back() = '\0'; cells_[row][col].Set(&*(str.begin())); } cells_[row][col].SetXFormatIdx(rCellBlocks[j].labelsst_.XFRecordIndex_); //MF read format index break; } case CODE::MULBLANK: { size_t maxCols = rCellBlocks[j].mulblank_.lastColIndex_ - rCellBlocks[j].mulblank_.firstColIndex_ + 1; for (size_t k=0; k str(str_.size()); if (!str.empty() && Get(&*(str.begin()))) return &*(str_.begin()); else return 0; } // Get an Unicode string. // Returns 0 if cell does not contain an Unicode string. const wchar_t* BasicExcelCell::GetWString() const { vector wstr(wstr_.size()); if (!wstr.empty() && Get(&*(wstr.begin()))) return &*(wstr_.begin()); else return 0; } // Set content of current Excel cell to an integer. void BasicExcelCell::Set(int val) { SetInteger(val); } // Set content of current Excel cell to a double. void BasicExcelCell::Set(double val) { SetDouble(val); } // Set content of current Excel cell to an ANSI string. void BasicExcelCell::Set(const char* str) { SetString(str); } // Set content of current Excel cell to an Unicode string. void BasicExcelCell::Set(const wchar_t* str) { SetWString(str); } // Set content of current Excel cell to an integer. void BasicExcelCell::SetInteger(int val) { type_ = INT; ival_ = val; } // Set content of current Excel cell to a double. void BasicExcelCell::SetDouble(double val) { type_ = DOUBLE; dval_ = val; } // Set content of current Excel cell to an ANSI string. void BasicExcelCell::SetString(const char* str) { size_t length = strlen(str); if (length > 0) { type_ = STRING; str_ = vector(length+1); strcpy(&*(str_.begin()), str); wstr_.clear(); } else EraseContents(); } // Set content of current Excel cell to an Unicode string. void BasicExcelCell::SetWString(const wchar_t* str) { size_t length = wcslen(str); if (length > 0) { type_ = WSTRING; wstr_ = vector(length+1); wcscpy(&*(wstr_.begin()), str); str_.clear(); } else EraseContents(); } //MF void BasicExcelCell::SetFormula(const Worksheet::CellTable::RowBlock::CellBlock::Formula& f) { type_ = BasicExcelCell::FORMULA; size_t length = f.string_.string_.size(); str_ = vector(length+1); if (length) memcpy(&*str_.begin(), &*f.string_.string_.begin(), length); str_[length] = '\0'; wstr_.clear(); _pFormula = new Formula(f); } BasicExcelCell::Formula::Formula(const Worksheet::CellTable::RowBlock::CellBlock::Formula& f) : _formula_type(0) { _formula_type = f.type_; _formula = f.RPNtoken_; memcpy(_result, f.result_, 8); } bool BasicExcelCell::get_formula(Worksheet::CellTable::RowBlock::CellBlock* pCell) { if (type_==FORMULA && _pFormula) { pCell->formula_.type_ = _pFormula->_formula_type; memcpy(pCell->formula_.result_, _pFormula->_result, 8); pCell->formula_.RPNtoken_ = _pFormula->_formula; size_t len = str_.size(); pCell->formula_.string_.string_.resize(len); memcpy(&pCell->formula_.string_.string_[0], &*str_.begin(), len); return true; } return false; } // Erase the content of current Excel cell. // Set type to UNDEFINED. void BasicExcelCell::EraseContents() { type_ = UNDEFINED; str_.clear(); wstr_.clear(); } ///< Print cell to output stream. ///< Print a null character if cell is undefined. ostream& operator<<(ostream& os, const BasicExcelCell& cell) { switch (cell.Type()) { case BasicExcelCell::UNDEFINED: os << '\0'; break; case BasicExcelCell::INT: os << cell.GetInteger(); break; case BasicExcelCell::DOUBLE: os << cell.GetDouble(); break; case BasicExcelCell::STRING: os << cell.GetString(); break; case BasicExcelCell::WSTRING: os << cell.GetWString(); break; } return os; } } // namespace YExcel