/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * This file is part of xlslib -- A multiplatform, C/C++ library * for dynamic generation of Excel(TM) files. * * Copyright 2004 Yeico S. A. de C. V. All Rights Reserved. * Copyright 2008-2013 David Hoerl All Rights Reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY David Hoerl ''AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL David Hoerl OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "../xlslib/record.h" #include "../xlslib/datast.h" // XTRACE2() [i_a] #include "../xlslib/rectypes.h" // for factory: #include "../xlslib/font.h" #include "../xlslib/format.h" #include "../xlslib/number.h" #include "../xlslib/boolean.h" #include "../xlslib/err.h" #include "../xlslib/note.h" #include "../xlslib/formula_cell.h" #include "../xlslib/merged.h" #include "../xlslib/label.h" #include "../xlslib/index.h" #include "../xlslib/extformat.h" #include "../xlslib/continue.h" #include "../xlslib/colinfo.h" #include "../xlslib/blank.h" #include "../xlslib/recdef.h" #include "../xlslib/HPSF.h" #ifdef __BCPLUSPLUS__ #include // malloc.h needed for calloc. RLN 111208 // They may be needed for other compilers as well #include // memory.h needed for memset. RLN 111215 // These may be needed for other compilers as well. #endif using namespace xlslib_strings; namespace xlslib_core { /* *********************************** * CDataStorage class Implementation *********************************** */ CDataStorage::CDataStorage() : store(), m_DataSize(0), m_FlushStack(), m_FlushLastEndLevel(0) { store.reserve(300); m_FlushLastEndPos = 0; // .begin(); } CDataStorage::CDataStorage(size_t blobs) : store(), m_DataSize(0), m_FlushStack(), m_FlushLastEndLevel(0) { store.reserve(blobs); m_FlushLastEndPos = 0; // m_FlushStack.begin(); } CDataStorage::~CDataStorage() { // Delete all the data. (Only if it exists) // flush all lingering units BEFORE we discard the associated UnitStore entities or we'll get a nasty assertion failure. FlushEm(BACKPATCH_LEVEL_EVERYONE); if (!store.empty()) { StoreList_Itor_t x0, x1; size_t cnt = 0; x0 = store.begin(); x1 = store.end(); for(StoreList_Itor_t di = x0; di != x1; ++di) { di->Reset(); cnt++; } XTRACE2("ACTUAL: total storage unit count: ", cnt); #if OLE_DEBUG std::cerr << "ACTUAL: total unit count: " << cnt << std::endl; #endif store.resize(0); } } void CDataStorage::operator+=(CUnit* from) { /* * constructor already positioned the CUnit; make sure our assumption sticks: * * we assume a usage style like this, i.e. += add in order AND BEFORE the next * unit is requested/constructed: * * p1 = new CUnit(store, ...); * ... * store += p1; * p2 = new CUnit(store, ...); * ... * store += p2; * * and we currently FAIL for any other 'mixed' usage pattern, e.g. this will b0rk: * * p1 = new CUnit(store, ...); * p2 = new CUnit(store, ...); * ... * store += p1; * store += p2; */ XL_ASSERT(from->m_Index == (int)store.size() - 1); m_DataSize += from->GetDataSize(); // and 'persist' the data associated with the CUnit from here-on after... // (that way, we can safely 'delete CUnit' and still have the data generated by the CUnit intact) store[(size_t)from->m_Index].MakeSticky(); // and signal that we've made our data 'sticky': XL_ASSERT(from->m_Index >= 0); from->m_Index = -1 ^ from->m_Index; XL_ASSERT(from->m_Index < 0); } size_t CDataStorage::GetDataSize() const { return m_DataSize; } StoreList_Itor_t CDataStorage::begin() { return store.begin(); } StoreList_Itor_t CDataStorage::end() { return store.end(); } signed32_t CDataStorage::RequestIndex(size_t minimum_size) { signed32_t idx = static_cast(store.size()); CUnitStore &unit = *store.insert(store.end(), CUnitStore()); if (unit.Prepare(minimum_size) != NO_ERRORS) { return INVALID_STORE_INDEX; } return idx; } CUnitStore& CDataStorage::operator[](signed32_t index) { XL_ASSERT(index != INVALID_STORE_INDEX); XL_ASSERT(index >= 0 ? index < (int)store.size() : 1); XL_ASSERT(index < 0 ? (~index) < (int)store.size() : 1); return index >= 0 ? store[(size_t)index] : store[~(size_t)index]; } // Queue a new unit void CDataStorage::Push(CUnit* unit) { m_FlushStack.push_back(unit); } /* * Clip to Max Data Size, as the rest of the record will be placed in continue records. * This means that we will duplicate data. Its possible to create a new UnitStorage that takes just a pointer; * however, these records are quite rare, so why go to the effort. */ size_t CDataStorage::Clip(CUnit* unit) { XL_ASSERT(unit == m_FlushStack.back()); CRecord *record = (CRecord *)unit; // Use this record, but only record the first big chunk record->SetRecordLength(MAX_RECORD_SIZE); // No way to directly change the size of a Unit's storage space, so we fake it by telling the UnitStore that its now smaller CUnitStore& unitStore = (*this)[record->GetIndex()]; size_t realSize = unitStore.GetDataSize() - RECORD_HEADER_SIZE; unitStore.SetDataSize( MAX_RECORD_SIZE + RECORD_HEADER_SIZE ); return realSize; } void CDataStorage::FlushEm(unsigned16_t backpatch_level) { /* * delete units which don't need to live any longer. * * In the same loop, we shrink the 'stack' for * future speed and to keep storage requirements in check. */ //printf("FLUSH-EM %d\n", backpatch_level); UnitList_Itor_t start = m_FlushStack.begin(); if (m_FlushLastEndLevel == backpatch_level && backpatch_level != BACKPATCH_LEVEL_EVERYONE // do not use cached position when 'flushing all' //&& m_FlushLastEndPos != m_FlushStack.begin() && m_FlushLastEndPos != m_FlushStack.size()) { //.end()) XL_ASSERT(start != m_FlushStack.end()); start = m_FlushStack.begin() + (signed32_t)m_FlushLastEndPos; XL_ASSERT(m_FlushLastEndPos <= m_FlushStack.size()); XL_ASSERT(start != m_FlushStack.end()); start++; } UnitList_Itor_t j = start; size_t cnt = 0; size_t cntleft = 0; for (UnitList_Itor_t i = j; i != m_FlushStack.end(); i++) { CUnit *up = *i; if (up->m_Backpatching_Level <= backpatch_level) { XL_ASSERT(up != NULL); delete up; (*i) = NULL; cnt++; continue; } XL_ASSERT(up->m_Backpatching_Level <= 4); // do we need to move-copy the unit reference down as part of a shrink operation? if (i != j) { (*j) = up; } j++; cntleft++; } size_t count = (size_t)(j - m_FlushStack.begin()); #if OLE_DEBUG std::cerr << "number of records deleted: " << cnt << ", left: " << cntleft << ", new.size: " << count << std::endl; #endif m_FlushStack.resize(count); XL_ASSERT(m_FlushStack.size() == count); // remember for the next time around m_FlushLastEndLevel = backpatch_level; j = m_FlushStack.end(); if (m_FlushStack.size() > 0) { j--; } else { #if OLE_DEBUG std::cerr << "empty!" << std::endl; #endif } m_FlushLastEndPos = (size_t)(j - m_FlushStack.begin()); } void CDataStorage::FlushLowerLevelUnits(const CUnit *unit) { if (unit && unit->m_Backpatching_Level > 0) { FlushEm(unit->m_Backpatching_Level - 1); } } CUnit* CDataStorage::MakeCUnit() { return new CUnit(*this); } CRecord* CDataStorage::MakeCRecord() { return new CRecord(*this); } CRow* CDataStorage::MakeCRow(unsigned32_t rownum, unsigned32_t firstcol, unsigned32_t lastcol, unsigned16_t rowheight, const xf_t* xformat) { return new CRow(*this, rownum, firstcol, lastcol, rowheight, xformat); } CBof* CDataStorage::MakeCBof(unsigned16_t boftype) { return new CBof(*this, boftype); } CEof* CDataStorage::MakeCEof() { return new CEof(*this); } CDimension* CDataStorage::MakeCDimension(unsigned32_t minRow, unsigned32_t maxRow, unsigned32_t minCol, unsigned32_t maxCol) { return new CDimension(*this, minRow, maxRow, minCol, maxCol); } CWindow1* CDataStorage::MakeCWindow1(const window1& wind1) { return new CWindow1(*this, wind1); } CWindow2* CDataStorage::MakeCWindow2(bool isActive) { return new CWindow2(*this, isActive); } CDateMode* CDataStorage::MakeCDateMode() { return new CDateMode(*this); } CStyle* CDataStorage::MakeCStyle(const style_t* styledef) { return new CStyle(*this, styledef); } CBSheet* CDataStorage::MakeCBSheet(const boundsheet_t* bsheetdef) { return new CBSheet(*this, bsheetdef); } CFormat* CDataStorage::MakeCFormat(const format_t* formatdef) { return new CFormat(*this, formatdef); } CFont* CDataStorage::MakeCFont(const font_t* fontdef) { return new CFont(*this, fontdef); } CNumber* CDataStorage::MakeCNumber(const number_t& numdef) { return new CNumber(*this, numdef); } CBoolean* CDataStorage::MakeCBoolean(const boolean_t& booldef) { return new CBoolean(*this, booldef); } CErr* CDataStorage::MakeCErr(const err_t& errdef) { return new CErr(*this, errdef); } CNote* CDataStorage::MakeCNote(const note_t& notedef) { return new CNote(*this, notedef); } CFormula* CDataStorage::MakeCFormula(const formula_cell_t& fdef) { return new CFormula(*this, fdef); } CMergedCells* CDataStorage::MakeCMergedCells() { return new CMergedCells(*this); } CLabel* CDataStorage::MakeCLabel(const label_t& labeldef) { return new CLabel(*this, labeldef); } CIndex* CDataStorage::MakeCIndex(unsigned32_t firstrow, unsigned32_t lastrow) { return new CIndex(*this, firstrow, lastrow); } CExtFormat* CDataStorage::MakeCExtFormat(const xf_t* xfdef) { return new CExtFormat(*this, xfdef); } CContinue* CDataStorage::MakeCContinue(CUnit* unit, const unsigned8_t* data, size_t size) { return new CContinue(unit, data, size); } CPalette* CDataStorage::MakeCPalette(const color_entry_t *colors) { return new CPalette(*this, colors); } CColInfo* CDataStorage::MakeCColInfo(const colinfo_t* newci) { return new CColInfo(*this, newci); } CBlank* CDataStorage::MakeCBlank(const blank_t& blankdef) { return new CBlank(*this, blankdef); } CCodePage* CDataStorage::MakeCCodePage(unsigned16_t boftype) { return new CCodePage(*this, boftype); } CDBCell* CDataStorage::MakeCDBCell(size_t startblock) { return new CDBCell(*this, startblock); } CHPSFdoc* CDataStorage::MakeCHPSFdoc(const hpsf_doc_t &docdef) { return new CHPSFdoc(*this, docdef); } CUnit* CDataStorage::MakeCExternBook(unsigned16_t sheet_count) { CRecord *supbook= new CRecord(*this); supbook->Inflate(8); supbook->SetRecordType(RECTYPE_SUPBOOK); supbook->SetRecordLength(4); supbook->AddValue16(sheet_count); supbook->AddValue8(0x01); supbook->AddValue8(0x04); return supbook; } CUnit* CDataStorage::MakeDrawingGroup(const Boundsheet_Vect_t& bsheets) { CRecord* data = NULL; Boundsheet_Vect_CItor_t bsheet_end = bsheets.end(); unsigned32_t total_notes = 0; unsigned32_t sheets_with_notes = 0; for(Boundsheet_Vect_CItor_t bsheet = bsheets.begin(); bsheet != bsheet_end; ++bsheet) { unsigned32_t count = (*bsheet)->GetNoteCount(); if(count) { ++sheets_with_notes; total_notes += count; } } if(total_notes) { sheet_notes *notes = new sheet_notes[sheets_with_notes]; unsigned16_t notes_idx = 0; unsigned16_t idx = 0; for(Boundsheet_Vect_CItor_t bsheet = bsheets.begin(); bsheet != bsheet_end; ++bsheet) { unsigned16_t count = (*bsheet)->GetNoteCount(); if(count) { sheet_notes tmp = { idx, count }; notes[notes_idx] = tmp; ++notes_idx; } ++idx; } data = new CRecord(*this); CNote::MakeDrawingGroup(data, sheets_with_notes, notes); delete[] notes; } return data; } CUnit* CDataStorage::MakeCExternSheet(const Boundsheet_Vect_t& sheets) { CRecord *externsheet= new CRecord(*this); externsheet->Inflate(4+2+sheets.size()*6); externsheet->SetRecordType(RECTYPE_EXTERNSHEET); externsheet->SetRecordLength(2+sheets.size()*6); externsheet->AddValue16(static_cast(sheets.size())); for (size_t i=0; iAddValue16(0); externsheet->AddValue16(static_cast(i)); externsheet->AddValue16(static_cast(i)); } return externsheet; } // Old makeSST, commented out, still in svn before 1/1/2014 (not sure why it was kept around) CUnit* CDataStorage::MakeSST(const Label_Vect_t& labels) { CRecord *record = new CRecord(*this); size_t count = labels.size(); record->SetAlreadyContinued(true); size_t offset = 0; // offset of last written data record->SetRecordTypeIndexed(RECTYPE_SST, 0); record->AddValue32(static_cast(count)); // usages record->AddValue32(static_cast(count)); // number of strings to follow size_t currSize = record->GetDataSize(); cLabel_Vect_Itor_t label_end = labels.end(); for(cLabel_Vect_Itor_t label = labels.begin(); label != label_end; ++label) { const label_t *currLabel = *label; u16string str16 = currLabel->GetStrLabel(); size_t strLen; bool isAscii; size_t strSize = record->UnicodeStringLength(str16, strLen, isAscii, CUnit::LEN2_FLAGS_UNICODE /* = LEN2_FLAGS_UNICODE */ ); if(strSize > MAX_RECORD_SIZE) { static const unsigned16_t tooLong[] = { 'L', 'e', 'n', 'g', 't', 'h', ' ', 't', 'o', 'o', ' ', 'l', 'o', 'n', 'g', '!' , 0}; str16 = (xchar16_t *)(tooLong); // cannot static_cast or const_cast this expression strSize = record->UnicodeStringLength(str16, strLen, isAscii, CUnit::LEN2_FLAGS_UNICODE /* = LEN2_FLAGS_UNICODE */ ); } //printf("TEST: (currSize=%ld + strSize=%ld ) offset=%ld\n", currSize, strSize, offset); // Payload is always 4 less than currSize, so account for that here if((currSize + strSize) > (MAX_RECORD_SIZE+4)) { record->SetRecordLengthIndexed(currSize-RECORD_HEADER_SIZE, offset); offset = record->GetDataSize(); // new offset is where we are now //printf("CHUNK: size=%ld END=%ld\n", currSize, offset); record->AddFixedDataArray(0, 4); // space for header record->SetRecordTypeIndexed(RECTYPE_CONTINUE, offset); } record->AddUnicodeString(str16, CUnit::LEN2_FLAGS_UNICODE); currSize = record->GetDataSize() - offset; // at end so its valid when we break out of the loop //printf("WROTE %ld bytes: total=%ld currBlock=%ld offset=%ld\n", strSize, record->GetDataSize(), currSize, offset); } //totalSize = record->GetDataSize(); // total size of this record //printf("FINAL: cursize=%ld offset=%ld\n", currSize, offset); record->SetRecordLengthIndexed(currSize-RECORD_HEADER_SIZE, offset); //printf("TOTAL STRING SIZE: %ld\n", record->GetDataSize()); return record; } CUnitStore::CUnitStore() : m_varying_width(false), m_is_in_use(false), m_is_sticky(false), m_nDataSize(0) { memset(&s, 0, sizeof(s)); XL_ASSERT(s.vary.m_pData == NULL); } CUnitStore::~CUnitStore() { Reset(); XL_ASSERT(s.vary.m_pData == NULL); } /* * This copy constructor is required as otherwise you'd get nuked by CDataStore * when it has to redimension its vector store when more units than * anticipated are requested: internally, STL detroys each unit during this * vector resize operation, so we'll need to copy the data to new space, especially * when we're m_varying_width !!! */ CUnitStore::CUnitStore(const CUnitStore &src) { if (&src == this) { return; } m_varying_width = src.m_varying_width; m_is_in_use = src.m_is_in_use; m_is_sticky = src.m_is_sticky; m_nDataSize = src.m_nDataSize; if (!m_varying_width) { XL_ASSERT(m_nDataSize <= FIXEDWIDTH_STORAGEUNIT_SIZE); memcpy(&s, &src.s, sizeof(s)); } else { XL_ASSERT(m_is_in_use); XL_ASSERT(src.s.vary.m_nSize > 0); s.vary.m_pData = (unsigned8_t *)malloc(src.s.vary.m_nSize); if (!s.vary.m_pData) { // ret = ERR_UNABLE_TOALLOCATE_MEMORY; m_nDataSize = s.vary.m_nSize = 0; } else { memcpy(s.vary.m_pData, src.s.vary.m_pData, m_nDataSize); s.vary.m_nSize = src.s.vary.m_nSize; } } } signed8_t CUnitStore::Prepare(size_t minimum_size) { signed8_t ret = NO_ERRORS; // allocate space in the 'variable sized store' if we cannot fit in a fixed-width unit: if (minimum_size <= FIXEDWIDTH_STORAGEUNIT_SIZE) { m_varying_width = false; m_is_in_use = true; m_is_sticky = false; m_nDataSize = 0; memset(&s, 0, sizeof(s)); // range: 0 ... +oo } else { m_varying_width = true; m_is_in_use = true; m_is_sticky = false; m_nDataSize = 0; memset(&s, 0, sizeof(s)); XL_ASSERT(s.vary.m_pData == NULL); if (minimum_size > 0) { s.vary.m_pData = (unsigned8_t *)malloc(minimum_size); if (!s.vary.m_pData) { ret = ERR_UNABLE_TOALLOCATE_MEMORY; minimum_size = 0; } s.vary.m_nSize = minimum_size; } } return ret; } void CUnitStore::Reset() { if (m_varying_width && s.vary.m_pData) { XL_ASSERT(m_is_in_use); free((void *)s.vary.m_pData); } m_varying_width = false; m_is_in_use = false; m_is_sticky = false; m_nDataSize = 0; memset(&s, 0, sizeof(s)); XL_ASSERT(s.vary.m_pData == NULL); } signed8_t CUnitStore::Resize(size_t newlen) { signed8_t ret = NO_ERRORS; XL_ASSERT(m_is_in_use); XL_ASSERT(newlen > 0); XL_ASSERT(newlen >= m_nDataSize); if (!m_varying_width) { if (newlen > FIXEDWIDTH_STORAGEUNIT_SIZE) { // turn this node into a varying-width unit store: unsigned8_t *p = (unsigned8_t *)malloc(newlen); if (!p) { ret = ERR_UNABLE_TOALLOCATE_MEMORY; newlen = 0; } else { memcpy(p, s.fixed.m_pData, m_nDataSize); } s.vary.m_pData = p; s.vary.m_nSize = newlen; m_varying_width = true; } } else { if (newlen != s.vary.m_nSize) { if (!s.vary.m_pData) { XL_ASSERT(m_nDataSize == 0); s.vary.m_pData = (unsigned8_t *)malloc(newlen); } else { s.vary.m_pData = (unsigned8_t *)realloc((void *)s.vary.m_pData, newlen); } if (!s.vary.m_pData) { ret = ERR_UNABLE_TOALLOCATE_MEMORY; newlen = 0; } s.vary.m_nSize = newlen; } } return ret; } signed8_t CUnitStore::Init(const unsigned8_t *data, size_t size, size_t datasize) { signed8_t ret; XL_ASSERT(m_is_in_use); XL_ASSERT(size > 0); XL_ASSERT(datasize <= size); ret = Resize(size); if (ret == NO_ERRORS) { memcpy(GetBuffer(), data, datasize); SetDataSize(datasize); } return ret; } signed8_t CUnitStore::InitWithValue(unsigned8_t value, size_t size) { signed8_t ret; XL_ASSERT(m_is_in_use); XL_ASSERT(size > 0); ret = Resize(size); if (ret == NO_ERRORS) { memset(GetBuffer(), value, size); SetDataSize(size); } return ret; } }